diff options
author | Wim Van Sebroeck <wim@iguana.be> | 2007-05-01 06:53:01 +0000 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2007-05-01 06:53:01 +0000 |
commit | 48a7afe314bfc4d7f50e1608632f503dbba7e013 (patch) | |
tree | 4a80e6b96321a71affd1bacea817de93be08894b /drivers/media/video | |
parent | fb8f7ba077b5c665432082ab205bcd2cb01f6a3c (diff) | |
parent | dc87c3985e9b442c60994308a96f887579addc39 (diff) |
Merge /pub/scm/linux/kernel/git/torvalds/linux-2.6
Diffstat (limited to 'drivers/media/video')
135 files changed, 17635 insertions, 2017 deletions
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 7a6105153f2..bc773781993 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -577,14 +577,14 @@ config VIDEO_ZORAN_AVS6EYES config VIDEO_MEYE tristate "Sony Vaio Picturebook Motion Eye Video For Linux" - depends on PCI && SONYPI && VIDEO_V4L1 + depends on PCI && SONY_LAPTOP && VIDEO_V4L1 ---help--- This is the video4linux driver for the Motion Eye camera found in the Vaio Picturebook laptops. Please read the material in <file:Documentation/video4linux/meye.txt> for more information. - If you say Y or M here, you need to say Y or M to "Sony Programmable - I/O Control Device" in the character device section. + If you say Y or M here, you need to say Y or M to "Sony Laptop + Extras" in the misc device section. To compile this driver as a module, choose M here: the module will be called meye. @@ -647,6 +647,8 @@ config VIDEO_HEXIUM_GEMINI source "drivers/media/video/cx88/Kconfig" +source "drivers/media/video/ivtv/Kconfig" + config VIDEO_M32R_AR tristate "AR devices" depends on M32R && VIDEO_V4L1 @@ -761,6 +763,18 @@ source "drivers/media/video/zc0301/Kconfig" source "drivers/media/video/pwc/Kconfig" +config USB_ZR364XX + tristate "USB ZR364XX Camera support" + depends on USB && VIDEO_V4L2 + ---help--- + Say Y here if you want to connect this type of camera to your + computer's USB port. + See <file:Documentation/video4linux/zr364xx.txt> for more info + and list of supported cameras. + + To compile this driver as a module, choose M here: the + module will be called zr364xx. + endmenu # V4L USB devices endmenu diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 44ccaed40b4..9c2de501612 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o obj-$(CONFIG_VIDEO_MEYE) += meye.o obj-$(CONFIG_VIDEO_SAA7134) += ir-kbd-i2c.o saa7134/ obj-$(CONFIG_VIDEO_CX88) += cx88/ +obj-$(CONFIG_VIDEO_IVTV) += ivtv/ obj-$(CONFIG_VIDEO_EM28XX) += em28xx/ obj-$(CONFIG_VIDEO_USBVISION) += usbvision/ obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o @@ -99,6 +100,7 @@ obj-$(CONFIG_USB_OV511) += ov511.o obj-$(CONFIG_USB_SE401) += se401.o obj-$(CONFIG_USB_STV680) += stv680.o obj-$(CONFIG_USB_W9968CF) += w9968cf.o +obj-$(CONFIG_USB_ZR364XX) += zr364xx.o obj-$(CONFIG_USB_SN9C102) += sn9c102/ obj-$(CONFIG_USB_ET61X251) += et61x251/ diff --git a/drivers/media/video/bt8xx/bttv-cards.c b/drivers/media/video/bt8xx/bttv-cards.c index 6addc42df04..6b31e50fb95 100644 --- a/drivers/media/video/bt8xx/bttv-cards.c +++ b/drivers/media/video/bt8xx/bttv-cards.c @@ -291,6 +291,9 @@ static struct CARD { { 0x15409511, BTTV_BOARD_ACORP_Y878F, "Acorp Y878F" }, + { 0x53534149, BTTV_BOARD_SSAI_SECURITY, "SSAI Security Video Interface" }, + { 0x5353414a, BTTV_BOARD_SSAI_ULTRASOUND, "SSAI Ultrasound Video Interface" }, + /* likely broken, vendor id doesn't match the other magic views ... * { 0xa0fca04f, BTTV_BOARD_MAGICTVIEW063, "Guillemot Maxi TV Video 3" }, */ @@ -2907,6 +2910,28 @@ struct tvcard bttv_tvcards[] = { .has_radio = 1, .has_remote = 1, }, + [BTTV_BOARD_SSAI_SECURITY] = { + .name = "SSAI Security Video Interface", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .muxsel = { 0, 1, 2, 3 }, + .tuner_type = -1, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + }, + [BTTV_BOARD_SSAI_ULTRASOUND] = { + .name = "SSAI Ultrasound Video Interface", + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 0, 1, 3 }, + .tuner_type = -1, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + }, }; static const unsigned int bttv_num_tvcards = ARRAY_SIZE(bttv_tvcards); @@ -2970,20 +2995,20 @@ void __devinit bttv_idcard(struct bttv *btv) if (UNSET != audiomux[0]) { gpiobits = 0; - for (i = 0; i < 4; i++) { + for (i = 0; i < ARRAY_SIZE(bttv_tvcards->gpiomux); i++) { bttv_tvcards[btv->c.type].gpiomux[i] = audiomux[i]; gpiobits |= audiomux[i]; } } else { gpiobits = audioall; - for (i = 0; i < 4; i++) { + for (i = 0; i < ARRAY_SIZE(bttv_tvcards->gpiomux); i++) { bttv_tvcards[btv->c.type].gpiomux[i] = audioall; } } bttv_tvcards[btv->c.type].gpiomask = (UNSET != gpiomask) ? gpiomask : gpiobits; printk(KERN_INFO "bttv%d: gpio config override: mask=0x%x, mux=", btv->c.nr,bttv_tvcards[btv->c.type].gpiomask); - for (i = 0; i < 5; i++) { + for (i = 0; i < ARRAY_SIZE(bttv_tvcards->gpiomux); i++) { printk("%s0x%x", i ? "," : "", bttv_tvcards[btv->c.type].gpiomux[i]); } printk("\n"); @@ -3638,7 +3663,7 @@ static int __devinit pvr_altera_load(struct bttv *btv, u8 *micro, u32 microlen) for (n = 0; n < microlen; n++) { bits = micro[n]; - for ( i = 0 ; i < 8 ; i++ ) { + for (i = 0 ; i < 8 ; i++) { gpio_bits(BTTV_ALT_DCLK,0); if (bits & 0x01) gpio_bits(BTTV_ALT_DATA,BTTV_ALT_DATA); @@ -3691,7 +3716,7 @@ static void __devinit osprey_eeprom(struct bttv *btv) /* this might be an antique... check for MMAC label in eeprom */ if ((ee[0]=='M') && (ee[1]=='M') && (ee[2]=='A') && (ee[3]=='C')) { unsigned char checksum = 0; - for (i =0; i<21; i++) + for (i = 0; i < 21; i++) checksum += ee[i]; if (checksum != ee[21]) return; @@ -3703,12 +3728,13 @@ static void __devinit osprey_eeprom(struct bttv *btv) unsigned short type; int offset = 4*16; - for(; offset < 8*16; offset += 16) { + for (; offset < 8*16; offset += 16) { unsigned short checksum = 0; /* verify the checksum */ - for(i = 0; i<14; i++) checksum += ee[i+offset]; - checksum = ~checksum; /* no idea why */ - if ((((checksum>>8)&0x0FF) == ee[offset+14]) && + for (i = 0; i < 14; i++) + checksum += ee[i+offset]; + checksum = ~checksum; /* no idea why */ + if ((((checksum>>8)&0x0FF) == ee[offset+14]) && ((checksum & 0x0FF) == ee[offset+15])) { break; } @@ -3721,7 +3747,6 @@ static void __devinit osprey_eeprom(struct bttv *btv) type = (ee[offset+4]<<8) | (ee[offset+5]); switch(type) { - /* 848 based */ case 0x0004: btv->c.type = BTTV_BOARD_OSPREY1x0_848; @@ -4149,8 +4174,7 @@ static int tea5757_read(struct bttv *btv) } dprintk("bttv%d: tea5757:",btv->c.nr); - for(i = 0; i < 24; i++) - { + for (i = 0; i < 24; i++) { udelay(5); bus_high(btv,btv->mbox_clk); udelay(5); @@ -4182,8 +4206,7 @@ static int tea5757_write(struct bttv *btv, int value) dprintk("bttv%d: tea5757: write 0x%X\n", btv->c.nr, value); bus_low(btv,btv->mbox_clk); bus_high(btv,btv->mbox_we); - for(i = 0; i < 25; i++) - { + for (i = 0; i < 25; i++) { if (reg & 0x1000000) bus_high(btv,btv->mbox_data); else @@ -4755,7 +4778,7 @@ static void kodicom4400r_init(struct bttv *btv) gpio_write(1 << 9); /* reset MUX */ gpio_write(0); /* Preset camera 0 to the 4 controllers */ - for (ix=0; ix<4; ix++) { + for (ix = 0; ix < 4; ix++) { sw_status[ix] = ix; kodicom4400r_write(btv, ix, ix, 1); } diff --git a/drivers/media/video/bt8xx/bttv-driver.c b/drivers/media/video/bt8xx/bttv-driver.c index 5720b77ac9a..1c38723d316 100644 --- a/drivers/media/video/bt8xx/bttv-driver.c +++ b/drivers/media/video/bt8xx/bttv-driver.c @@ -164,6 +164,24 @@ static ssize_t show_card(struct class_device *cd, char *buf) static CLASS_DEVICE_ATTR(card, S_IRUGO, show_card, NULL); /* ----------------------------------------------------------------------- */ +/* dvb auto-load setup */ +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + request_module("dvb-bt8xx"); +} + +static void request_modules(struct bttv *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#endif /* CONFIG_MODULES */ + + +/* ----------------------------------------------------------------------- */ /* static data */ /* special timing tables from conexant... */ @@ -4769,9 +4787,11 @@ static int __devinit bttv_probe(struct pci_dev *dev, disclaim_video_lines(btv); } - /* add subdevices */ - if (bttv_tvcards[btv->c.type].has_dvb) + /* add subdevices and autoload dvb-bt8xx if needed */ + if (bttv_tvcards[btv->c.type].has_dvb) { bttv_sub_add_device(&btv->c, "dvb"); + request_modules(btv); + } bttv_input_init(btv); diff --git a/drivers/media/video/bt8xx/bttv-gpio.c b/drivers/media/video/bt8xx/bttv-gpio.c index ba081f6f8c8..84154c26f9c 100644 --- a/drivers/media/video/bt8xx/bttv-gpio.c +++ b/drivers/media/video/bt8xx/bttv-gpio.c @@ -71,7 +71,6 @@ struct bus_type bttv_sub_bus_type = { .probe = bttv_sub_probe, .remove = bttv_sub_remove, }; -EXPORT_SYMBOL(bttv_sub_bus_type); static void release_sub_device(struct device *dev) { @@ -152,7 +151,6 @@ void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits) btwrite(data,BT848_GPIO_OUT_EN); spin_unlock_irqrestore(&btv->gpio_lock,flags); } -EXPORT_SYMBOL(bttv_gpio_inout); u32 bttv_gpio_read(struct bttv_core *core) { @@ -162,7 +160,6 @@ u32 bttv_gpio_read(struct bttv_core *core) value = btread(BT848_GPIO_DATA); return value; } -EXPORT_SYMBOL(bttv_gpio_read); void bttv_gpio_write(struct bttv_core *core, u32 value) { @@ -170,7 +167,6 @@ void bttv_gpio_write(struct bttv_core *core, u32 value) btwrite(value,BT848_GPIO_DATA); } -EXPORT_SYMBOL(bttv_gpio_write); void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits) { @@ -185,7 +181,6 @@ void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits) btwrite(data,BT848_GPIO_DATA); spin_unlock_irqrestore(&btv->gpio_lock,flags); } -EXPORT_SYMBOL(bttv_gpio_bits); /* * Local variables: diff --git a/drivers/media/video/bt8xx/bttv-i2c.c b/drivers/media/video/bt8xx/bttv-i2c.c index 62b873076e0..0dfa49b6641 100644 --- a/drivers/media/video/bt8xx/bttv-i2c.c +++ b/drivers/media/video/bt8xx/bttv-i2c.c @@ -412,7 +412,7 @@ static void do_i2c_scan(char *name, struct i2c_client *c) unsigned char buf; int i,rc; - for (i = 0; i < 128; i++) { + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { c->addr = i; rc = i2c_master_recv(c,&buf,0); if (rc < 0) diff --git a/drivers/media/video/bt8xx/bttv-if.c b/drivers/media/video/bt8xx/bttv-if.c index 19b564ab0e9..ecf07988cd3 100644 --- a/drivers/media/video/bt8xx/bttv-if.c +++ b/drivers/media/video/bt8xx/bttv-if.c @@ -33,32 +33,16 @@ #include "bttvp.h" -EXPORT_SYMBOL(bttv_get_cardinfo); EXPORT_SYMBOL(bttv_get_pcidev); -EXPORT_SYMBOL(bttv_get_id); EXPORT_SYMBOL(bttv_gpio_enable); EXPORT_SYMBOL(bttv_read_gpio); EXPORT_SYMBOL(bttv_write_gpio); -EXPORT_SYMBOL(bttv_get_gpio_queue); -EXPORT_SYMBOL(bttv_i2c_call); /* ----------------------------------------------------------------------- */ /* Exported functions - for other modules which want to access the */ /* gpio ports (IR for example) */ /* see bttv.h for comments */ -int bttv_get_cardinfo(unsigned int card, int *type, unsigned *cardid) -{ - printk("The bttv_* interface is obsolete and will go away,\n" - "please use the new, sysfs based interface instead.\n"); - if (card >= bttv_num) { - return -1; - } - *type = bttvs[card].c.type; - *cardid = bttvs[card].cardid; - return 0; -} - struct pci_dev* bttv_get_pcidev(unsigned int card) { if (card >= bttv_num) @@ -66,16 +50,6 @@ struct pci_dev* bttv_get_pcidev(unsigned int card) return bttvs[card].c.pci; } -int bttv_get_id(unsigned int card) -{ - printk("The bttv_* interface is obsolete and will go away,\n" - "please use the new, sysfs based interface instead.\n"); - if (card >= bttv_num) { - return -1; - } - return bttvs[card].c.type; -} - int bttv_gpio_enable(unsigned int card, unsigned long mask, unsigned long data) { @@ -130,28 +104,6 @@ int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data) return 0; } -wait_queue_head_t* bttv_get_gpio_queue(unsigned int card) -{ - struct bttv *btv; - - if (card >= bttv_num) { - return NULL; - } - - btv = &bttvs[card]; - if (bttvs[card].shutdown) { - return NULL; - } - return &btv->gpioq; -} - -void bttv_i2c_call(unsigned int card, unsigned int cmd, void *arg) -{ - if (card >= bttv_num) - return; - bttv_call_i2c_clients(&bttvs[card], cmd, arg); -} - /* * Local variables: * c-basic-offset: 8 diff --git a/drivers/media/video/bt8xx/bttv.h b/drivers/media/video/bt8xx/bttv.h index 5491acbdaf6..f821ba69db9 100644 --- a/drivers/media/video/bt8xx/bttv.h +++ b/drivers/media/video/bt8xx/bttv.h @@ -168,6 +168,8 @@ #define BTTV_BOARD_SABRENT_TVFM 0x8e #define BTTV_BOARD_HAUPPAUGE_IMPACTVCB 0x8f #define BTTV_BOARD_MACHTV_MAGICTV 0x90 +#define BTTV_BOARD_SSAI_SECURITY 0x91 +#define BTTV_BOARD_SSAI_ULTRASOUND 0x92 /* more card-specific defines */ #define PT2254_L_CHANNEL 0x10 @@ -260,17 +262,8 @@ extern int bttv_handle_chipset(struct bttv *btv); /* this obsolete -- please use the sysfs-based interface below for new code */ -/* returns card type + card ID (for bt878-based ones) - for possible values see lines below beginning with #define BTTV_BOARD_UNKNOWN - returns negative value if error occurred -*/ -extern int bttv_get_cardinfo(unsigned int card, int *type, - unsigned int *cardid); extern struct pci_dev* bttv_get_pcidev(unsigned int card); -/* obsolete, use bttv_get_cardinfo instead */ -extern int bttv_get_id(unsigned int card); - /* sets GPOE register (BT848_GPIO_OUT_EN) to new value: data | (current_GPOE_value & ~mask) returns negative value if error occurred @@ -290,20 +283,6 @@ extern int bttv_read_gpio(unsigned int card, unsigned long *data); extern int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data); -/* returns pointer to task queue which can be used as parameter to - interruptible_sleep_on - in interrupt handler if BT848_INT_GPINT bit is set - this queue is activated - (wake_up_interruptible) and following call to the function bttv_read_gpio - should return new value of GPDATA, - returns NULL value if error occurred or queue is not available - WARNING: because there is no buffer for GPIO data, one MUST - process data ASAP -*/ -extern wait_queue_head_t* bttv_get_gpio_queue(unsigned int card); - -/* call i2c clients -*/ -extern void bttv_i2c_call(unsigned int card, unsigned int cmd, void *arg); diff --git a/drivers/media/video/bt8xx/bttvp.h b/drivers/media/video/bt8xx/bttvp.h index ad79b8d5343..8f44f02029b 100644 --- a/drivers/media/video/bt8xx/bttvp.h +++ b/drivers/media/video/bt8xx/bttvp.h @@ -434,6 +434,9 @@ struct bttv { unsigned int users; struct bttv_fh init; + /* used to make dvb-bt8xx autoloadable */ + struct work_struct request_module_wk; + /* Default (0) and current (1) video capturing and overlay cropping parameters in bttv_tvnorm.cropcap units. Protected by bttv.lock. */ diff --git a/drivers/media/video/cafe_ccic.c b/drivers/media/video/cafe_ccic.c index 710c11a6829..96254dbaf62 100644 --- a/drivers/media/video/cafe_ccic.c +++ b/drivers/media/video/cafe_ccic.c @@ -4,6 +4,7 @@ * sensor. * * Copyright 2006 One Laptop Per Child Association, Inc. + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> * * Written by Jonathan Corbet, corbet@lwn.net. * @@ -22,6 +23,7 @@ #include <linux/spinlock.h> #include <linux/videodev2.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <linux/device.h> #include <linux/wait.h> #include <linux/list.h> @@ -36,7 +38,7 @@ #include "cafe_ccic-regs.h" -#define CAFE_VERSION 0x000001 +#define CAFE_VERSION 0x000002 /* @@ -164,7 +166,7 @@ struct cafe_camera struct tasklet_struct s_tasklet; /* Current operating parameters */ - enum v4l2_chip_ident sensor_type; /* Currently ov7670 only */ + u32 sensor_type; /* Currently ov7670 only */ struct v4l2_pix_format pix_format; /* Locks */ @@ -704,7 +706,13 @@ static void cafe_ctlr_init(struct cafe_camera *cam) cafe_reg_write(cam, REG_GL_CSR, GCSR_SRS|GCSR_MRS); /* Needed? */ cafe_reg_write(cam, REG_GL_CSR, GCSR_SRC|GCSR_MRC); cafe_reg_write(cam, REG_GL_CSR, GCSR_SRC|GCSR_MRS); + /* + * Here we must wait a bit for the controller to come around. + */ + spin_unlock_irqrestore(&cam->dev_lock, flags); mdelay(5); /* FIXME revisit this */ + spin_lock_irqsave(&cam->dev_lock, flags); + cafe_reg_write(cam, REG_GL_CSR, GCSR_CCIC_EN|GCSR_SRC|GCSR_MRC); cafe_reg_set_bit(cam, REG_GL_IMASK, GIMSK_CCIC_EN); /* @@ -772,9 +780,9 @@ static void cafe_ctlr_power_up(struct cafe_camera *cam) * Control 1 is power down, set to 0 to operate. */ cafe_reg_write(cam, REG_GPR, GPR_C1EN|GPR_C0EN); /* pwr up, reset */ - mdelay(1); /* Marvell says 1ms will do it */ +// mdelay(1); /* Marvell says 1ms will do it */ cafe_reg_write(cam, REG_GPR, GPR_C1EN|GPR_C0EN|GPR_C0); - mdelay(1); /* Enough? */ +// mdelay(1); /* Enough? */ spin_unlock_irqrestore(&cam->dev_lock, flags); } @@ -818,6 +826,7 @@ static int __cafe_cam_reset(struct cafe_camera *cam) */ static int cafe_cam_init(struct cafe_camera *cam) { + struct v4l2_chip_ident chip = { V4L2_CHIP_MATCH_I2C_ADDR, 0, 0, 0 }; int ret; mutex_lock(&cam->s_mutex); @@ -827,9 +836,11 @@ static int cafe_cam_init(struct cafe_camera *cam) ret = __cafe_cam_reset(cam); if (ret) goto out; - ret = __cafe_cam_cmd(cam, VIDIOC_INT_G_CHIP_IDENT, &cam->sensor_type); + chip.match_chip = cam->sensor->addr; + ret = __cafe_cam_cmd(cam, VIDIOC_G_CHIP_IDENT, &chip); if (ret) goto out; + cam->sensor_type = chip.ident; // if (cam->sensor->addr != OV7xx0_SID) { if (cam->sensor_type != V4L2_IDENT_OV7670) { cam_err(cam, "Unsupported sensor type %d", cam->sensor->addr); @@ -1792,18 +1803,19 @@ static void cafe_frame_tasklet(unsigned long data) if (list_empty(&cam->sb_avail)) break; /* Leave it valid, hope for better later */ clear_bit(bufno, &cam->flags); - /* - * We could perhaps drop the spinlock during this - * big copy. Something to consider. - */ sbuf = list_entry(cam->sb_avail.next, struct cafe_sio_buffer, list); + /* + * Drop the lock during the big copy. This *should* be safe... + */ + spin_unlock_irqrestore(&cam->dev_lock, flags); memcpy(sbuf->buffer, cam->dma_bufs[bufno], cam->pix_format.sizeimage); sbuf->v4lbuf.bytesused = cam->pix_format.sizeimage; sbuf->v4lbuf.sequence = cam->buf_seq[bufno]; sbuf->v4lbuf.flags &= ~V4L2_BUF_FLAG_QUEUED; sbuf->v4lbuf.flags |= V4L2_BUF_FLAG_DONE; + spin_lock_irqsave(&cam->dev_lock, flags); list_move_tail(&sbuf->list, &cam->sb_full); } if (! list_empty(&cam->sb_full)) @@ -2107,6 +2119,7 @@ static int cafe_pci_probe(struct pci_dev *pdev, cam->v4ldev = cafe_v4l_template; cam->v4ldev.debug = 0; // cam->v4ldev.debug = V4L2_DEBUG_IOCTL_ARG; + cam->v4ldev.dev = &pdev->dev; ret = video_register_device(&cam->v4ldev, VFL_TYPE_GRABBER, -1); if (ret) goto out_smbus; @@ -2176,10 +2189,52 @@ static void cafe_pci_remove(struct pci_dev *pdev) } +#ifdef CONFIG_PM +/* + * Basic power management. + */ +static int cafe_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct cafe_camera *cam = cafe_find_by_pdev(pdev); + int ret; + + ret = pci_save_state(pdev); + if (ret) + return ret; + cafe_ctlr_stop_dma(cam); + cafe_ctlr_power_down(cam); + pci_disable_device(pdev); + return 0; +} + + +static int cafe_pci_resume(struct pci_dev *pdev) +{ + struct cafe_camera *cam = cafe_find_by_pdev(pdev); + int ret = 0; + + ret = pci_restore_state(pdev); + if (ret) + return ret; + ret = pci_enable_device(pdev); + if (ret) { + cam_warn(cam, "Unable to re-enable device on resume!\n"); + return ret; + } + cafe_ctlr_init(cam); + cafe_ctlr_power_up(cam); + set_bit(CF_CONFIG_NEEDED, &cam->flags); + if (cam->state == S_SPECREAD) + cam->state = S_IDLE; /* Don't bother restarting */ + else if (cam->state == S_SINGLEREAD || cam->state == S_STREAMING) + ret = cafe_read_setup(cam, cam->state); + return ret; +} + +#endif /* CONFIG_PM */ static struct pci_device_id cafe_ids[] = { - { PCI_DEVICE(0x1148, 0x4340) }, /* Temporary ID on devel board */ { PCI_DEVICE(0x11ab, 0x4100) }, /* Eventual real ID */ { PCI_DEVICE(0x11ab, 0x4102) }, /* Really eventual real ID */ { 0, } @@ -2192,6 +2247,10 @@ static struct pci_driver cafe_pci_driver = { .id_table = cafe_ids, .probe = cafe_pci_probe, .remove = cafe_pci_remove, +#ifdef CONFIG_PM + .suspend = cafe_pci_suspend, + .resume = cafe_pci_resume, +#endif }; diff --git a/drivers/media/video/cpia_pp.c b/drivers/media/video/cpia_pp.c index b12cec94f4c..19711aaf9a3 100644 --- a/drivers/media/video/cpia_pp.c +++ b/drivers/media/video/cpia_pp.c @@ -62,7 +62,6 @@ static int cpia_pp_close(void *privdata); #define PPCPIA_PARPORT_OFF -2 #define PPCPIA_PARPORT_NONE -1 -#ifdef MODULE static int parport_nr[PARPORT_MAX] = {[0 ... PARPORT_MAX - 1] = PPCPIA_PARPORT_UNSPEC}; static char *parport[PARPORT_MAX] = {NULL,}; @@ -72,11 +71,6 @@ MODULE_LICENSE("GPL"); module_param_array(parport, charp, NULL, 0); MODULE_PARM_DESC(parport, "'auto' or a list of parallel port numbers. Just like lp."); -#else -static int parport_nr[PARPORT_MAX] __initdata = - {[0 ... PARPORT_MAX - 1] = PPCPIA_PARPORT_UNSPEC}; -static int parport_ptr = 0; -#endif struct pp_cam_entry { struct pardevice *pdev; @@ -141,7 +135,6 @@ static void cpia_pp_run_callback(struct work_struct *work) cam = container_of(work, struct pp_cam_entry, cb_task); cb_func = cam->cb_func; cb_data = cam->cb_data; - work_release(work); cb_func(cb_data); } @@ -682,7 +675,7 @@ static int cpia_pp_registerCallback(void *privdata, void (*cb)(void *cbdata), vo if(cam->port->irq != PARPORT_IRQ_NONE) { cam->cb_func = cb; cam->cb_data = cbdata; - INIT_WORK_NAR(&cam->cb_task, cpia_pp_run_callback); + INIT_WORK(&cam->cb_task, cpia_pp_run_callback); } else { retval = -1; } @@ -820,7 +813,7 @@ static struct parport_driver cpia_pp_driver = { .detach = cpia_pp_detach, }; -static int cpia_pp_init(void) +static int __init cpia_pp_init(void) { printk(KERN_INFO "%s v%d.%d.%d\n",ABOUT, CPIA_PP_MAJ_VER,CPIA_PP_MIN_VER,CPIA_PP_PATCH_VER); @@ -839,8 +832,7 @@ static int cpia_pp_init(void) return 0; } -#ifdef MODULE -int init_module(void) +static int __init cpia_init(void) { if (parport[0]) { /* The user gave some parameters. Let's see what they were. */ @@ -867,38 +859,11 @@ int init_module(void) return cpia_pp_init(); } -void cleanup_module(void) +static void __exit cpia_cleanup(void) { - parport_unregister_driver (&cpia_pp_driver); + parport_unregister_driver(&cpia_pp_driver); return; } -#else /* !MODULE */ - -static int __init cpia_pp_setup(char *str) -{ - int err; - - if (!strncmp(str, "parport", 7)) { - int n = simple_strtoul(str + 7, NULL, 10); - if (parport_ptr < PARPORT_MAX) { - parport_nr[parport_ptr++] = n; - } else { - LOG("too many ports, %s ignored.\n", str); - } - } else if (!strcmp(str, "auto")) { - parport_nr[0] = PPCPIA_PARPORT_AUTO; - } else if (!strcmp(str, "none")) { - parport_nr[parport_ptr++] = PPCPIA_PARPORT_NONE; - } - - err=cpia_pp_init(); - if (err) - return err; - - return 1; -} - -__setup("cpia_pp=", cpia_pp_setup); - -#endif /* !MODULE */ +module_init(cpia_init); +module_exit(cpia_cleanup); diff --git a/drivers/media/video/cs53l32a.c b/drivers/media/video/cs53l32a.c index de87247c74e..a73e285af73 100644 --- a/drivers/media/video/cs53l32a.c +++ b/drivers/media/video/cs53l32a.c @@ -28,6 +28,7 @@ #include <linux/i2c-id.h> #include <linux/videodev.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> MODULE_DESCRIPTION("i2c device driver for cs53l32a Audio ADC"); MODULE_AUTHOR("Martin Vaughan"); @@ -103,6 +104,9 @@ static int cs53l32a_command(struct i2c_client *client, unsigned int cmd, cs53l32a_write(client, 0x05, (u8) ctrl->value); break; + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_CS53l32A, 0); + case VIDIOC_LOG_STATUS: { u8 v = cs53l32a_read(client, 0x01); diff --git a/drivers/media/video/cx2341x.c b/drivers/media/video/cx2341x.c index d60cd5ecf82..88dbdddeec4 100644 --- a/drivers/media/video/cx2341x.c +++ b/drivers/media/video/cx2341x.c @@ -51,6 +51,7 @@ const u32 cx2341x_mpeg_ctrls[] = { V4L2_CID_MPEG_AUDIO_MODE_EXTENSION, V4L2_CID_MPEG_AUDIO_EMPHASIS, V4L2_CID_MPEG_AUDIO_CRC, + V4L2_CID_MPEG_AUDIO_MUTE, V4L2_CID_MPEG_VIDEO_ENCODING, V4L2_CID_MPEG_VIDEO_ASPECT, V4L2_CID_MPEG_VIDEO_B_FRAMES, @@ -60,6 +61,8 @@ const u32 cx2341x_mpeg_ctrls[] = { V4L2_CID_MPEG_VIDEO_BITRATE, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION, + V4L2_CID_MPEG_VIDEO_MUTE, + V4L2_CID_MPEG_VIDEO_MUTE_YUV, V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE, V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER, V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE, @@ -71,6 +74,7 @@ const u32 cx2341x_mpeg_ctrls[] = { V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP, V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM, V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP, + V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS, 0 }; @@ -102,6 +106,9 @@ static int cx2341x_get_ctrl(struct cx2341x_mpeg_params *params, case V4L2_CID_MPEG_AUDIO_CRC: ctrl->value = params->audio_crc; break; + case V4L2_CID_MPEG_AUDIO_MUTE: + ctrl->value = params->audio_mute; + break; case V4L2_CID_MPEG_VIDEO_ENCODING: ctrl->value = params->video_encoding; break; @@ -129,6 +136,12 @@ static int cx2341x_get_ctrl(struct cx2341x_mpeg_params *params, case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: ctrl->value = params->video_temporal_decimation; break; + case V4L2_CID_MPEG_VIDEO_MUTE: + ctrl->value = params->video_mute; + break; + case V4L2_CID_MPEG_VIDEO_MUTE_YUV: + ctrl->value = params->video_mute_yuv; + break; case V4L2_CID_MPEG_STREAM_TYPE: ctrl->value = params->stream_type; break; @@ -168,6 +181,9 @@ static int cx2341x_get_ctrl(struct cx2341x_mpeg_params *params, case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM: ctrl->value = params->video_chroma_median_filter_bottom; break; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + ctrl->value = params->stream_insert_nav_packets; + break; default: return -EINVAL; } @@ -201,6 +217,9 @@ static int cx2341x_set_ctrl(struct cx2341x_mpeg_params *params, case V4L2_CID_MPEG_AUDIO_CRC: params->audio_crc = ctrl->value; break; + case V4L2_CID_MPEG_AUDIO_MUTE: + params->audio_mute = ctrl->value; + break; case V4L2_CID_MPEG_VIDEO_ASPECT: params->video_aspect = ctrl->value; break; @@ -243,6 +262,12 @@ static int cx2341x_set_ctrl(struct cx2341x_mpeg_params *params, case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: params->video_temporal_decimation = ctrl->value; break; + case V4L2_CID_MPEG_VIDEO_MUTE: + params->video_mute = (ctrl->value != 0); + break; + case V4L2_CID_MPEG_VIDEO_MUTE_YUV: + params->video_mute_yuv = ctrl->value; + break; case V4L2_CID_MPEG_STREAM_TYPE: params->stream_type = ctrl->value; params->video_encoding = @@ -290,6 +315,9 @@ static int cx2341x_set_ctrl(struct cx2341x_mpeg_params *params, case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM: params->video_chroma_median_filter_bottom = ctrl->value; break; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + params->stream_insert_nav_packets = ctrl->value; + break; default: return -EINVAL; } @@ -336,6 +364,9 @@ static int cx2341x_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 ma case V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM: name = "Median Chroma Filter Minimum"; break; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + name = "Insert Navigation Packets"; + break; default: return v4l2_ctrl_query_fill(qctrl, min, max, step, def); @@ -350,6 +381,12 @@ static int cx2341x_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 ma min = 0; step = 1; break; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + qctrl->type = V4L2_CTRL_TYPE_BOOLEAN; + min = 0; + max = 1; + step = 1; + break; default: qctrl->type = V4L2_CTRL_TYPE_INTEGER; break; @@ -505,6 +542,9 @@ int cx2341x_ctrl_query(struct cx2341x_mpeg_params *params, struct v4l2_queryctrl qctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; return 0; + case V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS: + return cx2341x_ctrl_query_fill(qctrl, 0, 1, 1, 0); + default: return v4l2_ctrl_query_fill_std(qctrl); @@ -656,6 +696,7 @@ void cx2341x_fill_defaults(struct cx2341x_mpeg_params *p) /* stream */ .stream_type = V4L2_MPEG_STREAM_TYPE_MPEG2_PS, .stream_vbi_fmt = V4L2_MPEG_STREAM_VBI_FMT_NONE, + .stream_insert_nav_packets = 0, /* audio */ .audio_sampling_freq = V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000, @@ -665,6 +706,7 @@ void cx2341x_fill_defaults(struct cx2341x_mpeg_params *p) .audio_mode_extension = V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4, .audio_emphasis = V4L2_MPEG_AUDIO_EMPHASIS_NONE, .audio_crc = V4L2_MPEG_AUDIO_CRC_NONE, + .audio_mute = 0, /* video */ .video_encoding = V4L2_MPEG_VIDEO_ENCODING_MPEG_2, @@ -676,6 +718,8 @@ void cx2341x_fill_defaults(struct cx2341x_mpeg_params *p) .video_bitrate = 6000000, .video_bitrate_peak = 8000000, .video_temporal_decimation = 0, + .video_mute = 0, + .video_mute_yuv = 0x008080, /* YCbCr value for black */ /* encoding filters */ .video_spatial_filter_mode = V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL, @@ -779,6 +823,10 @@ int cx2341x_update(void *priv, cx2341x_mbox_func func, err = cx2341x_api(priv, func, CX2341X_ENC_SET_AUDIO_PROPERTIES, 1, new->audio_properties); if (err) return err; } + if (old == NULL || old->audio_mute != new->audio_mute) { + err = cx2341x_api(priv, func, CX2341X_ENC_MUTE_AUDIO, 1, new->audio_mute); + if (err) return err; + } if (old == NULL || old->video_bitrate_mode != new->video_bitrate_mode || old->video_bitrate != new->video_bitrate || old->video_bitrate_peak != new->video_bitrate_peak) { @@ -826,6 +874,15 @@ int cx2341x_update(void *priv, cx2341x_mbox_func func, new->video_temporal_decimation); if (err) return err; } + if (old == NULL || old->video_mute != new->video_mute || + (new->video_mute && old->video_mute_yuv != new->video_mute_yuv)) { + err = cx2341x_api(priv, func, CX2341X_ENC_MUTE_VIDEO, 1, new->video_mute | (new->video_mute_yuv << 8)); + if (err) return err; + } + if (old == NULL || old->stream_insert_nav_packets != new->stream_insert_nav_packets) { + err = cx2341x_api(priv, func, CX2341X_ENC_MISC, 2, 7, new->stream_insert_nav_packets); + if (err) return err; + } return 0; } @@ -854,18 +911,22 @@ void cx2341x_log_status(struct cx2341x_mpeg_params *p, const char *prefix) int temporal = p->video_temporal_filter; /* Stream */ - printk(KERN_INFO "%s: Stream: %s\n", + printk(KERN_INFO "%s: Stream: %s", prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_STREAM_TYPE)); + if (p->stream_insert_nav_packets) + printk(" (with navigation packets)"); + printk("\n"); printk(KERN_INFO "%s: VBI Format: %s\n", prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_STREAM_VBI_FMT)); /* Video */ - printk(KERN_INFO "%s: Video: %dx%d, %d fps\n", + printk(KERN_INFO "%s: Video: %dx%d, %d fps%s\n", prefix, p->width / (is_mpeg1 ? 2 : 1), p->height / (is_mpeg1 ? 2 : 1), - p->is_50hz ? 25 : 30); + p->is_50hz ? 25 : 30, + (p->video_mute) ? " (muted)" : ""); printk(KERN_INFO "%s: Video: %s, %s, %s, %d", prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_VIDEO_ENCODING), @@ -886,12 +947,13 @@ void cx2341x_log_status(struct cx2341x_mpeg_params *p, const char *prefix) } /* Audio */ - printk(KERN_INFO "%s: Audio: %s, %s, %s, %s", + printk(KERN_INFO "%s: Audio: %s, %s, %s, %s%s", prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ), cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_ENCODING), cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_L2_BITRATE), - cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_MODE)); + cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_MODE), + p->audio_mute ? " (muted)" : ""); if (p->audio_mode == V4L2_MPEG_AUDIO_MODE_JOINT_STEREO) { printk(", %s", cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_MODE_EXTENSION)); diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c index 774d2536555..1757a588970 100644 --- a/drivers/media/video/cx25840/cx25840-core.c +++ b/drivers/media/video/cx25840/cx25840-core.c @@ -35,6 +35,7 @@ #include <linux/videodev2.h> #include <linux/i2c.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <media/cx25840.h> #include "cx25840-core.h" @@ -827,9 +828,8 @@ static int cx25840_command(struct i2c_client *client, unsigned int cmd, cx25840_initialize(client, 0); break; - case VIDIOC_INT_G_CHIP_IDENT: - *(enum v4l2_chip_ident *)arg = state->id; - break; + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, state->id, state->rev); default: return -EINVAL; @@ -847,7 +847,7 @@ static int cx25840_detect_client(struct i2c_adapter *adapter, int address, { struct i2c_client *client; struct cx25840_state *state; - enum v4l2_chip_ident id; + u32 id; u16 device_id; /* Check if the adapter supports the needed features @@ -902,6 +902,7 @@ static int cx25840_detect_client(struct i2c_adapter *adapter, int address, state->audmode = V4L2_TUNER_MODE_LANG1; state->vbi_line_offset = 8; state->id = id; + state->rev = device_id; i2c_attach_client(client); diff --git a/drivers/media/video/cx25840/cx25840-core.h b/drivers/media/video/cx25840/cx25840-core.h index 28049064dd7..f4b56d2fd6b 100644 --- a/drivers/media/video/cx25840/cx25840-core.h +++ b/drivers/media/video/cx25840/cx25840-core.h @@ -43,7 +43,8 @@ struct cx25840_state { u32 audclk_freq; int audmode; int vbi_line_offset; - enum v4l2_chip_ident id; + u32 id; + u32 rev; int is_cx25836; }; diff --git a/drivers/media/video/cx25840/cx25840-firmware.c b/drivers/media/video/cx25840/cx25840-firmware.c index 0e86b9d033a..e852024a5ea 100644 --- a/drivers/media/video/cx25840/cx25840-firmware.c +++ b/drivers/media/video/cx25840/cx25840-firmware.c @@ -17,7 +17,6 @@ #include <linux/module.h> #include <linux/i2c.h> -#include <linux/i2c-algo-bit.h> #include <linux/firmware.h> #include <media/v4l2-common.h> #include <media/cx25840.h> diff --git a/drivers/media/video/cx88/Kconfig b/drivers/media/video/cx88/Kconfig index b2a66ba625f..0f9d9696361 100644 --- a/drivers/media/video/cx88/Kconfig +++ b/drivers/media/video/cx88/Kconfig @@ -53,7 +53,6 @@ config VIDEO_CX88_DVB select DVB_OR51132 if !DVB_FE_CUSTOMISE select DVB_CX22702 if !DVB_FE_CUSTOMISE select DVB_LGDT330X if !DVB_FE_CUSTOMISE - select DVB_TUNER_LGH06XF if !DVB_FE_CUSTOMISE select DVB_NXT200X if !DVB_FE_CUSTOMISE select DVB_CX24123 if !DVB_FE_CUSTOMISE select DVB_ISL6421 if !DVB_FE_CUSTOMISE diff --git a/drivers/media/video/cx88/cx88-alsa.c b/drivers/media/video/cx88/cx88-alsa.c index e4355fdc3b6..3956c257556 100644 --- a/drivers/media/video/cx88/cx88-alsa.c +++ b/drivers/media/video/cx88/cx88-alsa.c @@ -232,7 +232,8 @@ static void cx8801_aud_irq(snd_cx88_card_t *chip) cx_write(MO_AUD_INTSTAT, status); if (debug > 1 || (status & mask & ~0xff)) cx88_print_irqbits(core->name, "irq aud", - cx88_aud_irqs, status, mask); + cx88_aud_irqs, ARRAY_SIZE(cx88_aud_irqs), + status, mask); /* risc op code error */ if (status & (1 << 16)) { printk(KERN_WARNING "%s/0: audio risc op code error\n",core->name); @@ -413,11 +414,9 @@ static int snd_cx88_hw_params(struct snd_pcm_substream * substream, dprintk(1,"Setting buffer\n"); - buf = kmalloc(sizeof(*buf),GFP_KERNEL); + buf = kzalloc(sizeof(*buf),GFP_KERNEL); if (NULL == buf) return -ENOMEM; - memset(buf,0,sizeof(*buf)); - buf->vb.memory = V4L2_MEMORY_MMAP; buf->vb.width = chip->period_size; @@ -682,7 +681,7 @@ static int __devinit snd_cx88_create(struct snd_card *card, return err; } - if (!pci_dma_supported(pci,0xffffffff)) { + if (!pci_dma_supported(pci,DMA_32BIT_MASK)) { dprintk(0, "%s/1: Oops: no 32bit PCI DMA ???\n",core->name); err = -EIO; cx88_core_put(core,pci); diff --git a/drivers/media/video/cx88/cx88-cards.c b/drivers/media/video/cx88/cx88-cards.c index 65e9d8096b7..e61102dc8ad 100644 --- a/drivers/media/video/cx88/cx88-cards.c +++ b/drivers/media/video/cx88/cx88-cards.c @@ -885,6 +885,12 @@ struct cx88_board cx88_boards[] = { .input = {{ .type = CX88_VMUX_DVB, .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, }}, .mpeg = CX88_MPEG_DVB, }, @@ -1537,10 +1543,10 @@ struct cx88_subid cx88_subids[] = { },{ .subvendor = 0x17de, .subdevice = 0x0840, - .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, - },{ - .subvendor = 0x1421, - .subdevice = 0x0305, + .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, + },{ + .subvendor = 0x1421, + .subdevice = 0x0305, .card = CX88_BOARD_KWORLD_HARDWARE_MPEG_TV_XPERT, },{ .subvendor = 0x18ac, @@ -1631,6 +1637,10 @@ struct cx88_subid cx88_subids[] = { .subvendor = 0x0070, .subdevice = 0x1402, .card = CX88_BOARD_HAUPPAUGE_HVR3000, + },{ + .subvendor = 0x1421, + .subdevice = 0x0341, /* ADS Tech InstantTV DVB-S */ + .card = CX88_BOARD_KWORLD_DVBS_100, }, }; const unsigned int cx88_idcount = ARRAY_SIZE(cx88_subids); @@ -1786,7 +1796,7 @@ static void dvico_fusionhdtv_hybrid_init(struct cx88_core *core) { 0x03, 0x0C }, }; - for (i = 0; i < 13; i++) { + for (i = 0; i < ARRAY_SIZE(init_bufs); i++) { msg.buf = init_bufs[i]; msg.len = (i != 12 ? 5 : 2); err = i2c_transfer(&core->i2c_adap, &msg, 1); @@ -1913,12 +1923,21 @@ void cx88_card_setup(struct cx88_core *core) if (0 == core->i2c_rc) { /* enable tuner */ int i; - static const u8 buffer [] = { 0x10,0x12,0x13,0x04,0x16,0x00,0x14,0x04,0x017,0x00 }; + static const u8 buffer [][2] = { + {0x10,0x12}, + {0x13,0x04}, + {0x16,0x00}, + {0x14,0x04}, + {0x17,0x00} + }; core->i2c_client.addr = 0x0a; - for (i = 0; i < 5; i++) - if (2 != i2c_master_send(&core->i2c_client,&buffer[i*2],2)) - printk(KERN_WARNING "%s: Unable to enable tuner(%i).\n", + for (i = 0; i < ARRAY_SIZE(buffer); i++) + if (2 != i2c_master_send(&core->i2c_client, + buffer[i],2)) + printk(KERN_WARNING + "%s: Unable to enable " + "tuner(%i).\n", core->name, i); } break; diff --git a/drivers/media/video/cx88/cx88-core.c b/drivers/media/video/cx88/cx88-core.c index d86813be56d..f31ec96924b 100644 --- a/drivers/media/video/cx88/cx88-core.c +++ b/drivers/media/video/cx88/cx88-core.c @@ -489,12 +489,12 @@ static char *cx88_pci_irqs[32] = { }; void cx88_print_irqbits(char *name, char *tag, char **strings, - u32 bits, u32 mask) + int len, u32 bits, u32 mask) { unsigned int i; printk(KERN_DEBUG "%s: %s [0x%x]", name, tag, bits); - for (i = 0; i < 32; i++) { + for (i = 0; i < len; i++) { if (!(bits & (1 << i))) continue; if (strings[i]) @@ -520,8 +520,8 @@ int cx88_core_irq(struct cx88_core *core, u32 status) } if (!handled) cx88_print_irqbits(core->name, "irq pci", - cx88_pci_irqs, status, - core->pci_irqmask); + cx88_pci_irqs, ARRAY_SIZE(cx88_pci_irqs), + status, core->pci_irqmask); return handled; } diff --git a/drivers/media/video/cx88/cx88-dvb.c b/drivers/media/video/cx88/cx88-dvb.c index 4f556028577..dbfe4dc9cf8 100644 --- a/drivers/media/video/cx88/cx88-dvb.c +++ b/drivers/media/video/cx88/cx88-dvb.c @@ -42,7 +42,6 @@ #include "cx22702.h" #include "or51132.h" #include "lgdt330x.h" -#include "lgh06xf.h" #include "nxt200x.h" #include "cx24123.h" #include "isl6421.h" @@ -476,6 +475,8 @@ static int dvb_register(struct cx8802_dev *dev) case CX88_BOARD_WINFAST_DTV2000H: case CX88_BOARD_HAUPPAUGE_HVR1100: case CX88_BOARD_HAUPPAUGE_HVR1100LP: + case CX88_BOARD_HAUPPAUGE_HVR1300: + case CX88_BOARD_HAUPPAUGE_HVR3000: dev->dvb.frontend = dvb_attach(cx22702_attach, &hauppauge_hvr_config, &dev->core->i2c_adap); @@ -631,8 +632,9 @@ static int dvb_register(struct cx8802_dev *dev) &fusionhdtv_5_gold, &dev->core->i2c_adap); if (dev->dvb.frontend != NULL) { - dvb_attach(lgh06xf_attach, dev->dvb.frontend, - &dev->core->i2c_adap); + dvb_attach(dvb_pll_attach, dev->dvb.frontend, 0x61, + &dev->core->i2c_adap, + &dvb_pll_lg_tdvs_h06xf); } } break; @@ -650,8 +652,9 @@ static int dvb_register(struct cx8802_dev *dev) &pchdtv_hd5500, &dev->core->i2c_adap); if (dev->dvb.frontend != NULL) { - dvb_attach(lgh06xf_attach, dev->dvb.frontend, - &dev->core->i2c_adap); + dvb_attach(dvb_pll_attach, dev->dvb.frontend, 0x61, + &dev->core->i2c_adap, + &dvb_pll_lg_tdvs_h06xf); } } break; @@ -692,24 +695,6 @@ static int dvb_register(struct cx8802_dev *dev) dev->dvb.frontend->ops.set_voltage = geniatech_dvbs_set_voltage; } break; - case CX88_BOARD_HAUPPAUGE_HVR1300: - dev->dvb.frontend = dvb_attach(cx22702_attach, - &hauppauge_hvr_config, - &dev->core->i2c_adap); - if (dev->dvb.frontend != NULL) { - dvb_attach(dvb_pll_attach, dev->dvb.frontend, 0x61, - &dev->core->i2c_adap, &dvb_pll_fmd1216me); - } - break; - case CX88_BOARD_HAUPPAUGE_HVR3000: - dev->dvb.frontend = dvb_attach(cx22702_attach, - &hauppauge_hvr_config, - &dev->core->i2c_adap); - if (dev->dvb.frontend != NULL) { - dvb_attach(dvb_pll_attach, dev->dvb.frontend, 0x61, - &dev->core->i2c_adap, &dvb_pll_fmd1216me); - } - break; default: printk("%s: The frontend of your DVB/ATSC card isn't supported yet\n", dev->core->name); diff --git a/drivers/media/video/cx88/cx88-i2c.c b/drivers/media/video/cx88/cx88-i2c.c index 9830d5c4392..7919a1f9da0 100644 --- a/drivers/media/video/cx88/cx88-i2c.c +++ b/drivers/media/video/cx88/cx88-i2c.c @@ -1,3 +1,4 @@ + /* cx88-i2c.c -- all the i2c code is here @@ -195,7 +196,7 @@ static void do_i2c_scan(char *name, struct i2c_client *c) unsigned char buf; int i,rc; - for (i = 0; i < 128; i++) { + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { c->addr = i; rc = i2c_master_recv(c,&buf,0); if (rc < 0) diff --git a/drivers/media/video/cx88/cx88-mpeg.c b/drivers/media/video/cx88/cx88-mpeg.c index 1fe1a833c7c..b2eb32e01ae 100644 --- a/drivers/media/video/cx88/cx88-mpeg.c +++ b/drivers/media/video/cx88/cx88-mpeg.c @@ -49,6 +49,27 @@ MODULE_PARM_DESC(debug,"enable debug messages [mpeg]"); #define mpeg_dbg(level,fmt, arg...) if (debug >= level) \ printk(KERN_DEBUG "%s/2-mpeg: " fmt, core->name, ## arg) +#if defined(CONFIG_MODULES) && defined(MODULE) +static void request_module_async(struct work_struct *work) +{ + struct cx8802_dev *dev=container_of(work, struct cx8802_dev, request_module_wk); + + if (cx88_boards[dev->core->board].mpeg & CX88_MPEG_DVB) + request_module("cx88-dvb"); + if (cx88_boards[dev->core->board].mpeg & CX88_MPEG_BLACKBIRD) + request_module("cx88-blackbird"); +} + +static void request_modules(struct cx8802_dev *dev) +{ + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); +} +#else +#define request_modules(dev) +#endif /* CONFIG_MODULES */ + + static LIST_HEAD(cx8802_devlist); /* ------------------------------------------------------------------ */ @@ -345,7 +366,8 @@ static void cx8802_mpeg_irq(struct cx8802_dev *dev) if (debug || (status & mask & ~0xff)) cx88_print_irqbits(core->name, "irq mpeg ", - cx88_mpeg_irqs, status, mask); + cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_irqs), + status, mask); /* risc op code error */ if (status & (1 << 16)) { @@ -427,7 +449,7 @@ int cx8802_init_common(struct cx8802_dev *dev) if (pci_enable_device(dev->pci)) return -EIO; pci_set_master(dev->pci); - if (!pci_dma_supported(dev->pci,0xffffffff)) { + if (!pci_dma_supported(dev->pci,DMA_32BIT_MASK)) { printk("%s/2: Oops: no 32bit PCI DMA ???\n",dev->core->name); return -EIO; } @@ -778,6 +800,9 @@ static int __devinit cx8802_probe(struct pci_dev *pci_dev, /* Maintain a reference so cx88-video can query the 8802 device. */ core->dvbdev = dev; + + /* now autoload cx88-dvb or cx88-blackbird */ + request_modules(dev); return 0; fail_free: diff --git a/drivers/media/video/cx88/cx88-video.c b/drivers/media/video/cx88/cx88-video.c index bdfe2af7012..fbce1d50578 100644 --- a/drivers/media/video/cx88/cx88-video.c +++ b/drivers/media/video/cx88/cx88-video.c @@ -1555,7 +1555,8 @@ static void cx8800_vid_irq(struct cx8800_dev *dev) cx_write(MO_VID_INTSTAT, status); if (irq_debug || (status & mask & ~0xff)) cx88_print_irqbits(core->name, "irq vid", - cx88_vid_irqs, status, mask); + cx88_vid_irqs, ARRAY_SIZE(cx88_vid_irqs), + status, mask); /* risc op code error */ if (status & (1 << 16)) { @@ -1778,7 +1779,7 @@ static int __devinit cx8800_initdev(struct pci_dev *pci_dev, dev->pci_lat,(unsigned long long)pci_resource_start(pci_dev,0)); pci_set_master(pci_dev); - if (!pci_dma_supported(pci_dev,0xffffffff)) { + if (!pci_dma_supported(pci_dev,DMA_32BIT_MASK)) { printk("%s/0: Oops: no 32bit PCI DMA ???\n",core->name); err = -EIO; goto fail_core; diff --git a/drivers/media/video/cx88/cx88.h b/drivers/media/video/cx88/cx88.h index a4f7befda5b..738d4f20c58 100644 --- a/drivers/media/video/cx88/cx88.h +++ b/drivers/media/video/cx88/cx88.h @@ -481,6 +481,8 @@ struct cx8802_dev { /* List of attached drivers */ struct cx8802_driver drvlist; + struct work_struct request_module_wk; + }; /* ----------------------------------------------------------- */ @@ -510,7 +512,7 @@ struct cx8802_dev { /* cx88-core.c */ extern void cx88_print_irqbits(char *name, char *tag, char **strings, - u32 bits, u32 mask); + int len, u32 bits, u32 mask); extern int cx88_core_irq(struct cx88_core *core, u32 status); extern void cx88_wakeup(struct cx88_core *core, diff --git a/drivers/media/video/em28xx/em28xx-i2c.c b/drivers/media/video/em28xx/em28xx-i2c.c index d829d8f8c1f..563a8319e60 100644 --- a/drivers/media/video/em28xx/em28xx-i2c.c +++ b/drivers/media/video/em28xx/em28xx-i2c.c @@ -523,7 +523,7 @@ static void do_i2c_scan(char *name, struct i2c_client *c) unsigned char buf; int i, rc; - for (i = 0; i < 128; i++) { + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { c->addr = i; rc = i2c_master_recv(c, &buf, 0); if (rc < 0) diff --git a/drivers/media/video/ir-kbd-i2c.c b/drivers/media/video/ir-kbd-i2c.c index 210582d420f..ed92b6f7187 100644 --- a/drivers/media/video/ir-kbd-i2c.c +++ b/drivers/media/video/ir-kbd-i2c.c @@ -173,7 +173,7 @@ static int get_key_pinnacle(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw, return -EIO; } - for (start = 0; start<4; start++) { + for (start = 0; start < ARRAY_SIZE(b); start++) { if (b[start] == marker) { code=b[(start+parity_offset+1)%4]; parity=b[(start+parity_offset)%4]; diff --git a/drivers/media/video/ivtv/Kconfig b/drivers/media/video/ivtv/Kconfig new file mode 100644 index 00000000000..e854f3f1b70 --- /dev/null +++ b/drivers/media/video/ivtv/Kconfig @@ -0,0 +1,26 @@ +config VIDEO_IVTV + tristate "Conexant cx23416/cx23415 MPEG encoder/decoder support" + depends on VIDEO_V4L1 && VIDEO_V4L2 && USB && I2C && EXPERIMENTAL + select FW_LOADER + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_CX25840 + select VIDEO_MSP3400 + select VIDEO_SAA711X + select VIDEO_SAA7127 + select VIDEO_TVAUDIO + select VIDEO_CS53L32A + select VIDEO_WM8775 + select VIDEO_WM8739 + select VIDEO_UPD64031A + select VIDEO_UPD64083 + ---help--- + This is a video4linux driver for Conexant cx23416 or cx23416 based + PCI personal video recorder devices. + + This is used in devices such as the Hauppauge PVR-150/250/350/500 + cards. + + To compile this driver as a module, choose M here: the + module will be called ivtv. diff --git a/drivers/media/video/ivtv/Makefile b/drivers/media/video/ivtv/Makefile new file mode 100644 index 00000000000..7e95148fbf4 --- /dev/null +++ b/drivers/media/video/ivtv/Makefile @@ -0,0 +1,7 @@ +ivtv-objs := ivtv-audio.o ivtv-cards.o ivtv-controls.o \ + ivtv-driver.o ivtv-fileops.o ivtv-firmware.o \ + ivtv-gpio.o ivtv-i2c.o ivtv-ioctl.o ivtv-irq.o \ + ivtv-mailbox.o ivtv-queue.o ivtv-streams.o ivtv-udma.o \ + ivtv-vbi.o ivtv-video.o ivtv-yuv.o + +obj-$(CONFIG_VIDEO_IVTV) += ivtv.o diff --git a/drivers/media/video/ivtv/ivtv-audio.c b/drivers/media/video/ivtv/ivtv-audio.c new file mode 100644 index 00000000000..d702b8b539a --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-audio.c @@ -0,0 +1,74 @@ +/* + Audio-related ivtv functions. + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-mailbox.h" +#include "ivtv-i2c.h" +#include "ivtv-gpio.h" +#include "ivtv-cards.h" +#include "ivtv-audio.h" +#include <media/msp3400.h> +#include <linux/videodev.h> + +/* Selects the audio input and output according to the current + settings. */ +int ivtv_audio_set_io(struct ivtv *itv) +{ + struct v4l2_routing route; + u32 audio_input; + int mux_input; + + /* Determine which input to use */ + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) { + audio_input = itv->card->radio_input.audio_input; + mux_input = itv->card->radio_input.muxer_input; + } else { + audio_input = itv->card->audio_inputs[itv->audio_input].audio_input; + mux_input = itv->card->audio_inputs[itv->audio_input].muxer_input; + } + + /* handle muxer chips */ + route.input = mux_input; + route.output = 0; + ivtv_i2c_hw(itv, itv->card->hw_muxer, VIDIOC_INT_S_AUDIO_ROUTING, &route); + + route.input = audio_input; + if (itv->card->hw_audio & IVTV_HW_MSP34XX) { + route.output = MSP_OUTPUT(MSP_SC_IN_DSP_SCART1); + } + return ivtv_i2c_hw(itv, itv->card->hw_audio, VIDIOC_INT_S_AUDIO_ROUTING, &route); +} + +void ivtv_audio_set_route(struct ivtv *itv, struct v4l2_routing *route) +{ + ivtv_i2c_hw(itv, itv->card->hw_audio, VIDIOC_INT_S_AUDIO_ROUTING, route); +} + +void ivtv_audio_set_audio_clock_freq(struct ivtv *itv, u8 freq) +{ + static u32 freqs[3] = { 44100, 48000, 32000 }; + + /* The audio clock of the digitizer must match the codec sample + rate otherwise you get some very strange effects. */ + if (freq > 2) + return; + ivtv_call_i2c_clients(itv, VIDIOC_INT_AUDIO_CLOCK_FREQ, &freqs[freq]); +} + diff --git a/drivers/media/video/ivtv/ivtv-audio.h b/drivers/media/video/ivtv/ivtv-audio.h new file mode 100644 index 00000000000..9c42846d812 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-audio.h @@ -0,0 +1,23 @@ +/* + Audio-related ivtv functions. + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +int ivtv_audio_set_io(struct ivtv *itv); +void ivtv_audio_set_route(struct ivtv *itv, struct v4l2_routing *route); +void ivtv_audio_set_audio_clock_freq(struct ivtv *itv, u8 freq); diff --git a/drivers/media/video/ivtv/ivtv-cards.c b/drivers/media/video/ivtv/ivtv-cards.c new file mode 100644 index 00000000000..8eab0208388 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-cards.c @@ -0,0 +1,964 @@ +/* + Functions to query card hardware + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-i2c.h" + +#include <media/msp3400.h> +#include <media/wm8775.h> +#include <media/cs53l32a.h> +#include <media/cx25840.h> +#include <media/upd64031a.h> + +#define MSP_TUNER MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, \ + MSP_DSP_IN_TUNER, MSP_DSP_IN_TUNER) +#define MSP_SCART1 MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) +#define MSP_SCART2 MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) +#define MSP_SCART3 MSP_INPUT(MSP_IN_SCART3, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) +#define MSP_MONO MSP_INPUT(MSP_IN_MONO, MSP_IN_TUNER1, \ + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART) + +/********************** card configuration *******************************/ + +/* Please add new PCI IDs to: http://pci-ids.ucw.cz/iii + This keeps the PCI ID database up to date. Note that the entries + must be added under vendor 0x4444 (Conexant) as subsystem IDs. + New vendor IDs should still be added to the vendor ID list. */ + +/* Hauppauge PVR-250 cards */ + +/* Note: for Hauppauge cards the tveeprom information is used instead of PCI IDs */ +static const struct ivtv_card ivtv_card_pvr250 = { + .type = IVTV_CARD_PVR_250, + .name = "Hauppauge WinTV PVR-250", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7115 | + IVTV_HW_TVEEPROM | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE3, 1, IVTV_SAA71XX_COMPOSITE5 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, MSP_SCART1 }, + { IVTV_CARD_INPUT_LINE_IN2, MSP_SCART3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, MSP_SCART2 }, +}; + +/* ------------------------------------------------------------------------- */ + +/* Hauppauge PVR-350 cards */ + +/* Outputs for Hauppauge PVR350 cards */ +static struct ivtv_card_output ivtv_pvr350_outputs[] = { + { + .name = "S-Video + Composite", + .video_output = 0, + }, { + .name = "Composite", + .video_output = 1, + }, { + .name = "S-Video", + .video_output = 2, + }, { + .name = "RGB", + .video_output = 3, + }, { + .name = "YUV C", + .video_output = 4, + }, { + .name = "YUV V", + .video_output = 5, + } +}; + +static const struct ivtv_card ivtv_card_pvr350 = { + .type = IVTV_CARD_PVR_350, + .name = "Hauppauge WinTV PVR-350", + .v4l2_capabilities = IVTV_CAP_ENCODER | IVTV_CAP_DECODER, + .video_outputs = ivtv_pvr350_outputs, + .nof_outputs = ARRAY_SIZE(ivtv_pvr350_outputs), + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7115 | + IVTV_HW_SAA7127 | IVTV_HW_TVEEPROM | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE3, 1, IVTV_SAA71XX_COMPOSITE5 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, MSP_SCART1 }, + { IVTV_CARD_INPUT_LINE_IN2, MSP_SCART3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, MSP_SCART2 }, +}; + +/* PVR-350 V1 boards have a different audio tuner input and use a + saa7114 instead of a saa7115. + Note that the info below comes from a pre-production model so it may + not be correct. Especially the audio behaves strangely (mono only it seems) */ +static const struct ivtv_card ivtv_card_pvr350_v1 = { + .type = IVTV_CARD_PVR_350_V1, + .name = "Hauppauge WinTV PVR-350 (V1)", + .v4l2_capabilities = IVTV_CAP_ENCODER | IVTV_CAP_DECODER, + .video_outputs = ivtv_pvr350_outputs, + .nof_outputs = ARRAY_SIZE(ivtv_pvr350_outputs), + .hw_video = IVTV_HW_SAA7114, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7114 | + IVTV_HW_SAA7127 | IVTV_HW_TVEEPROM | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, IVTV_SAA71XX_COMPOSITE1 }, + { IVTV_CARD_INPUT_COMPOSITE3, 1, IVTV_SAA71XX_COMPOSITE5 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_MONO }, + { IVTV_CARD_INPUT_LINE_IN1, MSP_SCART1 }, + { IVTV_CARD_INPUT_LINE_IN2, MSP_SCART3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, MSP_SCART2 }, +}; + +/* ------------------------------------------------------------------------- */ + +/* Hauppauge PVR-150/PVR-500 cards */ + +static const struct ivtv_card ivtv_card_pvr150 = { + .type = IVTV_CARD_PVR_150, + .name = "Hauppauge WinTV PVR-150", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_WM8775, + .hw_all = IVTV_HW_WM8775 | IVTV_HW_CX25840 | + IVTV_HW_TVEEPROM | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE7 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, CX25840_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE3 }, + { IVTV_CARD_INPUT_SVIDEO2, 2, CX25840_SVIDEO2 }, + { IVTV_CARD_INPUT_COMPOSITE2, 2, CX25840_COMPOSITE4 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, + CX25840_AUDIO8, WM8775_AIN2 }, + { IVTV_CARD_INPUT_LINE_IN1, + CX25840_AUDIO_SERIAL, WM8775_AIN2 }, + { IVTV_CARD_INPUT_LINE_IN2, + CX25840_AUDIO_SERIAL, WM8775_AIN3 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, + CX25840_AUDIO_SERIAL, WM8775_AIN4 }, + /* apparently needed for the IR blaster */ + .gpio_init = { .direction = 0x1f01, .initial_value = 0x26f3 }, +}; + +/* ------------------------------------------------------------------------- */ + +/* AVerMedia M179 cards */ + +static const struct ivtv_card_pci_info ivtv_pci_m179[] = { + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_AVERMEDIA, 0xa3cf }, + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_AVERMEDIA, 0xa3ce }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_m179 = { + .type = IVTV_CARD_M179, + .name = "AVerMedia M179", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7114, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7114 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xe380, .initial_value = 0x8290 }, + .gpio_audio_input = { .mask = 0x8040, .tuner = 0x8000, .linein = 0x0000 }, + .gpio_audio_mute = { .mask = 0x2000, .mute = 0x2000 }, + .gpio_audio_mode = { .mask = 0x4300, .mono = 0x4000, .stereo = 0x0200, + .lang1 = 0x0200, .lang2 = 0x0100, .both = 0x0000 }, + .gpio_audio_freq = { .mask = 0x0018, .f32000 = 0x0000, + .f44100 = 0x0008, .f48000 = 0x0010 }, + .gpio_audio_detect = { .mask = 0x4000, .stereo = 0x0000 }, + .tuners = { + /* As far as we know all M179 cards use this tuner */ + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_NTSC }, + }, + .pci_list = ivtv_pci_m179, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPG600/Kuroutoshikou ITVC16-STVLP cards */ + +static const struct ivtv_card_pci_info ivtv_pci_mpg600[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0xfff3 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0xffff }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_mpg600 = { + .type = IVTV_CARD_MPG600, + .name = "Yuan MPG600, Kuroutoshikou ITVC16-STVLP", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0x3080, .initial_value = 0x0004 }, + .gpio_audio_input = { .mask = 0x3000, .tuner = 0x0000, .linein = 0x2000 }, + .gpio_audio_mute = { .mask = 0x0001, .mute = 0x0001 }, + .gpio_audio_mode = { .mask = 0x000e, .mono = 0x0006, .stereo = 0x0004, + .lang1 = 0x0004, .lang2 = 0x0000, .both = 0x0008 }, + .gpio_audio_detect = { .mask = 0x0900, .stereo = 0x0100 }, + .tuners = { + /* The PAL tuner is confirmed */ + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FQ1216ME }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_mpg600, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPG160/Kuroutoshikou ITVC15-STVLP cards */ + +static const struct ivtv_card_pci_info ivtv_pci_mpg160[] = { + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_YUAN1, 0 }, + { PCI_DEVICE_ID_IVTV15, IVTV_PCI_ID_IODATA, 0x40a0 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_mpg160 = { + .type = IVTV_CARD_MPG160, + .name = "YUAN MPG160, Kuroutoshikou ITVC15-STVLP, I/O Data GV-M2TV/PCI", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7114, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7114 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0x7080, .initial_value = 0x400c }, + .gpio_audio_input = { .mask = 0x3000, .tuner = 0x0000, .linein = 0x2000 }, + .gpio_audio_mute = { .mask = 0x0001, .mute = 0x0001 }, + .gpio_audio_mode = { .mask = 0x000e, .mono = 0x0006, .stereo = 0x0004, + .lang1 = 0x0004, .lang2 = 0x0000, .both = 0x0008 }, + .gpio_audio_detect = { .mask = 0x0900, .stereo = 0x0100 }, + .tuners = { + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FQ1216ME }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_mpg160, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan PG600/Diamond PVR-550 cards */ + +static const struct ivtv_card_pci_info ivtv_pci_pg600[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_DIAMONDMM, 0x0070 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN3, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_pg600 = { + .type = IVTV_CARD_PG600, + .name = "Yuan PG600, Diamond PVR-550", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .tuners = { + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FQ1216ME }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_pg600, +}; + +/* ------------------------------------------------------------------------- */ + +/* Adaptec VideOh! AVC-2410 card */ + +static const struct ivtv_card_pci_info ivtv_pci_avc2410[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ADAPTEC, 0x0093 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_avc2410 = { + .type = IVTV_CARD_AVC2410, + .name = "Adaptec VideOh! AVC-2410", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_muxer = IVTV_HW_CS53L32A, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_CS53L32A | + IVTV_HW_SAA7115 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, + MSP_TUNER, CS53L32A_IN0 }, + { IVTV_CARD_INPUT_LINE_IN1, + MSP_SCART1, CS53L32A_IN2 }, + }, + /* This card has no eeprom and in fact the Windows driver relies + on the country/region setting of the user to decide which tuner + is available. */ + .tuners = { + /* This tuner has been verified for the AVC2410 */ + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + /* This is a good guess, but I'm not totally sure this is + the correct tuner for NTSC. */ + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, + .pci_list = ivtv_pci_avc2410, +}; + +/* ------------------------------------------------------------------------- */ + +/* Adaptec VideOh! AVC-2010 card */ + +static const struct ivtv_card_pci_info ivtv_pci_avc2010[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_ADAPTEC, 0x0092 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_avc2010 = { + .type = IVTV_CARD_AVC2010, + .name = "Adaptec VideOh! AVC-2010", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_CS53L32A, + .hw_audio_ctrl = IVTV_HW_CS53L32A, + .hw_all = IVTV_HW_CS53L32A | IVTV_HW_SAA7115, + .video_inputs = { + { IVTV_CARD_INPUT_SVIDEO1, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 0, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_LINE_IN1, CS53L32A_IN2 }, + }, + /* Does not have a tuner */ + .pci_list = ivtv_pci_avc2010, +}; + +/* ------------------------------------------------------------------------- */ + +/* Nagase Transgear 5000TV card */ + +static const struct ivtv_card_pci_info ivtv_pci_tg5000tv[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xbfff }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_tg5000tv = { + .type = IVTV_CARD_TG5000TV, + .name = "Nagase Transgear 5000TV", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7114 | IVTV_HW_UPD64031A | IVTV_HW_UPD6408X | + IVTV_HW_GPIO, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7114 | IVTV_HW_TUNER | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO2 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gr_config = UPD64031A_VERTICAL_EXTERNAL, + .gpio_init = { .direction = 0xe080, .initial_value = 0x8000 }, + .gpio_audio_input = { .mask = 0x8080, .tuner = 0x8000, .linein = 0x0080 }, + .gpio_audio_mute = { .mask = 0x6000, .mute = 0x6000 }, + .gpio_audio_mode = { .mask = 0x4300, .mono = 0x4000, .stereo = 0x0200, + .lang1 = 0x0300, .lang2 = 0x0000, .both = 0x0200 }, + .gpio_video_input = { .mask = 0x0030, .tuner = 0x0000, + .composite = 0x0010, .svideo = 0x0020 }, + .tuners = { + { .std = V4L2_STD_525_60, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_tg5000tv, +}; + +/* ------------------------------------------------------------------------- */ + +/* AOpen VA2000MAX-SNT6 card */ + +static const struct ivtv_card_pci_info ivtv_pci_va2000[] = { + { PCI_DEVICE_ID_IVTV16, 0, 0xff5f }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_va2000 = { + .type = IVTV_CARD_VA2000MAX_SNT6, + .name = "AOpen VA2000MAX-SNT6", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115 | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_MSP34XX, + .hw_audio_ctrl = IVTV_HW_MSP34XX, + .hw_all = IVTV_HW_MSP34XX | IVTV_HW_SAA7115 | + IVTV_HW_UPD6408X | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, MSP_TUNER }, + }, + .tuners = { + { .std = V4L2_STD_525_60, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_va2000, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPG600GR/Kuroutoshikou CX23416GYC-STVLP cards */ + +static const struct ivtv_card_pci_info ivtv_pci_cx23416gyc[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0x0600 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN4, 0x0600 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_MELCO, 0x0523 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_cx23416gyc = { + .type = IVTV_CARD_CX23416GYC, + .name = "Yuan MPG600GR, Kuroutoshikou CX23416GYC-STVLP", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X | IVTV_HW_GPIO | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA717X | IVTV_HW_TUNER | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO3 | + IVTV_SAA717X_TUNER_FLAG }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN2 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN0 }, + }, + .gr_config = UPD64031A_VERTICAL_EXTERNAL, + .gpio_init = { .direction = 0xf880, .initial_value = 0x8800 }, + .gpio_video_input = { .mask = 0x0020, .tuner = 0x0000, + .composite = 0x0020, .svideo = 0x0020 }, + .gpio_audio_freq = { .mask = 0xc000, .f32000 = 0x0000, + .f44100 = 0x4000, .f48000 = 0x8000 }, + .tuners = { + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, + .pci_list = ivtv_pci_cx23416gyc, +}; + +static const struct ivtv_card ivtv_card_cx23416gyc_nogr = { + .type = IVTV_CARD_CX23416GYC_NOGR, + .name = "Yuan MPG600GR, Kuroutoshikou CX23416GYC-STVLP (no GR)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X | IVTV_HW_GPIO | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA717X | IVTV_HW_TUNER | + IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 | + IVTV_SAA717X_TUNER_FLAG }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN2 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN0 }, + }, + .gpio_init = { .direction = 0xf880, .initial_value = 0x8800 }, + .gpio_video_input = { .mask = 0x0020, .tuner = 0x0000, + .composite = 0x0020, .svideo = 0x0020 }, + .gpio_audio_freq = { .mask = 0xc000, .f32000 = 0x0000, + .f44100 = 0x4000, .f48000 = 0x8000 }, + .tuners = { + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, +}; + +static const struct ivtv_card ivtv_card_cx23416gyc_nogrycs = { + .type = IVTV_CARD_CX23416GYC_NOGRYCS, + .name = "Yuan MPG600GR, Kuroutoshikou CX23416GYC-STVLP (no GR/YCS)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X | IVTV_HW_GPIO, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA717X | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 | + IVTV_SAA717X_TUNER_FLAG }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE0 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN2 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN0 }, + }, + .gpio_init = { .direction = 0xf880, .initial_value = 0x8800 }, + .gpio_video_input = { .mask = 0x0020, .tuner = 0x0000, + .composite = 0x0020, .svideo = 0x0020 }, + .gpio_audio_freq = { .mask = 0xc000, .f32000 = 0x0000, + .f44100 = 0x4000, .f48000 = 0x8000 }, + .tuners = { + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_FM1236_MK3 }, + }, +}; + +/* ------------------------------------------------------------------------- */ + +/* I/O Data GV-MVP/RX & GV-MVP/RX2W (dual tuner) cards */ + +static const struct ivtv_card_pci_info ivtv_pci_gv_mvprx[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd01e }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd038 }, /* 2W unit #1 */ + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd039 }, /* 2W unit #2 */ + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_gv_mvprx = { + .type = IVTV_CARD_GV_MVPRX, + .name = "I/O Data GV-MVP/RX, GV-MVP/RX2W (dual tuner)", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115 | IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_WM8739, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TVAUDIO | + IVTV_HW_TUNER | IVTV_HW_WM8739 | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO1 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xc301, .initial_value = 0x0200 }, + .gpio_audio_input = { .mask = 0xffff, .tuner = 0x0200, .linein = 0x0300 }, + .tuners = { + /* This card has the Panasonic VP27 tuner */ + { .std = V4L2_STD_525_60, .tuner = TUNER_PANASONIC_VP27 }, + }, + .pci_list = ivtv_pci_gv_mvprx, +}; + +/* ------------------------------------------------------------------------- */ + +/* I/O Data GV-MVP/RX2E card */ + +static const struct ivtv_card_pci_info ivtv_pci_gv_mvprx2e[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_IODATA, 0xd025 }, + {0, 0, 0} +}; + +static const struct ivtv_card ivtv_card_gv_mvprx2e = { + .type = IVTV_CARD_GV_MVPRX2E, + .name = "I/O Data GV-MVP/RX2E", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_WM8739, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER | + IVTV_HW_TVAUDIO | IVTV_HW_WM8739, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE4 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xc301, .initial_value = 0x0200 }, + .gpio_audio_input = { .mask = 0xffff, .tuner = 0x0200, .linein = 0x0300 }, + .tuners = { + /* This card has the Panasonic VP27 tuner */ + { .std = V4L2_STD_525_60, .tuner = TUNER_PANASONIC_VP27 }, + }, + .pci_list = ivtv_pci_gv_mvprx2e, +}; + +/* ------------------------------------------------------------------------- */ + +/* GotVIEW PCI DVD card */ + +static const struct ivtv_card_pci_info ivtv_pci_gotview_pci_dvd[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN1, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_gotview_pci_dvd = { + .type = IVTV_CARD_GOTVIEW_PCI_DVD, + .name = "GotView PCI DVD", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA717X, + .hw_audio = IVTV_HW_SAA717X, + .hw_audio_ctrl = IVTV_HW_SAA717X, + .hw_all = IVTV_HW_SAA717X | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_COMPOSITE1 }, /* pin 116 */ + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO0 }, /* pin 114/109 */ + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_COMPOSITE3 }, /* pin 118 */ + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_SAA717X_IN0 }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_SAA717X_IN2 }, + }, + .gpio_init = { .direction = 0xf000, .initial_value = 0xA000 }, + .tuners = { + /* This card has a Philips FQ1216ME MK3 tuner */ + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + }, + .pci_list = ivtv_pci_gotview_pci_dvd, +}; + +/* ------------------------------------------------------------------------- */ + +/* GotVIEW PCI DVD2 Deluxe card */ + +static const struct ivtv_card_pci_info ivtv_pci_gotview_pci_dvd2[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_GOTVIEW1, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_gotview_pci_dvd2 = { + .type = IVTV_CARD_GOTVIEW_PCI_DVD2, + .name = "GotView PCI DVD2 Deluxe", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_muxer = IVTV_HW_GPIO, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5, 0 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL, 1 }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO_SERIAL, 2 }, + .gpio_init = { .direction = 0x0800, .initial_value = 0 }, + .gpio_audio_input = { .mask = 0x0800, .tuner = 0, .linein = 0, .radio = 0x0800 }, + .tuners = { + /* This card has a Philips FQ1216ME MK5 tuner */ + { .std = V4L2_STD_625_50, .tuner = TUNER_PHILIPS_FM1216ME_MK3 }, + }, + .pci_list = ivtv_pci_gotview_pci_dvd2, +}; + +/* ------------------------------------------------------------------------- */ + +/* Yuan MPC622 miniPCI card */ + +static const struct ivtv_card_pci_info ivtv_pci_yuan_mpc622[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN2, 0xd998 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_yuan_mpc622 = { + .type = IVTV_CARD_YUAN_MPC622, + .name = "Yuan MPC622", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, CX25840_COMPOSITE1 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .gpio_init = { .direction = 0x00ff, .initial_value = 0x0002 }, + .tuners = { + /* This card has the TDA8290/TDA8275 tuner chips */ + { .std = V4L2_STD_ALL, .tuner = TUNER_PHILIPS_TDA8290 }, + }, + .pci_list = ivtv_pci_yuan_mpc622, +}; + +/* ------------------------------------------------------------------------- */ + +/* DIGITAL COWBOY DCT-MTVP1 card */ + +static const struct ivtv_card_pci_info ivtv_pci_dctmvtvp1[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_AVERMEDIA, 0xbfff }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_dctmvtvp1 = { + .type = IVTV_CARD_DCTMTVP1, + .name = "Digital Cowboy DCT-MTVP1", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_SAA7115 | IVTV_HW_UPD64031A | IVTV_HW_UPD6408X | + IVTV_HW_GPIO, + .hw_audio = IVTV_HW_GPIO, + .hw_audio_ctrl = IVTV_HW_GPIO, + .hw_all = IVTV_HW_GPIO | IVTV_HW_SAA7115 | IVTV_HW_TUNER | + IVTV_HW_UPD64031A | IVTV_HW_UPD6408X, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, IVTV_SAA71XX_SVIDEO0 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, IVTV_SAA71XX_SVIDEO2 }, + { IVTV_CARD_INPUT_COMPOSITE1, 1, IVTV_SAA71XX_SVIDEO2 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, IVTV_GPIO_TUNER }, + { IVTV_CARD_INPUT_LINE_IN1, IVTV_GPIO_LINE_IN }, + }, + .gpio_init = { .direction = 0xe080, .initial_value = 0x8000 }, + .gpio_audio_input = { .mask = 0x8080, .tuner = 0x8000, .linein = 0x0080 }, + .gpio_audio_mute = { .mask = 0x6000, .mute = 0x6000 }, + .gpio_audio_mode = { .mask = 0x4300, .mono = 0x4000, .stereo = 0x0200, + .lang1 = 0x0300, .lang2 = 0x0000, .both = 0x0200 }, + .gpio_video_input = { .mask = 0x0030, .tuner = 0x0000, + .composite = 0x0010, .svideo = 0x0020}, + .tuners = { + { .std = V4L2_STD_525_60, .tuner = TUNER_PHILIPS_FQ1286 }, + }, + .pci_list = ivtv_pci_dctmvtvp1, +}; + +/* ------------------------------------------------------------------------- */ + +#ifdef HAVE_XC3028 + +/* Yuan PG600-2/GotView PCI DVD Lite/Club3D ZAP-TV1x01 cards */ + +static const struct ivtv_card_pci_info ivtv_pci_pg600v2[] = { + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_YUAN3, 0x0600 }, + { PCI_DEVICE_ID_IVTV16, IVTV_PCI_ID_GOTVIEW2, 0x0600 }, + { 0, 0, 0 } +}; + +static const struct ivtv_card ivtv_card_pg600v2 = { + .type = IVTV_CARD_PG600V2, + .name = "Yuan PG600-2, GotView PCI DVD Lite, Club3D ZAP-TV1x01", + .v4l2_capabilities = IVTV_CAP_ENCODER, + .hw_video = IVTV_HW_CX25840, + .hw_audio = IVTV_HW_CX25840, + .hw_audio_ctrl = IVTV_HW_CX25840, + .hw_all = IVTV_HW_CX25840 | IVTV_HW_TUNER, + .video_inputs = { + { IVTV_CARD_INPUT_VID_TUNER, 0, CX25840_COMPOSITE2 }, + { IVTV_CARD_INPUT_SVIDEO1, 1, + CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4 }, + }, + .audio_inputs = { + { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + { IVTV_CARD_INPUT_LINE_IN1, CX25840_AUDIO_SERIAL }, + }, + .radio_input = { IVTV_CARD_INPUT_AUD_TUNER, CX25840_AUDIO5 }, + .tuners = { + { .std = V4L2_STD_ALL, .tuner = TUNER_XCEIVE_XC3028 }, + }, + .gpio_init = { .direction = 0x1000, .initial_value = 0x1000 }, /* tuner reset */ + .pci_list = ivtv_pci_pg600v2, +}; +#endif + +static const struct ivtv_card *ivtv_card_list[] = { + &ivtv_card_pvr250, + &ivtv_card_pvr350, + &ivtv_card_pvr150, + &ivtv_card_m179, + &ivtv_card_mpg600, + &ivtv_card_mpg160, + &ivtv_card_pg600, + &ivtv_card_avc2410, + &ivtv_card_avc2010, + &ivtv_card_tg5000tv, + &ivtv_card_va2000, + &ivtv_card_cx23416gyc, + &ivtv_card_gv_mvprx, + &ivtv_card_gv_mvprx2e, + &ivtv_card_gotview_pci_dvd, + &ivtv_card_gotview_pci_dvd2, + &ivtv_card_yuan_mpc622, + &ivtv_card_dctmvtvp1, +#ifdef HAVE_XC3028 + &ivtv_card_pg600v2, +#endif + + /* Variations of standard cards but with the same PCI IDs. + These cards must come last in this list. */ + &ivtv_card_pvr350_v1, + &ivtv_card_cx23416gyc_nogr, + &ivtv_card_cx23416gyc_nogrycs, +}; + +const struct ivtv_card *ivtv_get_card(u16 index) +{ + if (index >= ARRAY_SIZE(ivtv_card_list)) + return NULL; + return ivtv_card_list[index]; +} + +int ivtv_get_input(struct ivtv *itv, u16 index, struct v4l2_input *input) +{ + const struct ivtv_card_video_input *card_input = itv->card->video_inputs + index; + static const char * const input_strs[] = { + "Tuner 1", + "S-Video 1", + "S-Video 2", + "Composite 1", + "Composite 2", + "Composite 3" + }; + + memset(input, 0, sizeof(*input)); + if (index >= itv->nof_inputs) + return -EINVAL; + input->index = index; + strcpy(input->name, input_strs[card_input->video_type - 1]); + input->type = (card_input->video_type == IVTV_CARD_INPUT_VID_TUNER ? + V4L2_INPUT_TYPE_TUNER : V4L2_INPUT_TYPE_CAMERA); + input->audioset = (1 << itv->nof_audio_inputs) - 1; + input->std = (input->type == V4L2_INPUT_TYPE_TUNER) ? + itv->tuner_std : V4L2_STD_ALL; + return 0; +} + +int ivtv_get_output(struct ivtv *itv, u16 index, struct v4l2_output *output) +{ + const struct ivtv_card_output *card_output = itv->card->video_outputs + index; + + memset(output, 0, sizeof(*output)); + if (index >= itv->card->nof_outputs) + return -EINVAL; + output->index = index; + strcpy(output->name, card_output->name); + output->type = V4L2_OUTPUT_TYPE_ANALOG; + output->audioset = 1; + output->std = V4L2_STD_ALL; + return 0; +} + +int ivtv_get_audio_input(struct ivtv *itv, u16 index, struct v4l2_audio *audio) +{ + const struct ivtv_card_audio_input *aud_input = itv->card->audio_inputs + index; + static const char * const input_strs[] = { + "Tuner 1", + "Line In 1", + "Line In 2" + }; + + memset(audio, 0, sizeof(*audio)); + if (index >= itv->nof_audio_inputs) + return -EINVAL; + strcpy(audio->name, input_strs[aud_input->audio_type - 1]); + audio->index = index; + audio->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +int ivtv_get_audio_output(struct ivtv *itv, u16 index, struct v4l2_audioout *aud_output) +{ + memset(aud_output, 0, sizeof(*aud_output)); + if (itv->card->video_outputs == NULL || index != 0) + return -EINVAL; + strcpy(aud_output->name, "A/V Audio Out"); + return 0; +} diff --git a/drivers/media/video/ivtv/ivtv-cards.h b/drivers/media/video/ivtv/ivtv-cards.h new file mode 100644 index 00000000000..15012f88b80 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-cards.h @@ -0,0 +1,207 @@ +/* + Functions to query card hardware + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* hardware flags */ +#define IVTV_HW_CX25840 (1 << 0) +#define IVTV_HW_SAA7115 (1 << 1) +#define IVTV_HW_SAA7127 (1 << 2) +#define IVTV_HW_MSP34XX (1 << 3) +#define IVTV_HW_TUNER (1 << 4) +#define IVTV_HW_WM8775 (1 << 5) +#define IVTV_HW_CS53L32A (1 << 6) +#define IVTV_HW_TVEEPROM (1 << 7) +#define IVTV_HW_SAA7114 (1 << 8) +#define IVTV_HW_TVAUDIO (1 << 9) +#define IVTV_HW_UPD64031A (1 << 10) +#define IVTV_HW_UPD6408X (1 << 11) +#define IVTV_HW_SAA717X (1 << 12) +#define IVTV_HW_WM8739 (1 << 13) +#define IVTV_HW_GPIO (1 << 14) + +#define IVTV_HW_SAA711X (IVTV_HW_SAA7115 | IVTV_HW_SAA7114) + +/* video inputs */ +#define IVTV_CARD_INPUT_VID_TUNER 1 +#define IVTV_CARD_INPUT_SVIDEO1 2 +#define IVTV_CARD_INPUT_SVIDEO2 3 +#define IVTV_CARD_INPUT_COMPOSITE1 4 +#define IVTV_CARD_INPUT_COMPOSITE2 5 +#define IVTV_CARD_INPUT_COMPOSITE3 6 + +/* audio inputs */ +#define IVTV_CARD_INPUT_AUD_TUNER 1 +#define IVTV_CARD_INPUT_LINE_IN1 2 +#define IVTV_CARD_INPUT_LINE_IN2 3 + +#define IVTV_CARD_MAX_VIDEO_INPUTS 6 +#define IVTV_CARD_MAX_AUDIO_INPUTS 3 +#define IVTV_CARD_MAX_TUNERS 2 + +/* SAA71XX HW inputs */ +#define IVTV_SAA71XX_COMPOSITE0 0 +#define IVTV_SAA71XX_COMPOSITE1 1 +#define IVTV_SAA71XX_COMPOSITE2 2 +#define IVTV_SAA71XX_COMPOSITE3 3 +#define IVTV_SAA71XX_COMPOSITE4 4 +#define IVTV_SAA71XX_COMPOSITE5 5 +#define IVTV_SAA71XX_SVIDEO0 6 +#define IVTV_SAA71XX_SVIDEO1 7 +#define IVTV_SAA71XX_SVIDEO2 8 +#define IVTV_SAA71XX_SVIDEO3 9 + +/* SAA717X needs to mark the tuner input by ORing with this flag */ +#define IVTV_SAA717X_TUNER_FLAG 0x80 + +/* Dummy HW input */ +#define IVTV_DUMMY_AUDIO 0 + +/* GPIO HW inputs */ +#define IVTV_GPIO_TUNER 0 +#define IVTV_GPIO_LINE_IN 1 + +/* SAA717X HW inputs */ +#define IVTV_SAA717X_IN0 0 +#define IVTV_SAA717X_IN1 1 +#define IVTV_SAA717X_IN2 2 + +/* V4L2 capability aliases */ +#define IVTV_CAP_ENCODER (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | \ + V4L2_CAP_AUDIO | V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE | \ + V4L2_CAP_SLICED_VBI_CAPTURE) +#define IVTV_CAP_DECODER (V4L2_CAP_VBI_OUTPUT | V4L2_CAP_VIDEO_OUTPUT | \ + V4L2_CAP_SLICED_VBI_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_OVERLAY | V4L2_CAP_VIDEO_OUTPUT_POS) + +struct ivtv_card_video_input { + u8 video_type; /* video input type */ + u8 audio_index; /* index in ivtv_card_audio_input array */ + u16 video_input; /* hardware video input */ +}; + +struct ivtv_card_audio_input { + u8 audio_type; /* audio input type */ + u32 audio_input; /* hardware audio input */ + u16 muxer_input; /* hardware muxer input for boards with a + multiplexer chip */ +}; + +struct ivtv_card_output { + u8 name[32]; + u16 video_output; /* hardware video output */ +}; + +struct ivtv_card_pci_info { + u16 device; + u16 subsystem_vendor; + u16 subsystem_device; +}; + +/* GPIO definitions */ + +/* The mask is the set of bits used by the operation */ + +struct ivtv_gpio_init { /* set initial GPIO DIR and OUT values */ + u16 direction; /* DIR setting. Leave to 0 if no init is needed */ + u16 initial_value; +}; + +struct ivtv_gpio_video_input { /* select tuner/line in input */ + u16 mask; /* leave to 0 if not supported */ + u16 tuner; + u16 composite; + u16 svideo; +}; + +struct ivtv_gpio_audio_input { /* select tuner/line in input */ + u16 mask; /* leave to 0 if not supported */ + u16 tuner; + u16 linein; + u16 radio; +}; + +struct ivtv_gpio_audio_mute { + u16 mask; /* leave to 0 if not supported */ + u16 mute; /* set this value to mute, 0 to unmute */ +}; + +struct ivtv_gpio_audio_mode { + u16 mask; /* leave to 0 if not supported */ + u16 mono; /* set audio to mono */ + u16 stereo; /* set audio to stereo */ + u16 lang1; /* set audio to the first language */ + u16 lang2; /* set audio to the second language */ + u16 both; /* both languages are output */ +}; + +struct ivtv_gpio_audio_freq { + u16 mask; /* leave to 0 if not supported */ + u16 f32000; + u16 f44100; + u16 f48000; +}; + +struct ivtv_gpio_audio_detect { + u16 mask; /* leave to 0 if not supported */ + u16 stereo; /* if the input matches this value then + stereo is detected */ +}; + +struct ivtv_card_tuner { + v4l2_std_id std; /* standard for which the tuner is suitable */ + int tuner; /* tuner ID (from tuner.h) */ +}; + +/* for card information/parameters */ +struct ivtv_card { + int type; + char *name; + u32 v4l2_capabilities; + u32 hw_video; /* hardware used to process video */ + u32 hw_audio; /* hardware used to process audio */ + u32 hw_audio_ctrl; /* hardware used for the V4L2 controls (only 1 dev allowed) */ + u32 hw_muxer; /* hardware used to multiplex audio input */ + u32 hw_all; /* all hardware used by the board */ + struct ivtv_card_video_input video_inputs[IVTV_CARD_MAX_VIDEO_INPUTS]; + struct ivtv_card_audio_input audio_inputs[IVTV_CARD_MAX_AUDIO_INPUTS]; + struct ivtv_card_audio_input radio_input; + int nof_outputs; + const struct ivtv_card_output *video_outputs; + u8 gr_config; /* config byte for the ghost reduction device */ + + /* GPIO card-specific settings */ + struct ivtv_gpio_init gpio_init; + struct ivtv_gpio_video_input gpio_video_input; + struct ivtv_gpio_audio_input gpio_audio_input; + struct ivtv_gpio_audio_mute gpio_audio_mute; + struct ivtv_gpio_audio_mode gpio_audio_mode; + struct ivtv_gpio_audio_freq gpio_audio_freq; + struct ivtv_gpio_audio_detect gpio_audio_detect; + + struct ivtv_card_tuner tuners[IVTV_CARD_MAX_TUNERS]; + + /* list of device and subsystem vendor/devices that + correspond to this card type. */ + const struct ivtv_card_pci_info *pci_list; +}; + +int ivtv_get_input(struct ivtv *itv, u16 index, struct v4l2_input *input); +int ivtv_get_output(struct ivtv *itv, u16 index, struct v4l2_output *output); +int ivtv_get_audio_input(struct ivtv *itv, u16 index, struct v4l2_audio *input); +int ivtv_get_audio_output(struct ivtv *itv, u16 index, struct v4l2_audioout *output); +const struct ivtv_card *ivtv_get_card(u16 index); diff --git a/drivers/media/video/ivtv/ivtv-controls.c b/drivers/media/video/ivtv/ivtv-controls.c new file mode 100644 index 00000000000..7a876c3e5b1 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-controls.c @@ -0,0 +1,303 @@ +/* + ioctl control functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-ioctl.h" +#include "ivtv-audio.h" +#include "ivtv-i2c.h" +#include "ivtv-mailbox.h" +#include "ivtv-controls.h" + +static const u32 user_ctrls[] = { + V4L2_CID_USER_CLASS, + V4L2_CID_BRIGHTNESS, + V4L2_CID_CONTRAST, + V4L2_CID_SATURATION, + V4L2_CID_HUE, + V4L2_CID_AUDIO_VOLUME, + V4L2_CID_AUDIO_BALANCE, + V4L2_CID_AUDIO_BASS, + V4L2_CID_AUDIO_TREBLE, + V4L2_CID_AUDIO_MUTE, + V4L2_CID_AUDIO_LOUDNESS, + 0 +}; + +static const u32 *ctrl_classes[] = { + user_ctrls, + cx2341x_mpeg_ctrls, + NULL +}; + +static int ivtv_queryctrl(struct ivtv *itv, struct v4l2_queryctrl *qctrl) +{ + const char *name; + + IVTV_DEBUG_IOCTL("VIDIOC_QUERYCTRL(%08x)\n", qctrl->id); + + qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id); + if (qctrl->id == 0) + return -EINVAL; + + switch (qctrl->id) { + /* Standard V4L2 controls */ + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_HUE: + case V4L2_CID_SATURATION: + case V4L2_CID_CONTRAST: + if (itv->video_dec_func(itv, VIDIOC_QUERYCTRL, qctrl)) + qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; + return 0; + + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_LOUDNESS: + if (ivtv_i2c_hw(itv, itv->card->hw_audio_ctrl, VIDIOC_QUERYCTRL, qctrl)) + qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; + return 0; + + default: + if (cx2341x_ctrl_query(&itv->params, qctrl)) + qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; + return 0; + } + strncpy(qctrl->name, name, sizeof(qctrl->name) - 1); + qctrl->name[sizeof(qctrl->name) - 1] = 0; + return 0; +} + +static int ivtv_querymenu(struct ivtv *itv, struct v4l2_querymenu *qmenu) +{ + struct v4l2_queryctrl qctrl; + + qctrl.id = qmenu->id; + ivtv_queryctrl(itv, &qctrl); + return v4l2_ctrl_query_menu(qmenu, &qctrl, cx2341x_ctrl_get_menu(qmenu->id)); +} + +static int ivtv_s_ctrl(struct ivtv *itv, struct v4l2_control *vctrl) +{ + s32 v = vctrl->value; + + IVTV_DEBUG_IOCTL("VIDIOC_S_CTRL(%08x, %x)\n", vctrl->id, v); + + switch (vctrl->id) { + /* Standard V4L2 controls */ + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_HUE: + case V4L2_CID_SATURATION: + case V4L2_CID_CONTRAST: + return itv->video_dec_func(itv, VIDIOC_S_CTRL, vctrl); + + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_LOUDNESS: + return ivtv_i2c_hw(itv, itv->card->hw_audio_ctrl, VIDIOC_S_CTRL, vctrl); + + default: + IVTV_DEBUG_IOCTL("invalid control %x\n", vctrl->id); + return -EINVAL; + } + return 0; +} + +static int ivtv_g_ctrl(struct ivtv *itv, struct v4l2_control *vctrl) +{ + IVTV_DEBUG_IOCTL("VIDIOC_G_CTRL(%08x)\n", vctrl->id); + + switch (vctrl->id) { + /* Standard V4L2 controls */ + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_HUE: + case V4L2_CID_SATURATION: + case V4L2_CID_CONTRAST: + return itv->video_dec_func(itv, VIDIOC_G_CTRL, vctrl); + + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_MUTE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_LOUDNESS: + return ivtv_i2c_hw(itv, itv->card->hw_audio_ctrl, VIDIOC_G_CTRL, vctrl); + default: + IVTV_DEBUG_IOCTL("invalid control %x\n", vctrl->id); + return -EINVAL; + } + return 0; +} + +static int ivtv_setup_vbi_fmt(struct ivtv *itv, enum v4l2_mpeg_stream_vbi_fmt fmt) +{ + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_CAPTURE)) + return -EINVAL; + if (atomic_read(&itv->capturing) > 0) + return -EBUSY; + + /* First try to allocate sliced VBI buffers if needed. */ + if (fmt && itv->vbi.sliced_mpeg_data[0] == NULL) { + int i; + + for (i = 0; i < IVTV_VBI_FRAMES; i++) { + /* Yuck, hardcoded. Needs to be a define */ + itv->vbi.sliced_mpeg_data[i] = kmalloc(2049, GFP_KERNEL); + if (itv->vbi.sliced_mpeg_data[i] == NULL) { + while (--i >= 0) { + kfree(itv->vbi.sliced_mpeg_data[i]); + itv->vbi.sliced_mpeg_data[i] = NULL; + } + return -ENOMEM; + } + } + } + + itv->vbi.insert_mpeg = fmt; + + if (itv->vbi.insert_mpeg == 0) { + return 0; + } + /* Need sliced data for mpeg insertion */ + if (get_service_set(itv->vbi.sliced_in) == 0) { + if (itv->is_60hz) + itv->vbi.sliced_in->service_set = V4L2_SLICED_CAPTION_525; + else + itv->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625; + expand_service_set(itv->vbi.sliced_in, itv->is_50hz); + } + return 0; +} + +int ivtv_control_ioctls(struct ivtv *itv, unsigned int cmd, void *arg) +{ + struct v4l2_control ctrl; + + switch (cmd) { + case VIDIOC_QUERYMENU: + IVTV_DEBUG_IOCTL("VIDIOC_QUERYMENU\n"); + return ivtv_querymenu(itv, arg); + + case VIDIOC_QUERYCTRL: + return ivtv_queryctrl(itv, arg); + + case VIDIOC_S_CTRL: + return ivtv_s_ctrl(itv, arg); + + case VIDIOC_G_CTRL: + return ivtv_g_ctrl(itv, arg); + + case VIDIOC_S_EXT_CTRLS: + { + struct v4l2_ext_controls *c = arg; + + if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { + int i; + int err = 0; + + for (i = 0; i < c->count; i++) { + ctrl.id = c->controls[i].id; + ctrl.value = c->controls[i].value; + err = ivtv_s_ctrl(itv, &ctrl); + c->controls[i].value = ctrl.value; + if (err) { + c->error_idx = i; + break; + } + } + return err; + } + IVTV_DEBUG_IOCTL("VIDIOC_S_EXT_CTRLS\n"); + if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) { + struct cx2341x_mpeg_params p = itv->params; + int err = cx2341x_ext_ctrls(&p, arg, cmd); + + if (err) + return err; + + if (p.video_encoding != itv->params.video_encoding) { + int is_mpeg1 = p.video_encoding == + V4L2_MPEG_VIDEO_ENCODING_MPEG_1; + struct v4l2_format fmt; + + /* fix videodecoder resolution */ + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = itv->params.width / (is_mpeg1 ? 2 : 1); + fmt.fmt.pix.height = itv->params.height; + itv->video_dec_func(itv, VIDIOC_S_FMT, &fmt); + } + err = cx2341x_update(itv, ivtv_api_func, &itv->params, &p); + if (!err && itv->params.stream_vbi_fmt != p.stream_vbi_fmt) { + err = ivtv_setup_vbi_fmt(itv, p.stream_vbi_fmt); + } + itv->params = p; + itv->dualwatch_stereo_mode = p.audio_properties & 0x0300; + ivtv_audio_set_audio_clock_freq(itv, p.audio_properties & 0x03); + return err; + } + return -EINVAL; + } + + case VIDIOC_G_EXT_CTRLS: + { + struct v4l2_ext_controls *c = arg; + + if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { + int i; + int err = 0; + + for (i = 0; i < c->count; i++) { + ctrl.id = c->controls[i].id; + ctrl.value = c->controls[i].value; + err = ivtv_g_ctrl(itv, &ctrl); + c->controls[i].value = ctrl.value; + if (err) { + c->error_idx = i; + break; + } + } + return err; + } + IVTV_DEBUG_IOCTL("VIDIOC_G_EXT_CTRLS\n"); + if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) + return cx2341x_ext_ctrls(&itv->params, arg, cmd); + return -EINVAL; + } + + case VIDIOC_TRY_EXT_CTRLS: + { + struct v4l2_ext_controls *c = arg; + + IVTV_DEBUG_IOCTL("VIDIOC_TRY_EXT_CTRLS\n"); + if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) + return cx2341x_ext_ctrls(&itv->params, arg, cmd); + return -EINVAL; + } + + default: + return -EINVAL; + } + return 0; +} diff --git a/drivers/media/video/ivtv/ivtv-controls.h b/drivers/media/video/ivtv/ivtv-controls.h new file mode 100644 index 00000000000..5a11149725a --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-controls.h @@ -0,0 +1,21 @@ +/* + ioctl control functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +int ivtv_control_ioctls(struct ivtv *itv, unsigned int cmd, void *arg); diff --git a/drivers/media/video/ivtv/ivtv-driver.c b/drivers/media/video/ivtv/ivtv-driver.c new file mode 100644 index 00000000000..45b9328a538 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-driver.c @@ -0,0 +1,1374 @@ +/* + ivtv driver initialization and card probing + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Main Driver file for the ivtv project: + * Driver for the Conexant CX23415/CX23416 chip. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + * License: GPL + * http://www.ivtvdriver.org + * + * ----- + * MPG600/MPG160 support by T.Adachi <tadachi@tadachi-net.com> + * and Takeru KOMORIYA<komoriya@paken.org> + * + * AVerMedia M179 GPIO info by Chris Pinkham <cpinkham@bc2va.org> + * using information provided by Jiun-Kuei Jung @ AVerMedia. + * + * Kurouto Sikou CX23416GYC-STVLP tested by K.Ohta <alpha292@bremen.or.jp> + * using information from T.Adachi,Takeru KOMORIYA and others :-) + * + * Nagase TRANSGEAR 5000TV, Aopen VA2000MAX-STN6 and I/O data GV-MVP/RX + * version by T.Adachi. Special thanks Mr.Suzuki + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-firmware.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-mailbox.h" +#include "ivtv-streams.h" +#include "ivtv-ioctl.h" +#include "ivtv-cards.h" +#include "ivtv-vbi.h" +#include "ivtv-audio.h" +#include "ivtv-gpio.h" +#include "ivtv-yuv.h" + +#include <linux/vermagic.h> +#include <media/tveeprom.h> +#include <media/v4l2-chip-ident.h> + +/* var to keep track of the number of array elements in use */ +int ivtv_cards_active = 0; + +/* If you have already X v4l cards, then set this to X. This way + the device numbers stay matched. Example: you have a WinTV card + without radio and a PVR-350 with. Normally this would give a + video1 device together with a radio0 device for the PVR. By + setting this to 1 you ensure that radio0 is now also radio1. */ +int ivtv_first_minor = 0; + +/* Master variable for all ivtv info */ +struct ivtv *ivtv_cards[IVTV_MAX_CARDS]; + +/* Protects ivtv_cards_active */ +spinlock_t ivtv_cards_lock = SPIN_LOCK_UNLOCKED; + +/* add your revision and whatnot here */ +static struct pci_device_id ivtv_pci_tbl[] __devinitdata = { + {PCI_VENDOR_ID_ICOMP, PCI_DEVICE_ID_IVTV15, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_ICOMP, PCI_DEVICE_ID_IVTV16, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci,ivtv_pci_tbl); + +const u32 yuv_offset[4] = { + IVTV_YUV_BUFFER_OFFSET, + IVTV_YUV_BUFFER_OFFSET_1, + IVTV_YUV_BUFFER_OFFSET_2, + IVTV_YUV_BUFFER_OFFSET_3 +}; + +/* Parameter declarations */ +static int cardtype[IVTV_MAX_CARDS]; +static int tuner[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; +static int radio[IVTV_MAX_CARDS] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + +static int cardtype_c = 1; +static int tuner_c = 1; +static int radio_c = 1; +static char pal[] = "--"; +static char secam[] = "--"; +static char ntsc[] = "-"; + +/* Buffers */ +static int enc_mpg_buffers = IVTV_DEFAULT_ENC_MPG_BUFFERS; +static int enc_yuv_buffers = IVTV_DEFAULT_ENC_YUV_BUFFERS; +static int enc_vbi_buffers = IVTV_DEFAULT_ENC_VBI_BUFFERS; +static int enc_pcm_buffers = IVTV_DEFAULT_ENC_PCM_BUFFERS; +static int dec_mpg_buffers = IVTV_DEFAULT_DEC_MPG_BUFFERS; +static int dec_yuv_buffers = IVTV_DEFAULT_DEC_YUV_BUFFERS; +static int dec_vbi_buffers = IVTV_DEFAULT_DEC_VBI_BUFFERS; + +static int ivtv_yuv_mode = 0; +static int ivtv_yuv_threshold=-1; +static int ivtv_pci_latency = 1; + +int ivtv_debug = 0; + +static int newi2c = -1; + +module_param_array(tuner, int, &tuner_c, 0644); +module_param_array(radio, bool, &radio_c, 0644); +module_param_array(cardtype, int, &cardtype_c, 0644); +module_param_string(pal, pal, sizeof(pal), 0644); +module_param_string(secam, secam, sizeof(secam), 0644); +module_param_string(ntsc, ntsc, sizeof(ntsc), 0644); +module_param_named(debug,ivtv_debug, int, 0644); +module_param(ivtv_pci_latency, int, 0644); +module_param(ivtv_yuv_mode, int, 0644); +module_param(ivtv_yuv_threshold, int, 0644); +module_param(ivtv_first_minor, int, 0644); + +module_param(enc_mpg_buffers, int, 0644); +module_param(enc_yuv_buffers, int, 0644); +module_param(enc_vbi_buffers, int, 0644); +module_param(enc_pcm_buffers, int, 0644); +module_param(dec_mpg_buffers, int, 0644); +module_param(dec_yuv_buffers, int, 0644); +module_param(dec_vbi_buffers, int, 0644); + +module_param(newi2c, int, 0644); + +MODULE_PARM_DESC(tuner, "Tuner type selection,\n" + "\t\t\tsee tuner.h for values"); +MODULE_PARM_DESC(radio, + "Enable or disable the radio. Use only if autodetection\n" + "\t\t\tfails. 0 = disable, 1 = enable"); +MODULE_PARM_DESC(cardtype, + "Only use this option if your card is not detected properly.\n" + "\t\tSpecify card type:\n" + "\t\t\t 1 = WinTV PVR 250\n" + "\t\t\t 2 = WinTV PVR 350\n" + "\t\t\t 3 = WinTV PVR-150 or PVR-500\n" + "\t\t\t 4 = AVerMedia M179\n" + "\t\t\t 5 = YUAN MPG600/Kuroutoshikou iTVC16-STVLP\n" + "\t\t\t 6 = YUAN MPG160/Kuroutoshikou iTVC15-STVLP\n" + "\t\t\t 7 = YUAN PG600/DIAMONDMM PVR-550 (CX Falcon 2)\n" + "\t\t\t 8 = Adaptec AVC-2410\n" + "\t\t\t 9 = Adaptec AVC-2010\n" + "\t\t\t10 = NAGASE TRANSGEAR 5000TV\n" + "\t\t\t11 = AOpen VA2000MAX-STN6\n" + "\t\t\t12 = YUAN MPG600GR/Kuroutoshikou CX23416GYC-STVLP\n" + "\t\t\t13 = I/O Data GV-MVP/RX\n" + "\t\t\t14 = I/O Data GV-MVP/RX2E\n" + "\t\t\t15 = GOTVIEW PCI DVD\n" + "\t\t\t16 = GOTVIEW PCI DVD2 Deluxe\n" + "\t\t\t17 = Yuan MPC622\n" + "\t\t\t18 = Digital Cowboy DCT-MTVP1\n" +#ifdef HAVE_XC3028 + "\t\t\t19 = Yuan PG600V2/GotView PCI DVD Lite/Club3D ZAP-TV1x01\n" +#endif + "\t\t\t 0 = Autodetect (default)\n" + "\t\t\t-1 = Ignore this card\n\t\t"); +MODULE_PARM_DESC(pal, "Set PAL standard: B, G, H, D, K, I, M, N, Nc, 60"); +MODULE_PARM_DESC(secam, "Set SECAM standard: B, G, H, D, K, L, LC"); +MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J, K"); +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: errors only\n" + "\t\t\t(debug = 511 gives full debugging)"); +MODULE_PARM_DESC(ivtv_pci_latency, + "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n" + "\t\t\tDefault: Yes"); +MODULE_PARM_DESC(ivtv_yuv_mode, + "Specify the yuv playback mode:\n" + "\t\t\t0 = interlaced\n\t\t\t1 = progressive\n\t\t\t2 = auto\n" + "\t\t\tDefault: 0 (interlaced)"); +MODULE_PARM_DESC(ivtv_yuv_threshold, + "If ivtv_yuv_mode is 2 (auto) then playback content as\n\t\tprogressive if src height <= ivtv_yuvthreshold\n" + "\t\t\tDefault: 480");; +MODULE_PARM_DESC(enc_mpg_buffers, + "Encoder MPG Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_MPG_BUFFERS)); +MODULE_PARM_DESC(enc_yuv_buffers, + "Encoder YUV Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_YUV_BUFFERS)); +MODULE_PARM_DESC(enc_vbi_buffers, + "Encoder VBI Buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_VBI_BUFFERS)); +MODULE_PARM_DESC(enc_pcm_buffers, + "Encoder PCM buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_ENC_PCM_BUFFERS)); +MODULE_PARM_DESC(dec_mpg_buffers, + "Decoder MPG buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_MPG_BUFFERS)); +MODULE_PARM_DESC(dec_yuv_buffers, + "Decoder YUV buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_YUV_BUFFERS)); +MODULE_PARM_DESC(dec_vbi_buffers, + "Decoder VBI buffers (in MB)\n" + "\t\t\tDefault: " __stringify(IVTV_DEFAULT_DEC_VBI_BUFFERS)); +MODULE_PARM_DESC(newi2c, + "Use new I2C implementation\n" + "\t\t\t-1 is autodetect, 0 is off, 1 is on\n" + "\t\t\tDefault is autodetect"); + +MODULE_PARM_DESC(ivtv_first_minor, "Set minor assigned to first card"); + +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_DESCRIPTION("CX23415/CX23416 driver"); +MODULE_SUPPORTED_DEVICE + ("CX23415/CX23416 MPEG2 encoder (WinTV PVR-150/250/350/500,\n" + "\t\t\tYuan MPG series and similar)"); +MODULE_LICENSE("GPL"); + +MODULE_VERSION(IVTV_VERSION); + +void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask) +{ + itv->irqmask &= ~mask; + write_reg_sync(itv->irqmask, IVTV_REG_IRQMASK); +} + +void ivtv_set_irq_mask(struct ivtv *itv, u32 mask) +{ + itv->irqmask |= mask; + write_reg_sync(itv->irqmask, IVTV_REG_IRQMASK); +} + +int ivtv_set_output_mode(struct ivtv *itv, int mode) +{ + int old_mode; + + spin_lock(&itv->lock); + old_mode = itv->output_mode; + if (old_mode == 0) + itv->output_mode = old_mode = mode; + spin_unlock(&itv->lock); + return old_mode; +} + +struct ivtv_stream *ivtv_get_output_stream(struct ivtv *itv) +{ + switch (itv->output_mode) { + case OUT_MPG: + return &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + case OUT_YUV: + return &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + default: + return NULL; + } +} + +int ivtv_waitq(wait_queue_head_t *waitq) +{ + DEFINE_WAIT(wait); + + prepare_to_wait(waitq, &wait, TASK_INTERRUPTIBLE); + schedule(); + finish_wait(waitq, &wait); + return signal_pending(current) ? -EINTR : 0; +} + +/* Generic utility functions */ +int ivtv_sleep_timeout(int timeout, int intr) +{ + int ret; + + do { + set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); + timeout = schedule_timeout(timeout); + if (intr && (ret = signal_pending(current))) + return ret; + } while (timeout); + return 0; +} + +/* Release ioremapped memory */ +static void ivtv_iounmap(struct ivtv *itv) +{ + if (itv == NULL) + return; + + /* Release registers memory */ + if (itv->reg_mem != NULL) { + IVTV_DEBUG_INFO("releasing reg_mem\n"); + iounmap(itv->reg_mem); + itv->reg_mem = NULL; + } + /* Release io memory */ + if (itv->has_cx23415 && itv->dec_mem != NULL) { + IVTV_DEBUG_INFO("releasing dec_mem\n"); + iounmap(itv->dec_mem); + } + itv->dec_mem = NULL; + + /* Release io memory */ + if (itv->enc_mem != NULL) { + IVTV_DEBUG_INFO("releasing enc_mem\n"); + iounmap(itv->enc_mem); + itv->enc_mem = NULL; + } +} + +/* Hauppauge card? get values from tveeprom */ +void ivtv_read_eeprom(struct ivtv *itv, struct tveeprom *tv) +{ + u8 eedata[256]; + + itv->i2c_client.addr = 0xA0 >> 1; + tveeprom_read(&itv->i2c_client, eedata, sizeof(eedata)); + tveeprom_hauppauge_analog(&itv->i2c_client, tv, eedata); +} + +static void ivtv_process_eeprom(struct ivtv *itv) +{ + struct tveeprom tv; + int pci_slot = PCI_SLOT(itv->dev->devfn); + + ivtv_read_eeprom(itv, &tv); + + /* Many thanks to Steven Toth from Hauppauge for providing the + model numbers */ + switch (tv.model) { + /* In a few cases the PCI subsystem IDs do not correctly + identify the card. A better method is to check the + model number from the eeprom instead. */ + case 32000 ... 32999: + case 48000 ... 48099: /* 48??? range are PVR250s with a cx23415 */ + case 48400 ... 48599: + itv->card = ivtv_get_card(IVTV_CARD_PVR_250); + break; + case 48100 ... 48399: + case 48600 ... 48999: + itv->card = ivtv_get_card(IVTV_CARD_PVR_350); + break; + case 23000 ... 23999: /* PVR500 */ + case 25000 ... 25999: /* Low profile PVR150 */ + case 26000 ... 26999: /* Regular PVR150 */ + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + break; + case 0: + IVTV_ERR("Invalid EEPROM\n"); + return; + default: + IVTV_ERR("Unknown model %d, defaulting to PVR-150\n", tv.model); + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + break; + } + + switch (tv.model) { + /* Old style PVR350 (with an saa7114) uses this input for + the tuner. */ + case 48254: + itv->card = ivtv_get_card(IVTV_CARD_PVR_350_V1); + break; + default: + break; + } + + itv->v4l2_cap = itv->card->v4l2_capabilities; + itv->card_name = itv->card->name; + + /* If this is a PVR500 then it should be possible to detect whether it is the + first or second unit by looking at the subsystem device ID: is bit 4 is + set, then it is the second unit (according to info from Hauppauge). + + However, while this works for most cards, I have seen a few PVR500 cards + where both units have the same subsystem ID. + + So instead I look at the reported 'PCI slot' (which is the slot on the PVR500 + PCI bridge) and if it is 8, then it is assumed to be the first unit, otherwise + it is the second unit. It is possible that it is a different slot when ivtv is + used in Xen, in that case I ignore this card here. The worst that can happen + is that the card presents itself with a non-working radio device. + + This detection is needed since the eeprom reports incorrectly that a radio is + present on the second unit. */ + if (tv.model / 1000 == 23) { + itv->card_name = "WinTV PVR 500"; + if (pci_slot == 8 || pci_slot == 9) { + int is_first = (pci_slot & 1) == 0; + + itv->card_name = is_first ? "WinTV PVR 500 (unit #1)" : + "WinTV PVR 500 (unit #2)"; + if (!is_first) { + IVTV_INFO("Correcting tveeprom data: no radio present on second unit\n"); + tv.has_radio = 0; + } + } + } + IVTV_INFO("Autodetected %s\n", itv->card_name); + + switch (tv.tuner_hauppauge_model) { + case 85: + case 99: + case 112: + itv->pvr150_workaround = 1; + break; + default: + break; + } + if (tv.tuner_type == TUNER_ABSENT) + IVTV_ERR("tveeprom cannot autodetect tuner!"); + + if (itv->options.tuner == -1) + itv->options.tuner = tv.tuner_type; + if (itv->options.radio == -1) + itv->options.radio = (tv.has_radio != 0); + /* only enable newi2c if an IR blaster is present */ + /* FIXME: for 2.6.20 the test against 2 should be removed */ + if (itv->options.newi2c == -1 && tv.has_ir != -1 && tv.has_ir != 2) { + itv->options.newi2c = (tv.has_ir & 2) ? 1 : 0; + if (itv->options.newi2c) { + IVTV_INFO("reopen i2c bus for IR-blaster support\n"); + exit_ivtv_i2c(itv); + init_ivtv_i2c(itv); + } + } + + if (itv->std != 0) + /* user specified tuner standard */ + return; + + /* autodetect tuner standard */ + if (tv.tuner_formats & V4L2_STD_PAL) { + IVTV_DEBUG_INFO("PAL tuner detected\n"); + itv->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H; + } else if (tv.tuner_formats & V4L2_STD_NTSC) { + IVTV_DEBUG_INFO("NTSC tuner detected\n"); + itv->std |= V4L2_STD_NTSC_M; + } else if (tv.tuner_formats & V4L2_STD_SECAM) { + IVTV_DEBUG_INFO("SECAM tuner detected\n"); + itv->std |= V4L2_STD_SECAM_L; + } else { + IVTV_INFO("No tuner detected, default to NTSC-M\n"); + itv->std |= V4L2_STD_NTSC_M; + } +} + +static v4l2_std_id ivtv_parse_std(struct ivtv *itv) +{ + switch (pal[0]) { + case '6': + return V4L2_STD_PAL_60; + case 'b': + case 'B': + case 'g': + case 'G': + return V4L2_STD_PAL_BG; + case 'h': + case 'H': + return V4L2_STD_PAL_H; + case 'n': + case 'N': + if (pal[1] == 'c' || pal[1] == 'C') + return V4L2_STD_PAL_Nc; + return V4L2_STD_PAL_N; + case 'i': + case 'I': + return V4L2_STD_PAL_I; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_PAL_DK; + case 'M': + case 'm': + return V4L2_STD_PAL_M; + case '-': + break; + default: + IVTV_WARN("pal= argument not recognised\n"); + return 0; + } + + switch (secam[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + case 'h': + case 'H': + return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H; + case 'd': + case 'D': + case 'k': + case 'K': + return V4L2_STD_SECAM_DK; + case 'l': + case 'L': + if (secam[1] == 'C' || secam[1] == 'c') + return V4L2_STD_SECAM_LC; + return V4L2_STD_SECAM_L; + case '-': + break; + default: + IVTV_WARN("secam= argument not recognised\n"); + return 0; + } + + switch (ntsc[0]) { + case 'm': + case 'M': + return V4L2_STD_NTSC_M; + case 'j': + case 'J': + return V4L2_STD_NTSC_M_JP; + case 'k': + case 'K': + return V4L2_STD_NTSC_M_KR; + case '-': + break; + default: + IVTV_WARN("ntsc= argument not recognised\n"); + return 0; + } + + /* no match found */ + return 0; +} + +static void ivtv_process_options(struct ivtv *itv) +{ + const char *chipname; + int i, j; + + itv->options.megabytes[IVTV_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers; + itv->options.megabytes[IVTV_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers; + itv->options.megabytes[IVTV_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers; + itv->options.megabytes[IVTV_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers; + itv->options.megabytes[IVTV_DEC_STREAM_TYPE_MPG] = dec_mpg_buffers; + itv->options.megabytes[IVTV_DEC_STREAM_TYPE_YUV] = dec_yuv_buffers; + itv->options.megabytes[IVTV_DEC_STREAM_TYPE_VBI] = dec_vbi_buffers; + itv->options.cardtype = cardtype[itv->num]; + itv->options.tuner = tuner[itv->num]; + itv->options.radio = radio[itv->num]; + itv->options.newi2c = newi2c; + + itv->std = ivtv_parse_std(itv); + itv->has_cx23415 = (itv->dev->device == PCI_DEVICE_ID_IVTV15); + chipname = itv->has_cx23415 ? "cx23415" : "cx23416"; + if (itv->options.cardtype == -1) { + IVTV_INFO("Ignore card (detected %s based chip)\n", chipname); + return; + } + if ((itv->card = ivtv_get_card(itv->options.cardtype - 1))) { + IVTV_INFO("User specified %s card (detected %s based chip)\n", + itv->card->name, chipname); + } else if (itv->options.cardtype != 0) { + IVTV_ERR("Unknown user specified type, trying to autodetect card\n"); + } + if (itv->card == NULL) { + if (itv->dev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE || + itv->dev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE_ALT1 || + itv->dev->subsystem_vendor == IVTV_PCI_ID_HAUPPAUGE_ALT2) { + itv->card = ivtv_get_card(itv->has_cx23415 ? IVTV_CARD_PVR_350 : IVTV_CARD_PVR_150); + IVTV_INFO("Autodetected Hauppauge card (%s based)\n", + chipname); + } + } + if (itv->card == NULL) { + for (i = 0; (itv->card = ivtv_get_card(i)); i++) { + if (itv->card->pci_list == NULL) + continue; + for (j = 0; itv->card->pci_list[j].device; j++) { + if (itv->dev->device != + itv->card->pci_list[j].device) + continue; + if (itv->dev->subsystem_vendor != + itv->card->pci_list[j].subsystem_vendor) + continue; + if (itv->dev->subsystem_device != + itv->card->pci_list[j].subsystem_device) + continue; + IVTV_INFO("Autodetected %s card (%s based)\n", + itv->card->name, chipname); + goto done; + } + } + } +done: + + if (itv->card == NULL) { + itv->card = ivtv_get_card(IVTV_CARD_PVR_150); + IVTV_ERR("Unknown card: vendor/device: %04x/%04x\n", + itv->dev->vendor, itv->dev->device); + IVTV_ERR(" subsystem vendor/device: %04x/%04x\n", + itv->dev->subsystem_vendor, itv->dev->subsystem_device); + IVTV_ERR(" %s based\n", chipname); + IVTV_ERR("Defaulting to %s card\n", itv->card->name); + IVTV_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n"); + IVTV_ERR("card you have to the ivtv-devel mailinglist (www.ivtvdriver.org)\n"); + IVTV_ERR("Prefix your subject line with [UNKNOWN CARD].\n"); + } + itv->v4l2_cap = itv->card->v4l2_capabilities; + itv->card_name = itv->card->name; +} + +/* Precondition: the ivtv structure has been memset to 0. Only + the dev and num fields have been filled in. + No assumptions on the card type may be made here (see ivtv_init_struct2 + for that). + */ +static int __devinit ivtv_init_struct1(struct ivtv *itv) +{ + itv->base_addr = pci_resource_start(itv->dev, 0); + itv->enc_mbox.max_mbox = 2; /* the encoder has 3 mailboxes (0-2) */ + itv->dec_mbox.max_mbox = 1; /* the decoder has 2 mailboxes (0-1) */ + + mutex_init(&itv->i2c_bus_lock); + mutex_init(&itv->udma.lock); + + spin_lock_init(&itv->lock); + spin_lock_init(&itv->dma_reg_lock); + + itv->irq_work_queues = create_workqueue(itv->name); + if (itv->irq_work_queues == NULL) { + IVTV_ERR("Could not create ivtv workqueue\n"); + return -1; + } + + INIT_WORK(&itv->irq_work_queue, ivtv_irq_work_handler); + + /* start counting open_id at 1 */ + itv->open_id = 1; + + /* Initial settings */ + cx2341x_fill_defaults(&itv->params); + itv->params.port = CX2341X_PORT_MEMORY; + itv->params.capabilities = CX2341X_CAP_HAS_SLICED_VBI; + init_waitqueue_head(&itv->cap_w); + init_waitqueue_head(&itv->event_waitq); + init_waitqueue_head(&itv->vsync_waitq); + init_waitqueue_head(&itv->dma_waitq); + init_timer(&itv->dma_timer); + itv->dma_timer.function = ivtv_unfinished_dma; + itv->dma_timer.data = (unsigned long)itv; + + itv->cur_dma_stream = -1; + itv->audio_stereo_mode = AUDIO_STEREO; + itv->audio_bilingual_mode = AUDIO_MONO_LEFT; + + /* Ctrls */ + itv->speed = 1000; + + /* VBI */ + itv->vbi.in.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; + itv->vbi.sliced_in = &itv->vbi.in.fmt.sliced; + + /* OSD */ + itv->osd_global_alpha_state = 1; + itv->osd_global_alpha = 255; + + /* YUV */ + atomic_set(&itv->yuv_info.next_dma_frame, -1); + itv->yuv_info.lace_mode = ivtv_yuv_mode; + itv->yuv_info.lace_threshold = ivtv_yuv_threshold; + return 0; +} + +/* Second initialization part. Here the card type has been + autodetected. */ +static void __devinit ivtv_init_struct2(struct ivtv *itv) +{ + int i; + + for (i = 0; i < IVTV_CARD_MAX_VIDEO_INPUTS; i++) + if (itv->card->video_inputs[i].video_type == 0) + break; + itv->nof_inputs = i; + for (i = 0; i < IVTV_CARD_MAX_AUDIO_INPUTS; i++) + if (itv->card->audio_inputs[i].audio_type == 0) + break; + itv->nof_audio_inputs = i; + + /* 0x00EF = saa7114(239) 0x00F0 = saa7115(240) 0x0106 = micro */ + if (itv->card->hw_all & (IVTV_HW_SAA7115 | IVTV_HW_SAA717X)) + itv->digitizer = 0xF1; + else if (itv->card->hw_all & IVTV_HW_SAA7114) + itv->digitizer = 0xEF; + else /* cx25840 */ + itv->digitizer = 0x140; + + if (itv->card->hw_all & IVTV_HW_CX25840) { + itv->vbi.sliced_size = 288; /* multiple of 16, real size = 284 */ + } else { + itv->vbi.sliced_size = 64; /* multiple of 16, real size = 52 */ + } + + /* Find tuner input */ + for (i = 0; i < itv->nof_inputs; i++) { + if (itv->card->video_inputs[i].video_type == + IVTV_CARD_INPUT_VID_TUNER) + break; + } + if (i == itv->nof_inputs) + i = 0; + itv->active_input = i; + itv->audio_input = itv->card->video_inputs[i].audio_index; + if (itv->card->hw_all & IVTV_HW_CX25840) + itv->video_dec_func = ivtv_cx25840; + else if (itv->card->hw_all & IVTV_HW_SAA717X) + itv->video_dec_func = ivtv_saa717x; + else + itv->video_dec_func = ivtv_saa7115; +} + +static int ivtv_setup_pci(struct ivtv *itv, struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + u16 cmd; + unsigned char pci_latency; + + IVTV_DEBUG_INFO("Enabling pci device\n"); + + if (pci_enable_device(dev)) { + IVTV_ERR("Can't enable device %d!\n", itv->num); + return -EIO; + } + if (pci_set_dma_mask(dev, 0xffffffff)) { + IVTV_ERR("No suitable DMA available on card %d.\n", itv->num); + return -EIO; + } + if (!request_mem_region(itv->base_addr, IVTV_ENCODER_SIZE, "ivtv encoder")) { + IVTV_ERR("Cannot request encoder memory region on card %d.\n", itv->num); + return -EIO; + } + + if (!request_mem_region(itv->base_addr + IVTV_REG_OFFSET, + IVTV_REG_SIZE, "ivtv registers")) { + IVTV_ERR("Cannot request register memory region on card %d.\n", itv->num); + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + return -EIO; + } + + if (itv->has_cx23415 && + !request_mem_region(itv->base_addr + IVTV_DECODER_OFFSET, + IVTV_DECODER_SIZE, "ivtv decoder")) { + IVTV_ERR("Cannot request decoder memory region on card %d.\n", itv->num); + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + release_mem_region(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + return -EIO; + } + + /* Check for bus mastering */ + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + IVTV_DEBUG_INFO("Attempting to enable Bus Mastering\n"); + pci_set_master(dev); + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MASTER)) { + IVTV_ERR("Bus Mastering is not enabled\n"); + return -ENXIO; + } + } + IVTV_DEBUG_INFO("Bus Mastering Enabled.\n"); + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &itv->card_rev); + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &pci_latency); + + if (pci_latency < 64 && ivtv_pci_latency) { + IVTV_INFO("Unreasonably low latency timer, " + "setting to 64 (was %d)\n", pci_latency); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, 64); + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &pci_latency); + } + /* This config space value relates to DMA latencies. The + default value 0x8080 is too low however and will lead + to DMA errors. 0xffff is the max value which solves + these problems. */ + pci_write_config_dword(dev, 0x40, 0xffff); + + IVTV_DEBUG_INFO("%d (rev %d) at %02x:%02x.%x, " + "irq: %d, latency: %d, memory: 0x%lx\n", + itv->dev->device, itv->card_rev, dev->bus->number, + PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), + itv->dev->irq, pci_latency, (unsigned long)itv->base_addr); + + return 0; +} + +static void ivtv_request_module(struct ivtv *itv, const char *name) +{ + if (request_module(name) != 0) { + IVTV_ERR("Failed to load module %s\n", name); + } else { + IVTV_DEBUG_INFO("Loaded module %s\n", name); + } +} + +static void ivtv_load_and_init_modules(struct ivtv *itv) +{ + struct v4l2_control ctrl; + u32 hw = itv->card->hw_all; + int i; + + /* load modules */ +#ifndef CONFIG_VIDEO_TUNER + if (hw & IVTV_HW_TUNER) { + ivtv_request_module(itv, "tuner"); +#ifdef HAVE_XC3028 + if (itv->options.tuner == TUNER_XCEIVE_XC3028) + ivtv_request_module(itv, "xc3028-tuner"); +#endif + } +#endif +#ifndef CONFIG_VIDEO_CX25840 + if (hw & IVTV_HW_CX25840) + ivtv_request_module(itv, "cx25840"); +#endif +#ifndef CONFIG_VIDEO_SAA711X + if (hw & IVTV_HW_SAA711X) + ivtv_request_module(itv, "saa7115"); +#endif +#ifndef CONFIG_VIDEO_SAA7127 + if (hw & IVTV_HW_SAA7127) + ivtv_request_module(itv, "saa7127"); +#endif + if (hw & IVTV_HW_SAA717X) + ivtv_request_module(itv, "saa717x"); +#ifndef CONFIG_VIDEO_UPD64031A + if (hw & IVTV_HW_UPD64031A) + ivtv_request_module(itv, "upd64031a"); +#endif +#ifndef CONFIG_VIDEO_UPD64083 + if (hw & IVTV_HW_UPD6408X) + ivtv_request_module(itv, "upd64083"); +#endif +#ifndef CONFIG_VIDEO_MSP3400 + if (hw & IVTV_HW_MSP34XX) + ivtv_request_module(itv, "msp3400"); +#endif + if (hw & IVTV_HW_TVAUDIO) + ivtv_request_module(itv, "tvaudio"); +#ifndef CONFIG_VIDEO_WM8775 + if (hw & IVTV_HW_WM8775) + ivtv_request_module(itv, "wm8775"); +#endif +#ifndef CONFIG_VIDEO_WM8739 + if (hw & IVTV_HW_WM8739) + ivtv_request_module(itv, "wm8739"); +#endif +#ifndef CONFIG_VIDEO_CS53L32A + if (hw & IVTV_HW_CS53L32A) + ivtv_request_module(itv, "cs53l32a"); +#endif + + /* check which i2c devices are actually found */ + for (i = 0; i < 32; i++) { + u32 device = 1 << i; + + if (!(device & hw)) + continue; + if (device == IVTV_HW_GPIO) { + /* GPIO is always available */ + itv->hw_flags |= IVTV_HW_GPIO; + continue; + } + if (ivtv_i2c_hw_addr(itv, device) > 0) + itv->hw_flags |= device; + } + + hw = itv->hw_flags; + + if (itv->card->type == IVTV_CARD_CX23416GYC) { + /* Several variations of this card exist, detect which card + type should be used. */ + if ((hw & (IVTV_HW_UPD64031A | IVTV_HW_UPD6408X)) == 0) + itv->card = ivtv_get_card(IVTV_CARD_CX23416GYC_NOGRYCS); + else if ((hw & IVTV_HW_UPD64031A) == 0) + itv->card = ivtv_get_card(IVTV_CARD_CX23416GYC_NOGR); + } + + if (hw & IVTV_HW_CX25840) { + /* CX25840_CID_ENABLE_PVR150_WORKAROUND */ + ctrl.id = V4L2_CID_PRIVATE_BASE; + ctrl.value = itv->pvr150_workaround; + itv->video_dec_func(itv, VIDIOC_S_CTRL, &ctrl); + + itv->vbi.raw_decoder_line_size = 1444; + itv->vbi.raw_decoder_sav_odd_field = 0x20; + itv->vbi.raw_decoder_sav_even_field = 0x60; + itv->vbi.sliced_decoder_line_size = 272; + itv->vbi.sliced_decoder_sav_odd_field = 0xB0; + itv->vbi.sliced_decoder_sav_even_field = 0xF0; + } + + if (hw & IVTV_HW_SAA711X) { + struct v4l2_chip_ident v = { V4L2_CHIP_MATCH_I2C_DRIVER, I2C_DRIVERID_SAA711X }; + + /* determine the exact saa711x model */ + itv->hw_flags &= ~IVTV_HW_SAA711X; + + ivtv_saa7115(itv, VIDIOC_G_CHIP_IDENT, &v); + if (v.ident == V4L2_IDENT_SAA7114) { + itv->hw_flags |= IVTV_HW_SAA7114; + /* VBI is not yet supported by the saa7114 driver. */ + itv->v4l2_cap &= ~(V4L2_CAP_SLICED_VBI_CAPTURE|V4L2_CAP_VBI_CAPTURE); + } + else { + itv->hw_flags |= IVTV_HW_SAA7115; + } + itv->vbi.raw_decoder_line_size = 1443; + itv->vbi.raw_decoder_sav_odd_field = 0x25; + itv->vbi.raw_decoder_sav_even_field = 0x62; + itv->vbi.sliced_decoder_line_size = 51; + itv->vbi.sliced_decoder_sav_odd_field = 0xAB; + itv->vbi.sliced_decoder_sav_even_field = 0xEC; + } + + if (hw & IVTV_HW_SAA717X) { + itv->vbi.raw_decoder_line_size = 1443; + itv->vbi.raw_decoder_sav_odd_field = 0x25; + itv->vbi.raw_decoder_sav_even_field = 0x62; + itv->vbi.sliced_decoder_line_size = 51; + itv->vbi.sliced_decoder_sav_odd_field = 0xAB; + itv->vbi.sliced_decoder_sav_even_field = 0xEC; + } +} + +static int __devinit ivtv_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + int retval = 0; + int video_input; + int yuv_buf_size; + int vbi_buf_size; + int fw_retry_count = 3; + struct ivtv *itv; + struct v4l2_frequency vf; + + spin_lock(&ivtv_cards_lock); + + /* Make sure we've got a place for this card */ + if (ivtv_cards_active == IVTV_MAX_CARDS) { + printk(KERN_ERR "ivtv: Maximum number of cards detected (%d).\n", + ivtv_cards_active); + spin_unlock(&ivtv_cards_lock); + return -ENOMEM; + } + + itv = kzalloc(sizeof(struct ivtv), GFP_ATOMIC); + if (itv == 0) { + spin_unlock(&ivtv_cards_lock); + return -ENOMEM; + } + ivtv_cards[ivtv_cards_active] = itv; + itv->dev = dev; + itv->num = ivtv_cards_active++; + snprintf(itv->name, sizeof(itv->name) - 1, "ivtv%d", itv->num); + if (itv->num) { + printk(KERN_INFO "ivtv: ====================== NEXT CARD ======================\n"); + } + + spin_unlock(&ivtv_cards_lock); + + ivtv_process_options(itv); + if (itv->options.cardtype == -1) { + retval = -ENODEV; + goto err; + } + if (ivtv_init_struct1(itv)) { + retval = -ENOMEM; + goto err; + } + + IVTV_DEBUG_INFO("base addr: 0x%08x\n", itv->base_addr); + + /* PCI Device Setup */ + if ((retval = ivtv_setup_pci(itv, dev, pci_id)) != 0) { + if (retval == -EIO) + goto free_workqueue; + else if (retval == -ENXIO) + goto free_mem; + } + /* save itv in the pci struct for later use */ + pci_set_drvdata(dev, itv); + + /* map io memory */ + IVTV_DEBUG_INFO("attempting ioremap at 0x%08x len 0x%08x\n", + itv->base_addr + IVTV_ENCODER_OFFSET, IVTV_ENCODER_SIZE); + itv->enc_mem = ioremap_nocache(itv->base_addr + IVTV_ENCODER_OFFSET, + IVTV_ENCODER_SIZE); + if (!itv->enc_mem) { + IVTV_ERR("ioremap failed, perhaps increasing __VMALLOC_RESERVE in page.h\n"); + IVTV_ERR("or disabling CONFIG_HIMEM4G into the kernel would help\n"); + retval = -ENOMEM; + goto free_mem; + } + + if (itv->has_cx23415) { + IVTV_DEBUG_INFO("attempting ioremap at 0x%08x len 0x%08x\n", + itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); + itv->dec_mem = ioremap_nocache(itv->base_addr + IVTV_DECODER_OFFSET, + IVTV_DECODER_SIZE); + if (!itv->dec_mem) { + IVTV_ERR("ioremap failed, perhaps increasing __VMALLOC_RESERVE in page.h\n"); + IVTV_ERR("or disabling CONFIG_HIMEM4G into the kernel would help\n"); + retval = -ENOMEM; + goto free_mem; + } + } + else { + itv->dec_mem = itv->enc_mem; + } + + /* map registers memory */ + IVTV_DEBUG_INFO("attempting ioremap at 0x%08x len 0x%08x\n", + itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + itv->reg_mem = + ioremap_nocache(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + if (!itv->reg_mem) { + IVTV_ERR("ioremap failed, perhaps increasing __VMALLOC_RESERVE in page.h\n"); + IVTV_ERR("or disabling CONFIG_HIMEM4G into the kernel would help\n"); + retval = -ENOMEM; + goto free_io; + } + + while (--fw_retry_count > 0) { + /* load firmware */ + if (ivtv_firmware_init(itv) == 0) + break; + if (fw_retry_count > 1) + IVTV_WARN("Retry loading firmware\n"); + } + if (fw_retry_count == 0) { + IVTV_ERR("Error initializing firmware\n"); + goto free_i2c; + } + + /* Try and get firmware versions */ + IVTV_DEBUG_INFO("Getting firmware version..\n"); + ivtv_firmware_versions(itv); + + /* Check yuv output filter table */ + if (itv->has_cx23415) ivtv_yuv_filter_check(itv); + + ivtv_gpio_init(itv); + + /* active i2c */ + IVTV_DEBUG_INFO("activating i2c...\n"); + if (init_ivtv_i2c(itv)) { + IVTV_ERR("Could not initialize i2c\n"); + goto free_irq; + } + + IVTV_DEBUG_INFO("Active card count: %d.\n", ivtv_cards_active); + + if (itv->card->hw_all & IVTV_HW_TVEEPROM) { +#ifdef CONFIG_VIDEO_TVEEPROM_MODULE + ivtv_request_module(itv, "tveeprom"); +#endif + /* Based on the model number the cardtype may be changed. + The PCI IDs are not always reliable. */ + ivtv_process_eeprom(itv); + } + + if (itv->std == 0) { + itv->std = V4L2_STD_NTSC_M; + } + + if (itv->options.tuner == -1) { + int i; + + for (i = 0; i < IVTV_CARD_MAX_TUNERS; i++) { + if ((itv->std & itv->card->tuners[i].std) == 0) + continue; + itv->options.tuner = itv->card->tuners[i].tuner; + break; + } + } + /* if no tuner was found, then pick the first tuner in the card list */ + if (itv->options.tuner == -1 && itv->card->tuners[0].std) { + itv->std = itv->card->tuners[0].std; + itv->options.tuner = itv->card->tuners[0].tuner; + } + if (itv->options.radio == -1) + itv->options.radio = (itv->card->radio_input.audio_type != 0); + + /* The card is now fully identified, continue with card-specific + initialization. */ + ivtv_init_struct2(itv); + + ivtv_load_and_init_modules(itv); + + if (itv->std & V4L2_STD_525_60) { + itv->is_60hz = 1; + itv->is_out_60hz = 1; + } else { + itv->is_50hz = 1; + itv->is_out_50hz = 1; + } + itv->params.video_gop_size = itv->is_60hz ? 15 : 12; + + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_MPG] = 0x08000; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_PCM] = 0x01200; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_MPG] = 0x10000; + + /* 0x15180 == 720 * 480 / 4, 0x19500 == 720 * 576 / 4 */ + yuv_buf_size = itv->is_60hz ? 0x15180 : 0x19500; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_YUV] = yuv_buf_size / 2; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_YUV] = yuv_buf_size / 8; + + /* Setup VBI Raw Size. Should be big enough to hold PAL. + It is possible to switch between PAL and NTSC, so we need to + take the largest size here. */ + /* 1456 is multiple of 16, real size = 1444 */ + itv->vbi.raw_size = 1456; + /* We use a buffer size of 1/2 of the total size needed for a + frame. This is actually very useful, since we now receive + a field at a time and that makes 'compressing' the raw data + down to size by stripping off the SAV codes a lot easier. + Note: having two different buffer sizes prevents standard + switching on the fly. We need to find a better solution... */ + vbi_buf_size = itv->vbi.raw_size * (itv->is_60hz ? 24 : 36) / 2; + itv->stream_buf_size[IVTV_ENC_STREAM_TYPE_VBI] = vbi_buf_size; + itv->stream_buf_size[IVTV_DEC_STREAM_TYPE_VBI] = sizeof(struct v4l2_sliced_vbi_data) * 36; + + if (itv->options.radio > 0) + itv->v4l2_cap |= V4L2_CAP_RADIO; + + if (itv->options.tuner > -1) { + struct tuner_setup setup; + + setup.addr = ADDR_UNSET; + setup.type = itv->options.tuner; + setup.mode_mask = T_ANALOG_TV; /* matches TV tuners */ +#ifdef HAVE_XC3028 + setup.initmode = V4L2_TUNER_ANALOG_TV; + if (itv->options.tuner == TUNER_XCEIVE_XC3028) { + setup.gpio_write = ivtv_reset_tuner_gpio; + setup.gpio_priv = itv; + } +#endif + ivtv_call_i2c_clients(itv, TUNER_SET_TYPE_ADDR, &setup); + } + + vf.tuner = 0; + vf.type = V4L2_TUNER_ANALOG_TV; + vf.frequency = 6400; /* the tuner 'baseline' frequency */ + if (itv->std & V4L2_STD_NTSC_M) { + /* Why on earth? */ + vf.frequency = 1076; /* ch. 4 67250*16/1000 */ + } + + /* The tuner is fixed to the standard. The other inputs (e.g. S-Video) + are not. */ + itv->tuner_std = itv->std; + + video_input = itv->active_input; + itv->active_input++; /* Force update of input */ + ivtv_v4l2_ioctls(itv, NULL, VIDIOC_S_INPUT, &video_input); + + /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code + in one place. */ + itv->std++; /* Force full standard initialization */ + itv->std_out = itv->std; + ivtv_v4l2_ioctls(itv, NULL, VIDIOC_S_FREQUENCY, &vf); + + retval = ivtv_streams_setup(itv); + if (retval) { + IVTV_ERR("Error %d setting up streams\n", retval); + goto free_i2c; + } + + if (itv->card->v4l2_capabilities & V4L2_CAP_VIDEO_OUTPUT) { + ivtv_init_mpeg_decoder(itv); + } + ivtv_v4l2_ioctls(itv, NULL, VIDIOC_S_STD, &itv->tuner_std); + + IVTV_DEBUG_IRQ("Masking interrupts\n"); + /* clear interrupt mask, effectively disabling interrupts */ + ivtv_set_irq_mask(itv, 0xffffffff); + + /* Register IRQ */ + retval = request_irq(itv->dev->irq, ivtv_irq_handler, + IRQF_SHARED | IRQF_DISABLED, itv->name, (void *)itv); + if (retval) { + IVTV_ERR("Failed to register irq %d\n", retval); + goto free_streams; + } + + /* On a cx23416 this seems to be able to enable DMA to the chip? */ + if (!itv->has_cx23415) + write_reg_sync(0x03, IVTV_REG_DMACONTROL); + + /* Default interrupts enabled. For the PVR350 this includes the + decoder VSYNC interrupt, which is always on. It is not only used + during decoding but also by the OSD. + Some old PVR250 cards had a cx23415, so testing for that is too + general. Instead test if the card has video output capability. */ + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_INIT | IVTV_IRQ_DEC_VSYNC); + else + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_INIT); + + if (itv->has_cx23415) + ivtv_set_osd_alpha(itv); + + IVTV_INFO("Initialized %s, card #%d\n", itv->card_name, itv->num); + + return 0; + + free_irq: + free_irq(itv->dev->irq, (void *)itv); + free_streams: + ivtv_streams_cleanup(itv); + free_i2c: + exit_ivtv_i2c(itv); + free_io: + ivtv_iounmap(itv); + free_mem: + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + release_mem_region(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + if (itv->has_cx23415) + release_mem_region(itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); + free_workqueue: + destroy_workqueue(itv->irq_work_queues); + err: + if (retval == 0) + retval = -ENODEV; + IVTV_ERR("Error %d on initialization\n", retval); + + kfree(ivtv_cards[ivtv_cards_active]); + ivtv_cards[ivtv_cards_active] = NULL; + return retval; +} + +static void ivtv_remove(struct pci_dev *pci_dev) +{ + struct ivtv *itv = pci_get_drvdata(pci_dev); + + IVTV_DEBUG_INFO("Removing Card #%d.\n", itv->num); + + /* Stop all captures */ + IVTV_DEBUG_INFO(" Stopping all streams.\n"); + if (atomic_read(&itv->capturing) > 0) + ivtv_stop_all_captures(itv); + + /* Stop all decoding */ + IVTV_DEBUG_INFO(" Stopping decoding.\n"); + if (atomic_read(&itv->decoding) > 0) { + int type; + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) + type = IVTV_DEC_STREAM_TYPE_YUV; + else + type = IVTV_DEC_STREAM_TYPE_MPG; + ivtv_stop_v4l2_decode_stream(&itv->streams[type], + VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY, 0); + } + + /* Interrupts */ + IVTV_DEBUG_INFO(" Disabling interrupts.\n"); + ivtv_set_irq_mask(itv, 0xffffffff); + del_timer_sync(&itv->dma_timer); + + /* Stop all Work Queues */ + IVTV_DEBUG_INFO(" Stop Work Queues.\n"); + flush_workqueue(itv->irq_work_queues); + destroy_workqueue(itv->irq_work_queues); + + IVTV_DEBUG_INFO(" Stopping Firmware.\n"); + ivtv_halt_firmware(itv); + + IVTV_DEBUG_INFO(" Unregistering v4l devices.\n"); + ivtv_streams_cleanup(itv); + IVTV_DEBUG_INFO(" Freeing dma resources.\n"); + ivtv_udma_free(itv); + + exit_ivtv_i2c(itv); + + IVTV_DEBUG_INFO(" Releasing irq.\n"); + free_irq(itv->dev->irq, (void *)itv); + + if (itv->dev) { + ivtv_iounmap(itv); + } + + IVTV_DEBUG_INFO(" Releasing mem.\n"); + release_mem_region(itv->base_addr, IVTV_ENCODER_SIZE); + release_mem_region(itv->base_addr + IVTV_REG_OFFSET, IVTV_REG_SIZE); + if (itv->has_cx23415) + release_mem_region(itv->base_addr + IVTV_DECODER_OFFSET, IVTV_DECODER_SIZE); + + pci_disable_device(itv->dev); + + IVTV_INFO("Removed %s, card #%d\n", itv->card_name, itv->num); +} + +/* define a pci_driver for card detection */ +static struct pci_driver ivtv_pci_driver = { + .name = "ivtv", + .id_table = ivtv_pci_tbl, + .probe = ivtv_probe, + .remove = ivtv_remove, +}; + +static int module_start(void) +{ + printk(KERN_INFO "ivtv: ==================== START INIT IVTV ====================\n"); + printk(KERN_INFO "ivtv: version %s (" VERMAGIC_STRING ") loading\n", IVTV_VERSION); + + memset(ivtv_cards, 0, sizeof(ivtv_cards)); + + /* Validate parameters */ + if (ivtv_first_minor < 0 || ivtv_first_minor >= IVTV_MAX_CARDS) { + printk(KERN_ERR "ivtv: ivtv_first_minor must be between 0 and %d. Exiting...\n", + IVTV_MAX_CARDS - 1); + return -1; + } + + if (ivtv_debug < 0 || ivtv_debug > 511) { + ivtv_debug = 0; + printk(KERN_INFO "ivtv: debug value must be >= 0 and <= 511!\n"); + } + + if (pci_register_driver(&ivtv_pci_driver)) { + printk(KERN_ERR "ivtv: Error detecting PCI card\n"); + return -ENODEV; + } + printk(KERN_INFO "ivtv: ==================== END INIT IVTV ====================\n"); + return 0; +} + +static void module_cleanup(void) +{ + int i, j; + + pci_unregister_driver(&ivtv_pci_driver); + + for (i = 0; i < ivtv_cards_active; i++) { + if (ivtv_cards[i] == NULL) + continue; + for (j = 0; j < IVTV_VBI_FRAMES; j++) { + kfree(ivtv_cards[i]->vbi.sliced_mpeg_data[j]); + } + kfree(ivtv_cards[i]); + } +} + +/* Note: These symbols are exported because they are used by the ivtv-fb + framebuffer module and an infrared module for the IR-blaster. */ +EXPORT_SYMBOL(ivtv_set_irq_mask); +EXPORT_SYMBOL(ivtv_cards_active); +EXPORT_SYMBOL(ivtv_cards); +EXPORT_SYMBOL(ivtv_api); +EXPORT_SYMBOL(ivtv_vapi); +EXPORT_SYMBOL(ivtv_vapi_result); +EXPORT_SYMBOL(ivtv_clear_irq_mask); +EXPORT_SYMBOL(ivtv_debug); +EXPORT_SYMBOL(ivtv_reset_ir_gpio); +EXPORT_SYMBOL(ivtv_udma_setup); +EXPORT_SYMBOL(ivtv_udma_unmap); +EXPORT_SYMBOL(ivtv_udma_alloc); +EXPORT_SYMBOL(ivtv_udma_prepare); + +module_init(module_start); +module_exit(module_cleanup); diff --git a/drivers/media/video/ivtv/ivtv-driver.h b/drivers/media/video/ivtv/ivtv-driver.h new file mode 100644 index 00000000000..9a412d6c6d0 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-driver.h @@ -0,0 +1,868 @@ +/* + ivtv driver internal defines and structures + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef IVTV_DRIVER_H +#define IVTV_DRIVER_H + +/* Internal header for ivtv project: + * Driver for the cx23415/6 chip. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + * License: GPL + * http://www.ivtvdriver.org + * + * ----- + * MPG600/MPG160 support by T.Adachi <tadachi@tadachi-net.com> + * and Takeru KOMORIYA<komoriya@paken.org> + * + * AVerMedia M179 GPIO info by Chris Pinkham <cpinkham@bc2va.org> + * using information provided by Jiun-Kuei Jung @ AVerMedia. + */ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/list.h> +#include <linux/unistd.h> +#include <linux/byteorder/swab.h> +#include <linux/pagemap.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#include <linux/dvb/video.h> +#include <linux/dvb/audio.h> +#include <media/v4l2-common.h> +#include <media/tuner.h> +#include <media/cx2341x.h> + +/* #define HAVE_XC3028 1 */ + +#include <media/ivtv.h> + +#ifdef CONFIG_LIRC_I2C +# error "This driver is not compatible with the LIRC I2C kernel configuration option." +#endif /* CONFIG_LIRC_I2C */ + +#ifndef CONFIG_PCI +# error "This driver requires kernel PCI support." +#endif /* CONFIG_PCI */ + +#define IVTV_ENCODER_OFFSET 0x00000000 +#define IVTV_ENCODER_SIZE 0x00800000 /* Last half isn't needed 0x01000000 */ + +#define IVTV_DECODER_OFFSET 0x01000000 +#define IVTV_DECODER_SIZE 0x00800000 /* Last half isn't needed 0x01000000 */ + +#define IVTV_REG_OFFSET 0x02000000 +#define IVTV_REG_SIZE 0x00010000 + +/* Buffers on hardware offsets */ +#define IVTV_YUV_BUFFER_OFFSET 0x001a8600 /* First YUV Buffer */ +#define IVTV_YUV_BUFFER_OFFSET_1 0x00240400 /* Second YUV Buffer */ +#define IVTV_YUV_BUFFER_OFFSET_2 0x002d8200 /* Third YUV Buffer */ +#define IVTV_YUV_BUFFER_OFFSET_3 0x00370000 /* Fourth YUV Buffer */ +#define IVTV_YUV_BUFFER_UV_OFFSET 0x65400 /* Offset to UV Buffer */ + +/* Offset to filter table in firmware */ +#define IVTV_YUV_HORIZONTAL_FILTER_OFFSET 0x025d8 +#define IVTV_YUV_VERTICAL_FILTER_OFFSET 0x03358 + +extern const u32 yuv_offset[4]; + +/* Maximum ivtv driver instances. + Based on 6 PVR500s each with two PVR15s... + TODO: make this dynamic. I believe it is only a global in order to support + ivtv-fb. There must be a better way to do that. */ +#define IVTV_MAX_CARDS 12 + +/* Supported cards */ +#define IVTV_CARD_PVR_250 0 /* WinTV PVR 250 */ +#define IVTV_CARD_PVR_350 1 /* encoder, decoder, tv-out */ +#define IVTV_CARD_PVR_150 2 /* WinTV PVR 150 and PVR 500 (really just two + PVR150s on one PCI board) */ +#define IVTV_CARD_M179 3 /* AVerMedia M179 (encoder only) */ +#define IVTV_CARD_MPG600 4 /* Kuroutoshikou ITVC16-STVLP/YUAN MPG600, encoder only */ +#define IVTV_CARD_MPG160 5 /* Kuroutoshikou ITVC15-STVLP/YUAN MPG160 + cx23415 based, but does not have tv-out */ +#define IVTV_CARD_PG600 6 /* YUAN PG600/DIAMONDMM PVR-550 based on the CX Falcon 2 */ +#define IVTV_CARD_AVC2410 7 /* Adaptec AVC-2410 */ +#define IVTV_CARD_AVC2010 8 /* Adaptec AVD-2010 (No Tuner) */ +#define IVTV_CARD_TG5000TV 9 /* NAGASE TRANSGEAR 5000TV, encoder only */ +#define IVTV_CARD_VA2000MAX_SNT6 10 /* VA2000MAX-STN6 */ +#define IVTV_CARD_CX23416GYC 11 /* Kuroutoshikou CX23416GYC-STVLP (Yuan MPG600GR OEM) */ +#define IVTV_CARD_GV_MVPRX 12 /* I/O Data GV-MVP/RX, RX2, RX2W */ +#define IVTV_CARD_GV_MVPRX2E 13 /* I/O Data GV-MVP/RX2E */ +#define IVTV_CARD_GOTVIEW_PCI_DVD 14 /* GotView PCI DVD */ +#define IVTV_CARD_GOTVIEW_PCI_DVD2 15 /* GotView PCI DVD2 */ +#define IVTV_CARD_YUAN_MPC622 16 /* Yuan MPC622 miniPCI */ +#define IVTV_CARD_DCTMTVP1 17 /* DIGITAL COWBOY DCT-MTVP1 */ +#ifdef HAVE_XC3028 +#define IVTV_CARD_PG600V2 18 /* Yuan PG600V2/GotView PCI DVD Lite/Club3D ZAP-TV1x01 */ +#define IVTV_CARD_LAST 18 +#else +#define IVTV_CARD_LAST 17 +#endif + +/* Variants of existing cards but with the same PCI IDs. The driver + detects these based on other device information. + These cards must always come last. + New cards must be inserted above, and the indices of the cards below + must be adjusted accordingly. */ + +/* PVR-350 V1 (uses saa7114) */ +#define IVTV_CARD_PVR_350_V1 (IVTV_CARD_LAST+1) +/* 2 variants of Kuroutoshikou CX23416GYC-STVLP (Yuan MPG600GR OEM) */ +#define IVTV_CARD_CX23416GYC_NOGR (IVTV_CARD_LAST+2) +#define IVTV_CARD_CX23416GYC_NOGRYCS (IVTV_CARD_LAST+3) + +#define IVTV_ENC_STREAM_TYPE_MPG 0 +#define IVTV_ENC_STREAM_TYPE_YUV 1 +#define IVTV_ENC_STREAM_TYPE_VBI 2 +#define IVTV_ENC_STREAM_TYPE_PCM 3 +#define IVTV_ENC_STREAM_TYPE_RAD 4 +#define IVTV_DEC_STREAM_TYPE_MPG 5 +#define IVTV_DEC_STREAM_TYPE_VBI 6 +#define IVTV_DEC_STREAM_TYPE_VOUT 7 +#define IVTV_DEC_STREAM_TYPE_YUV 8 +#define IVTV_MAX_STREAMS 9 + +#define IVTV_V4L2_DEC_MPG_OFFSET 16 /* offset from 0 to register decoder mpg v4l2 minors on */ +#define IVTV_V4L2_ENC_PCM_OFFSET 24 /* offset from 0 to register pcm v4l2 minors on */ +#define IVTV_V4L2_ENC_YUV_OFFSET 32 /* offset from 0 to register yuv v4l2 minors on */ +#define IVTV_V4L2_DEC_YUV_OFFSET 48 /* offset from 0 to register decoder yuv v4l2 minors on */ +#define IVTV_V4L2_DEC_VBI_OFFSET 8 /* offset from 0 to register decoder vbi input v4l2 minors on */ +#define IVTV_V4L2_DEC_VOUT_OFFSET 16 /* offset from 0 to register vbi output v4l2 minors on */ + +#define IVTV_ENC_MEM_START 0x00000000 +#define IVTV_DEC_MEM_START 0x01000000 + +/* system vendor and device IDs */ +#define PCI_VENDOR_ID_ICOMP 0x4444 +#define PCI_DEVICE_ID_IVTV15 0x0803 +#define PCI_DEVICE_ID_IVTV16 0x0016 + +/* subsystem vendor ID */ +#define IVTV_PCI_ID_HAUPPAUGE 0x0070 +#define IVTV_PCI_ID_HAUPPAUGE_ALT1 0x0270 +#define IVTV_PCI_ID_HAUPPAUGE_ALT2 0x4070 +#define IVTV_PCI_ID_ADAPTEC 0x9005 +#define IVTV_PCI_ID_AVERMEDIA 0x1461 +#define IVTV_PCI_ID_YUAN1 0x12ab +#define IVTV_PCI_ID_YUAN2 0xff01 +#define IVTV_PCI_ID_YUAN3 0xffab +#define IVTV_PCI_ID_YUAN4 0xfbab +#define IVTV_PCI_ID_DIAMONDMM 0xff92 +#define IVTV_PCI_ID_IODATA 0x10fc +#define IVTV_PCI_ID_MELCO 0x1154 +#define IVTV_PCI_ID_GOTVIEW1 0xffac +#define IVTV_PCI_ID_GOTVIEW2 0xffad + +/* Decoder Buffer hardware size on Chip */ +#define IVTV_DEC_MAX_BUF 0x00100000 /* max bytes in decoder buffer */ +#define IVTV_DEC_MIN_BUF 0x00010000 /* min bytes in dec buffer */ + +/* ======================================================================== */ +/* ========================== START USER SETTABLE DMA VARIABLES =========== */ +/* ======================================================================== */ + +#define IVTV_DMA_SG_OSD_ENT (2883584/PAGE_SIZE) /* sg entities */ + +/* DMA Buffers, Default size in MB allocated */ +#define IVTV_DEFAULT_ENC_MPG_BUFFERS 4 +#define IVTV_DEFAULT_ENC_YUV_BUFFERS 2 +#define IVTV_DEFAULT_ENC_VBI_BUFFERS 1 +#define IVTV_DEFAULT_ENC_PCM_BUFFERS 1 +#define IVTV_DEFAULT_DEC_MPG_BUFFERS 1 +#define IVTV_DEFAULT_DEC_YUV_BUFFERS 1 +#define IVTV_DEFAULT_DEC_VBI_BUFFERS 1 + +/* ======================================================================== */ +/* ========================== END USER SETTABLE DMA VARIABLES ============= */ +/* ======================================================================== */ + +/* Decoder Status Register */ +#define IVTV_DMA_ERR_LIST 0x00000010 +#define IVTV_DMA_ERR_WRITE 0x00000008 +#define IVTV_DMA_ERR_READ 0x00000004 +#define IVTV_DMA_SUCCESS_WRITE 0x00000002 +#define IVTV_DMA_SUCCESS_READ 0x00000001 +#define IVTV_DMA_READ_ERR (IVTV_DMA_ERR_LIST | IVTV_DMA_ERR_READ) +#define IVTV_DMA_WRITE_ERR (IVTV_DMA_ERR_LIST | IVTV_DMA_ERR_WRITE) +#define IVTV_DMA_ERR (IVTV_DMA_ERR_LIST | IVTV_DMA_ERR_WRITE | IVTV_DMA_ERR_READ) + +/* DMA Registers */ +#define IVTV_REG_DMAXFER (0x0000) +#define IVTV_REG_DMASTATUS (0x0004) +#define IVTV_REG_DECDMAADDR (0x0008) +#define IVTV_REG_ENCDMAADDR (0x000c) +#define IVTV_REG_DMACONTROL (0x0010) +#define IVTV_REG_IRQSTATUS (0x0040) +#define IVTV_REG_IRQMASK (0x0048) + +/* Setup Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC) +#define IVTV_REG_DEC_SDRAM_REFRESH (0x08F8) +#define IVTV_REG_DEC_SDRAM_PRECHARGE (0x08FC) +#define IVTV_REG_VDM (0x2800) +#define IVTV_REG_AO (0x2D00) +#define IVTV_REG_BYTEFLUSH (0x2D24) +#define IVTV_REG_SPU (0x9050) +#define IVTV_REG_HW_BLOCKS (0x9054) +#define IVTV_REG_VPU (0x9058) +#define IVTV_REG_APU (0xA064) + +#define IVTV_IRQ_ENC_START_CAP (0x1 << 31) +#define IVTV_IRQ_ENC_EOS (0x1 << 30) +#define IVTV_IRQ_ENC_VBI_CAP (0x1 << 29) +#define IVTV_IRQ_ENC_VIM_RST (0x1 << 28) +#define IVTV_IRQ_ENC_DMA_COMPLETE (0x1 << 27) +#define IVTV_IRQ_DEC_AUD_MODE_CHG (0x1 << 24) +#define IVTV_IRQ_DEC_DATA_REQ (0x1 << 22) +#define IVTV_IRQ_DEC_DMA_COMPLETE (0x1 << 20) +#define IVTV_IRQ_DEC_VBI_RE_INSERT (0x1 << 19) +#define IVTV_IRQ_DMA_ERR (0x1 << 18) +#define IVTV_IRQ_DMA_WRITE (0x1 << 17) +#define IVTV_IRQ_DMA_READ (0x1 << 16) +#define IVTV_IRQ_DEC_VSYNC (0x1 << 10) + +/* IRQ Masks */ +#define IVTV_IRQ_MASK_INIT (IVTV_IRQ_DMA_ERR|IVTV_IRQ_ENC_DMA_COMPLETE|IVTV_IRQ_DMA_READ) + +#define IVTV_IRQ_MASK_CAPTURE (IVTV_IRQ_ENC_START_CAP | IVTV_IRQ_ENC_EOS) +#define IVTV_IRQ_MASK_DECODE (IVTV_IRQ_DEC_DATA_REQ|IVTV_IRQ_DEC_AUD_MODE_CHG) + +/* i2c stuff */ +#define I2C_CLIENTS_MAX 16 + +/* debugging */ + +#define IVTV_DBGFLG_WARN (1 << 0) +#define IVTV_DBGFLG_INFO (1 << 1) +#define IVTV_DBGFLG_API (1 << 2) +#define IVTV_DBGFLG_DMA (1 << 3) +#define IVTV_DBGFLG_IOCTL (1 << 4) +#define IVTV_DBGFLG_I2C (1 << 5) +#define IVTV_DBGFLG_IRQ (1 << 6) +#define IVTV_DBGFLG_DEC (1 << 7) +#define IVTV_DBGFLG_YUV (1 << 8) + +/* NOTE: extra space before comma in 'itv->num , ## args' is required for + gcc-2.95, otherwise it won't compile. */ +#define IVTV_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_debug) \ + printk(KERN_INFO "ivtv%d " type ": " fmt, itv->num , ## args); \ + } while (0) +#define IVTV_DEBUG_WARN(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_WARN, "warning", fmt , ## args) +#define IVTV_DEBUG_INFO(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_INFO, "info",fmt , ## args) +#define IVTV_DEBUG_API(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_API, "api", fmt , ## args) +#define IVTV_DEBUG_DMA(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_DMA, "dma", fmt , ## args) +#define IVTV_DEBUG_IOCTL(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define IVTV_DEBUG_I2C(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_I2C, "i2c", fmt , ## args) +#define IVTV_DEBUG_IRQ(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_IRQ, "irq", fmt , ## args) +#define IVTV_DEBUG_DEC(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_DEC, "dec", fmt , ## args) +#define IVTV_DEBUG_YUV(fmt, args...) IVTV_DEBUG(IVTV_DBGFLG_YUV, "yuv", fmt , ## args) + +#define IVTV_FB_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_debug) \ + printk(KERN_INFO "ivtv%d-fb " type ": " fmt, itv->num , ## args); \ + } while (0) +#define IVTV_FB_DEBUG_WARN(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_WARN, "warning", fmt , ## args) +#define IVTV_FB_DEBUG_INFO(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_INFO, "info", fmt , ## args) +#define IVTV_FB_DEBUG_API(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_API, "api", fmt , ## args) +#define IVTV_FB_DEBUG_DMA(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_DMA, "dma", fmt , ## args) +#define IVTV_FB_DEBUG_IOCTL(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_IOCTL, "ioctl", fmt , ## args) +#define IVTV_FB_DEBUG_I2C(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_I2C, "i2c", fmt , ## args) +#define IVTV_FB_DEBUG_IRQ(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_IRQ, "irq", fmt , ## args) +#define IVTV_FB_DEBUG_DEC(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_DEC, "dec", fmt , ## args) +#define IVTV_FB_DEBUG_YUV(fmt, args...) IVTV_FB_DEBUG(IVTV_DBGFLG_YUV, "yuv", fmt , ## args) + +/* Standard kernel messages */ +#define IVTV_ERR(fmt, args...) printk(KERN_ERR "ivtv%d: " fmt, itv->num , ## args) +#define IVTV_WARN(fmt, args...) printk(KERN_WARNING "ivtv%d: " fmt, itv->num , ## args) +#define IVTV_INFO(fmt, args...) printk(KERN_INFO "ivtv%d: " fmt, itv->num , ## args) +#define IVTV_FB_ERR(fmt, args...) printk(KERN_ERR "ivtv%d-fb: " fmt, itv->num , ## args) +#define IVTV_FB_WARN(fmt, args...) printk(KERN_WARNING "ivtv%d-fb: " fmt, itv->num , ## args) +#define IVTV_FB_INFO(fmt, args...) printk(KERN_INFO "ivtv%d-fb: " fmt, itv->num , ## args) + +/* Values for IVTV_API_DEC_PLAYBACK_SPEED mpeg_frame_type_mask parameter: */ +#define MPEG_FRAME_TYPE_IFRAME 1 +#define MPEG_FRAME_TYPE_IFRAME_PFRAME 3 +#define MPEG_FRAME_TYPE_ALL 7 + +/* output modes (cx23415 only) */ +#define OUT_NONE 0 +#define OUT_MPG 1 +#define OUT_YUV 2 +#define OUT_UDMA_YUV 3 +#define OUT_PASSTHROUGH 4 + +#define IVTV_MAX_PGM_INDEX (400) + +extern int ivtv_debug; + + +struct ivtv_options { + int megabytes[IVTV_MAX_STREAMS]; /* Size in megabytes of each stream */ + int cardtype; /* force card type on load */ + int tuner; /* set tuner on load */ + int radio; /* enable/disable radio */ + int newi2c; /* New I2C algorithm */ +}; + +#define IVTV_MBOX_DMA_START 6 +#define IVTV_MBOX_DMA_END 8 +#define IVTV_MBOX_DMA 9 +#define IVTV_MBOX_FIELD_DISPLAYED 8 + +/* ivtv-specific mailbox template */ +struct ivtv_mailbox { + u32 flags; + u32 cmd; + u32 retval; + u32 timeout; + u32 data[CX2341X_MBOX_MAX_DATA]; +}; + +struct ivtv_api_cache { + unsigned long last_jiffies; /* when last command was issued */ + u32 data[CX2341X_MBOX_MAX_DATA]; /* last sent api data */ +}; + +struct ivtv_mailbox_data { + volatile struct ivtv_mailbox __iomem *mbox; + /* Bits 0-2 are for the encoder mailboxes, 0-1 are for the decoder mailboxes. + If the bit is set, then the corresponding mailbox is in use by the driver. */ + unsigned long busy; + u8 max_mbox; +}; + +/* per-buffer bit flags */ +#define IVTV_F_B_NEED_BUF_SWAP 0 /* this buffer should be byte swapped */ + +/* per-stream, s_flags */ +#define IVTV_F_S_DMA_PENDING 0 /* this stream has pending DMA */ +#define IVTV_F_S_DMA_HAS_VBI 1 /* the current DMA request also requests VBI data */ +#define IVTV_F_S_NEEDS_DATA 2 /* this decoding stream needs more data */ + +#define IVTV_F_S_CLAIMED 3 /* this stream is claimed */ +#define IVTV_F_S_STREAMING 4 /* the fw is decoding/encoding this stream */ +#define IVTV_F_S_INTERNAL_USE 5 /* this stream is used internally (sliced VBI processing) */ +#define IVTV_F_S_PASSTHROUGH 6 /* this stream is in passthrough mode */ +#define IVTV_F_S_STREAMOFF 7 /* signal end of stream EOS */ +#define IVTV_F_S_APPL_IO 8 /* this stream is used read/written by an application */ + +/* per-ivtv, i_flags */ +#define IVTV_F_I_DMA 0 /* DMA in progress */ +#define IVTV_F_I_UDMA 1 /* UDMA in progress */ +#define IVTV_F_I_UDMA_PENDING 2 /* UDMA pending */ +#define IVTV_F_I_SPEED_CHANGE 3 /* A speed change is in progress */ +#define IVTV_F_I_EOS 4 /* End of encoder stream reached */ +#define IVTV_F_I_RADIO_USER 5 /* The radio tuner is selected */ +#define IVTV_F_I_DIG_RST 6 /* Reset digitizer */ +#define IVTV_F_I_DEC_YUV 7 /* YUV instead of MPG is being decoded */ +#define IVTV_F_I_ENC_VBI 8 /* VBI DMA */ +#define IVTV_F_I_UPDATE_CC 9 /* CC should be updated */ +#define IVTV_F_I_UPDATE_WSS 10 /* WSS should be updated */ +#define IVTV_F_I_UPDATE_VPS 11 /* VPS should be updated */ +#define IVTV_F_I_DECODING_YUV 12 /* this stream is YUV frame decoding */ +#define IVTV_F_I_ENC_PAUSED 13 /* the encoder is paused */ +#define IVTV_F_I_VALID_DEC_TIMINGS 14 /* last_dec_timing is valid */ +#define IVTV_F_I_WORK_HANDLER_VBI 15 /* there is work to be done for VBI */ +#define IVTV_F_I_WORK_HANDLER_YUV 16 /* there is work to be done for YUV */ + +/* Event notifications */ +#define IVTV_F_I_EV_DEC_STOPPED 28 /* decoder stopped event */ +#define IVTV_F_I_EV_VSYNC 29 /* VSYNC event */ +#define IVTV_F_I_EV_VSYNC_FIELD 30 /* VSYNC event field (0 = first, 1 = second field) */ +#define IVTV_F_I_EV_VSYNC_ENABLED 31 /* VSYNC event enabled */ + +/* Scatter-Gather array element, used in DMA transfers */ +struct ivtv_SG_element { + u32 src; + u32 dst; + u32 size; +}; + +struct ivtv_user_dma { + struct mutex lock; + int page_count; + struct page *map[IVTV_DMA_SG_OSD_ENT]; + + /* Base Dev SG Array for cx23415/6 */ + struct ivtv_SG_element SGarray[IVTV_DMA_SG_OSD_ENT]; + dma_addr_t SG_handle; + int SG_length; + + /* SG List of Buffers */ + struct scatterlist SGlist[IVTV_DMA_SG_OSD_ENT]; +}; + +struct ivtv_dma_page_info { + unsigned long uaddr; + unsigned long first; + unsigned long last; + unsigned int offset; + unsigned int tail; + int page_count; +}; + +struct ivtv_buffer { + struct list_head list; + dma_addr_t dma_handle; + unsigned long b_flags; + char *buf; + + u32 bytesused; + u32 readpos; +}; + +struct ivtv_queue { + struct list_head list; + u32 buffers; + u32 length; + u32 bytesused; +}; + +struct ivtv; /* forward reference */ + +struct ivtv_stream { + /* These first four fields are always set, even if the stream + is not actually created. */ + struct video_device *v4l2dev; /* NULL when stream not created */ + struct ivtv *itv; /* for ease of use */ + const char *name; /* name of the stream */ + int type; /* stream type */ + + u32 id; + spinlock_t qlock; /* locks access to the queues */ + unsigned long s_flags; /* status flags, see above */ + int dma; /* can be PCI_DMA_TODEVICE, + PCI_DMA_FROMDEVICE or + PCI_DMA_NONE */ + u32 dma_offset; + u32 dma_backup; + u64 dma_pts; + + int subtype; + wait_queue_head_t waitq; + u32 dma_last_offset; + + /* Buffer Stats */ + u32 buffers; + u32 buf_size; + u32 buffers_stolen; + + /* Buffer Queues */ + struct ivtv_queue q_free; /* free buffers */ + struct ivtv_queue q_full; /* full buffers */ + struct ivtv_queue q_io; /* waiting for I/O */ + struct ivtv_queue q_dma; /* waiting for DMA */ + struct ivtv_queue q_predma; /* waiting for DMA */ + + /* Base Dev SG Array for cx23415/6 */ + struct ivtv_SG_element *SGarray; + dma_addr_t SG_handle; + int SG_length; + + /* SG List of Buffers */ + struct scatterlist *SGlist; +}; + +struct ivtv_open_id { + u32 open_id; + int type; + enum v4l2_priority prio; + struct ivtv *itv; +}; + +#define IVTV_YUV_UPDATE_HORIZONTAL 0x01 +#define IVTV_YUV_UPDATE_VERTICAL 0x02 + +struct yuv_frame_info +{ + u32 update; + int src_x; + int src_y; + unsigned int src_w; + unsigned int src_h; + int dst_x; + int dst_y; + unsigned int dst_w; + unsigned int dst_h; + int pan_x; + int pan_y; + u32 vis_w; + u32 vis_h; + u32 interlaced_y; + u32 interlaced_uv; + int tru_x; + u32 tru_w; + u32 tru_h; + u32 offset_y; +}; + +#define IVTV_YUV_MODE_INTERLACED 0x00 +#define IVTV_YUV_MODE_PROGRESSIVE 0x01 +#define IVTV_YUV_MODE_AUTO 0x02 +#define IVTV_YUV_MODE_MASK 0x03 + +#define IVTV_YUV_SYNC_EVEN 0x00 +#define IVTV_YUV_SYNC_ODD 0x04 +#define IVTV_YUV_SYNC_MASK 0x04 + +struct yuv_playback_info +{ + u32 reg_2834; + u32 reg_2838; + u32 reg_283c; + u32 reg_2840; + u32 reg_2844; + u32 reg_2848; + u32 reg_2854; + u32 reg_285c; + u32 reg_2864; + + u32 reg_2870; + u32 reg_2874; + u32 reg_2890; + u32 reg_2898; + u32 reg_289c; + + u32 reg_2918; + u32 reg_291c; + u32 reg_2920; + u32 reg_2924; + u32 reg_2928; + u32 reg_292c; + u32 reg_2930; + + u32 reg_2934; + + u32 reg_2938; + u32 reg_293c; + u32 reg_2940; + u32 reg_2944; + u32 reg_2948; + u32 reg_294c; + u32 reg_2950; + u32 reg_2954; + u32 reg_2958; + u32 reg_295c; + u32 reg_2960; + u32 reg_2964; + u32 reg_2968; + u32 reg_296c; + + u32 reg_2970; + + int v_filter_1; + int v_filter_2; + int h_filter; + + u32 osd_x_offset; + u32 osd_y_offset; + + u32 osd_x_pan; + u32 osd_y_pan; + + u32 osd_vis_w; + u32 osd_vis_h; + + int decode_height; + + int frame_interlaced; + int frame_interlaced_last; + + int lace_mode; + int lace_threshold; + int lace_sync_field; + + atomic_t next_dma_frame; + atomic_t next_fill_frame; + + u32 yuv_forced_update; + int update_frame; + struct yuv_frame_info new_frame_info[4]; + struct yuv_frame_info old_frame_info; + struct yuv_frame_info old_frame_info_args; + + void *blanking_ptr; + dma_addr_t blanking_dmaptr; +}; + +#define IVTV_VBI_FRAMES 32 + +/* VBI data */ +struct vbi_info { + u32 dec_start; + u32 enc_start, enc_size; + int fpi; + u32 frame; + u32 dma_offset; + u8 cc_data_odd[256]; + u8 cc_data_even[256]; + int cc_pos; + u8 cc_no_update; + u8 vps[5]; + u8 vps_found; + int wss; + u8 wss_found; + u8 wss_no_update; + u32 raw_decoder_line_size; + u8 raw_decoder_sav_odd_field; + u8 raw_decoder_sav_even_field; + u32 sliced_decoder_line_size; + u8 sliced_decoder_sav_odd_field; + u8 sliced_decoder_sav_even_field; + struct v4l2_format in; + /* convenience pointer to sliced struct in vbi_in union */ + struct v4l2_sliced_vbi_format *sliced_in; + u32 service_set_in; + u32 service_set_out; + int insert_mpeg; + + /* Buffer for the maximum of 2 * 18 * packet_size sliced VBI lines. + One for /dev/vbi0 and one for /dev/vbi8 */ + struct v4l2_sliced_vbi_data sliced_data[36]; + struct v4l2_sliced_vbi_data sliced_dec_data[36]; + + /* Buffer for VBI data inserted into MPEG stream. + The first byte is a dummy byte that's never used. + The next 16 bytes contain the MPEG header for the VBI data, + the remainder is the actual VBI data. + The max size accepted by the MPEG VBI reinsertion turns out + to be 1552 bytes, which happens to be 4 + (1 + 42) * (2 * 18) bytes, + where 4 is a four byte header, 42 is the max sliced VBI payload, 1 is + a single line header byte and 2 * 18 is the number of VBI lines per frame. + + However, it seems that the data must be 1K aligned, so we have to + pad the data until the 1 or 2 K boundary. + + This pointer array will allocate 2049 bytes to store each VBI frame. */ + u8 *sliced_mpeg_data[IVTV_VBI_FRAMES]; + u32 sliced_mpeg_size[IVTV_VBI_FRAMES]; + struct ivtv_buffer sliced_mpeg_buf; + u32 inserted_frame; + + u32 start[2], count; + u32 raw_size; + u32 sliced_size; +}; + +/* forward declaration of struct defined in ivtv-cards.h */ +struct ivtv_card; + +/* Struct to hold info about ivtv cards */ +struct ivtv { + int num; /* board number, -1 during init! */ + char name[8]; /* board name for printk and interrupts (e.g. 'ivtv0') */ + struct pci_dev *dev; /* PCI device */ + const struct ivtv_card *card; /* card information */ + const char *card_name; /* full name of the card */ + u8 has_cx23415; /* 1 if it is a cx23415 based card, 0 for cx23416 */ + u8 is_50hz; + u8 is_60hz; + u8 is_out_50hz; + u8 is_out_60hz; + u8 pvr150_workaround; /* 1 if the cx25840 needs to workaround a PVR150 bug */ + u8 nof_inputs; /* number of video inputs */ + u8 nof_audio_inputs; /* number of audio inputs */ + u32 v4l2_cap; /* V4L2 capabilities of card */ + u32 hw_flags; /* Hardware description of the board */ + + /* controlling Video decoder function */ + int (*video_dec_func)(struct ivtv *, unsigned int, void *); + + struct ivtv_options options; /* User options */ + int stream_buf_size[IVTV_MAX_STREAMS]; /* Stream buffer size */ + struct ivtv_stream streams[IVTV_MAX_STREAMS]; /* Stream data */ + int speed; + u8 speed_mute_audio; + unsigned long i_flags; /* global ivtv flags */ + atomic_t capturing; /* count number of active capture streams */ + atomic_t decoding; /* count number of active decoding streams */ + u32 irq_rr_idx; /* Round-robin stream index */ + int cur_dma_stream; /* index of stream doing DMA */ + u32 dma_data_req_offset; + u32 dma_data_req_size; + int output_mode; /* NONE, MPG, YUV, UDMA YUV, passthrough */ + spinlock_t lock; /* lock access to this struct */ + int search_pack_header; + + spinlock_t dma_reg_lock; /* lock access to DMA engine registers */ + + /* User based DMA for OSD */ + struct ivtv_user_dma udma; + + int open_id; /* incremented each time an open occurs, used as unique ID. + starts at 1, so 0 can be used as uninitialized value + in the stream->id. */ + + u32 base_addr; + u32 irqmask; + + struct v4l2_prio_state prio; + struct workqueue_struct *irq_work_queues; + struct work_struct irq_work_queue; + struct timer_list dma_timer; /* Timer used to catch unfinished DMAs */ + + struct vbi_info vbi; + + struct ivtv_mailbox_data enc_mbox; + struct ivtv_mailbox_data dec_mbox; + struct ivtv_api_cache api_cache[256]; /* Cached API Commands */ + + u8 card_rev; + volatile void __iomem *enc_mem, *dec_mem, *reg_mem; + + u32 pgm_info_offset; + u32 pgm_info_num; + u32 pgm_info_write_idx; + u32 pgm_info_read_idx; + struct v4l2_enc_idx_entry pgm_info[IVTV_MAX_PGM_INDEX]; + + u64 mpg_data_received; + u64 vbi_data_inserted; + + wait_queue_head_t cap_w; + /* when the next decoder event arrives this queue is woken up */ + wait_queue_head_t event_waitq; + /* when the next decoder vsync arrives this queue is woken up */ + wait_queue_head_t vsync_waitq; + /* when the current DMA is finished this queue is woken up */ + wait_queue_head_t dma_waitq; + + /* OSD support */ + unsigned long osd_video_pbase; + int osd_global_alpha_state; /* 0=off : 1=on */ + int osd_local_alpha_state; /* 0=off : 1=on */ + int osd_color_key_state; /* 0=off : 1=on */ + u8 osd_global_alpha; /* Current global alpha */ + u32 osd_color_key; /* Current color key */ + u32 osd_pixelformat; /* Current pixel format */ + struct v4l2_rect osd_rect; /* Current OSD position and size */ + struct v4l2_rect main_rect; /* Current Main window position and size */ + + u32 last_dec_timing[3]; /* Store last retrieved pts/scr/frame values */ + + /* i2c */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + struct mutex i2c_bus_lock; + int i2c_state; + struct i2c_client *i2c_clients[I2C_CLIENTS_MAX]; + + /* v4l2 and User settings */ + + /* codec settings */ + struct cx2341x_mpeg_params params; + u32 audio_input; + u32 active_input; + u32 active_output; + v4l2_std_id std; + v4l2_std_id std_out; + v4l2_std_id tuner_std; /* The norm of the tuner (fixed) */ + u8 audio_stereo_mode; + u8 audio_bilingual_mode; + + /* dualwatch */ + unsigned long dualwatch_jiffies; + u16 dualwatch_stereo_mode; + + /* Digitizer type */ + int digitizer; /* 0x00EF = saa7114 0x00FO = saa7115 0x0106 = mic */ + + u32 lastVsyncFrame; + + struct yuv_playback_info yuv_info; + struct osd_info *osd_info; +}; + +/* Globals */ +extern struct ivtv *ivtv_cards[]; +extern int ivtv_cards_active; +extern int ivtv_first_minor; +extern spinlock_t ivtv_cards_lock; + +/*==============Prototypes==================*/ + +/* Hardware/IRQ */ +void ivtv_set_irq_mask(struct ivtv *itv, u32 mask); +void ivtv_clear_irq_mask(struct ivtv *itv, u32 mask); + +/* try to set output mode, return current mode. */ +int ivtv_set_output_mode(struct ivtv *itv, int mode); + +/* return current output stream based on current mode */ +struct ivtv_stream *ivtv_get_output_stream(struct ivtv *itv); + +/* Return non-zero if a signal is pending */ +int ivtv_sleep_timeout(int timeout, int intr); + +/* Wait on queue, returns -EINTR if interrupted */ +int ivtv_waitq(wait_queue_head_t *waitq); + +/* Read Hauppauge eeprom */ +struct tveeprom; /* forward reference */ +void ivtv_read_eeprom(struct ivtv *itv, struct tveeprom *tv); + +/* This is a PCI post thing, where if the pci register is not read, then + the write doesn't always take effect right away. By reading back the + register any pending PCI writes will be performed (in order), and so + you can be sure that the writes are guaranteed to be done. + + Rarely needed, only in some timing sensitive cases. + Apparently if this is not done some motherboards seem + to kill the firmware and get into the broken state until computer is + rebooted. */ +#define write_sync(val, reg) \ + do { writel(val, reg); readl(reg); } while (0) + +#define read_reg(reg) readl(itv->reg_mem + (reg)) +#define write_reg(val, reg) writel(val, itv->reg_mem + (reg)) +#define write_reg_sync(val, reg) \ + do { write_reg(val, reg); read_reg(reg); } while (0) + +#define read_enc(addr) readl(itv->enc_mem + (u32)(addr)) +#define write_enc(val, addr) writel(val, itv->enc_mem + (u32)(addr)) +#define write_enc_sync(val, addr) \ + do { write_enc(val, addr); read_enc(addr); } while (0) + +#define read_dec(addr) readl(itv->dec_mem + (u32)(addr)) +#define write_dec(val, addr) writel(val, itv->dec_mem + (u32)(addr)) +#define write_dec_sync(val, addr) \ + do { write_dec(val, addr); read_dec(addr); } while (0) + +#endif /* IVTV_DRIVER_H */ diff --git a/drivers/media/video/ivtv/ivtv-fileops.c b/drivers/media/video/ivtv/ivtv-fileops.c new file mode 100644 index 00000000000..1637097ddec --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-fileops.c @@ -0,0 +1,921 @@ +/* + file operation functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-vbi.h" +#include "ivtv-mailbox.h" +#include "ivtv-audio.h" +#include "ivtv-streams.h" +#include "ivtv-yuv.h" +#include "ivtv-controls.h" +#include "ivtv-ioctl.h" + +/* This function tries to claim the stream for a specific file descriptor. + If no one else is using this stream then the stream is claimed and + associated VBI streams are also automatically claimed. + Possible error returns: -EBUSY if someone else has claimed + the stream or 0 on success. */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[type]; + struct ivtv_stream *s_vbi; + int vbi_type; + + if (test_and_set_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + /* someone already claimed this stream */ + if (s->id == id->open_id) { + /* yes, this file descriptor did. So that's OK. */ + return 0; + } + if (s->id == -1 && (type == IVTV_DEC_STREAM_TYPE_VBI || + type == IVTV_ENC_STREAM_TYPE_VBI)) { + /* VBI is handled already internally, now also assign + the file descriptor to this stream for external + reading of the stream. */ + s->id = id->open_id; + IVTV_DEBUG_INFO("Start Read VBI\n"); + return 0; + } + /* someone else is using this stream already */ + IVTV_DEBUG_INFO("Stream %d is busy\n", type); + return -EBUSY; + } + s->id = id->open_id; + if (type == IVTV_DEC_STREAM_TYPE_VBI) { + /* Enable reinsertion interrupt */ + ivtv_clear_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + } + + /* IVTV_DEC_STREAM_TYPE_MPG needs to claim IVTV_DEC_STREAM_TYPE_VBI, + IVTV_ENC_STREAM_TYPE_MPG needs to claim IVTV_ENC_STREAM_TYPE_VBI + (provided VBI insertion is on and sliced VBI is selected), for all + other streams we're done */ + if (type == IVTV_DEC_STREAM_TYPE_MPG) { + vbi_type = IVTV_DEC_STREAM_TYPE_VBI; + } else if (type == IVTV_ENC_STREAM_TYPE_MPG && + itv->vbi.insert_mpeg && itv->vbi.sliced_in->service_set) { + vbi_type = IVTV_ENC_STREAM_TYPE_VBI; + } else { + return 0; + } + s_vbi = &itv->streams[vbi_type]; + + if (!test_and_set_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags)) { + /* Enable reinsertion interrupt */ + if (vbi_type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_clear_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + } + /* mark that it is used internally */ + set_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags); + return 0; +} + +/* This function releases a previously claimed stream. It will take into + account associated VBI streams. */ +void ivtv_release_stream(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi; + + s->id = -1; + if ((s->type == IVTV_DEC_STREAM_TYPE_VBI || s->type == IVTV_ENC_STREAM_TYPE_VBI) && + test_bit(IVTV_F_S_INTERNAL_USE, &s->s_flags)) { + /* this stream is still in use internally */ + return; + } + if (!test_and_clear_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + IVTV_DEBUG_WARN("Release stream %s not in use!\n", s->name); + return; + } + + ivtv_flush_queues(s); + + /* disable reinsertion interrupt */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + + /* IVTV_DEC_STREAM_TYPE_MPG needs to release IVTV_DEC_STREAM_TYPE_VBI, + IVTV_ENC_STREAM_TYPE_MPG needs to release IVTV_ENC_STREAM_TYPE_VBI, + for all other streams we're done */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + s_vbi = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI]; + else if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + else + return; + + /* clear internal use flag */ + if (!test_and_clear_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags)) { + /* was already cleared */ + return; + } + if (s_vbi->id != -1) { + /* VBI stream still claimed by a file descriptor */ + return; + } + /* disable reinsertion interrupt */ + if (s_vbi->type == IVTV_DEC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_DEC_VBI_RE_INSERT); + clear_bit(IVTV_F_S_CLAIMED, &s_vbi->s_flags); + ivtv_flush_queues(s_vbi); +} + +static void ivtv_dualwatch(struct ivtv *itv) +{ + struct v4l2_tuner vt; + u16 new_bitmap; + u16 new_stereo_mode; + const u16 stereo_mask = 0x0300; + const u16 dual = 0x0200; + + new_stereo_mode = itv->params.audio_properties & stereo_mask; + memset(&vt, 0, sizeof(vt)); + ivtv_call_i2c_clients(itv, VIDIOC_G_TUNER, &vt); + if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 && (vt.rxsubchans & V4L2_TUNER_SUB_LANG2)) + new_stereo_mode = dual; + + if (new_stereo_mode == itv->dualwatch_stereo_mode) + return; + + new_bitmap = new_stereo_mode | (itv->params.audio_properties & ~stereo_mask); + + IVTV_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x. new audio_bitmask=0x%ux\n", + itv->dualwatch_stereo_mode, new_stereo_mode, new_bitmap); + + if (ivtv_vapi(itv, CX2341X_ENC_SET_AUDIO_PROPERTIES, 1, new_bitmap) == 0) { + itv->dualwatch_stereo_mode = new_stereo_mode; + return; + } + IVTV_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); +} + +static void ivtv_update_pgm_info(struct ivtv *itv) +{ + u32 wr_idx = (read_enc(itv->pgm_info_offset) - itv->pgm_info_offset - 4) / 24; + int cnt; + int i = 0; + + if (wr_idx >= itv->pgm_info_num) { + IVTV_DEBUG_WARN("Invalid PGM index %d (>= %d)\n", wr_idx, itv->pgm_info_num); + return; + } + cnt = (wr_idx + itv->pgm_info_num - itv->pgm_info_write_idx) % itv->pgm_info_num; + while (i < cnt) { + int idx = (itv->pgm_info_write_idx + i) % itv->pgm_info_num; + struct v4l2_enc_idx_entry *e = itv->pgm_info + idx; + u32 addr = itv->pgm_info_offset + 4 + idx * 24; + const int mapping[] = { V4L2_ENC_IDX_FRAME_P, V4L2_ENC_IDX_FRAME_I, V4L2_ENC_IDX_FRAME_B, 0 }; + + e->offset = read_enc(addr + 4) + ((u64)read_enc(addr + 8) << 32); + if (e->offset > itv->mpg_data_received) { + break; + } + e->offset += itv->vbi_data_inserted; + e->length = read_enc(addr); + e->pts = read_enc(addr + 16) + ((u64)(read_enc(addr + 20) & 1) << 32); + e->flags = mapping[read_enc(addr + 12) & 3]; + i++; + } + itv->pgm_info_write_idx = (itv->pgm_info_write_idx + i) % itv->pgm_info_num; +} + +static struct ivtv_buffer *ivtv_get_buffer(struct ivtv_stream *s, int non_block, int *err) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + struct ivtv_buffer *buf; + DEFINE_WAIT(wait); + + *err = 0; + while (1) { + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) { + /* Process pending program info updates and pending VBI data */ + ivtv_update_pgm_info(itv); + + if (jiffies - itv->dualwatch_jiffies > HZ) { + itv->dualwatch_jiffies = jiffies; + ivtv_dualwatch(itv); + } + + if (test_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_bit(IVTV_F_S_APPL_IO, &s_vbi->s_flags)) { + while ((buf = ivtv_dequeue(s_vbi, &s_vbi->q_full))) { + /* byteswap and process VBI data */ + ivtv_process_vbi_data(itv, buf, s_vbi->dma_pts, s_vbi->type); + ivtv_enqueue(s_vbi, buf, &s_vbi->q_free); + } + } + buf = &itv->vbi.sliced_mpeg_buf; + if (buf->readpos != buf->bytesused) { + return buf; + } + } + + /* do we have leftover data? */ + buf = ivtv_dequeue(s, &s->q_io); + if (buf) + return buf; + + /* do we have new data? */ + buf = ivtv_dequeue(s, &s->q_full); + if (buf) { + if (!test_and_clear_bit(IVTV_F_B_NEED_BUF_SWAP, &buf->b_flags)) + return buf; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG) + /* byteswap MPG data */ + ivtv_buf_swap(buf); + else if (s->type != IVTV_DEC_STREAM_TYPE_VBI) { + /* byteswap and process VBI data */ + ivtv_process_vbi_data(itv, buf, s->dma_pts, s->type); + } + return buf; + } + /* return if file was opened with O_NONBLOCK */ + if (non_block) { + *err = -EAGAIN; + return NULL; + } + + /* return if end of stream */ + if (s->type != IVTV_DEC_STREAM_TYPE_VBI && !test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + IVTV_DEBUG_INFO("EOS %s\n", s->name); + return NULL; + } + + /* wait for more data to arrive */ + prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE); + /* New buffers might have become available before we were added to the waitqueue */ + if (!s->q_full.buffers) + schedule(); + finish_wait(&s->waitq, &wait); + if (signal_pending(current)) { + /* return if a signal was received */ + IVTV_DEBUG_INFO("User stopped %s\n", s->name); + *err = -EINTR; + return NULL; + } + } +} + +static void ivtv_setup_sliced_vbi_buf(struct ivtv *itv) +{ + int idx = itv->vbi.inserted_frame % IVTV_VBI_FRAMES; + + itv->vbi.sliced_mpeg_buf.buf = itv->vbi.sliced_mpeg_data[idx]; + itv->vbi.sliced_mpeg_buf.bytesused = itv->vbi.sliced_mpeg_size[idx]; + itv->vbi.sliced_mpeg_buf.readpos = 0; +} + +static size_t ivtv_copy_buf_to_user(struct ivtv_stream *s, struct ivtv_buffer *buf, + char __user *ubuf, size_t ucount) +{ + struct ivtv *itv = s->itv; + size_t len = buf->bytesused - buf->readpos; + + if (len > ucount) len = ucount; + if (itv->vbi.insert_mpeg && s->type == IVTV_ENC_STREAM_TYPE_MPG && + itv->vbi.sliced_in->service_set && buf != &itv->vbi.sliced_mpeg_buf) { + const char *start = buf->buf + buf->readpos; + const char *p = start + 1; + const u8 *q; + u8 ch = itv->search_pack_header ? 0xba : 0xe0; + int stuffing, i; + + while (start + len > p && (q = memchr(p, 0, start + len - p))) { + p = q + 1; + if ((char *)q + 15 >= buf->buf + buf->bytesused || + q[1] != 0 || q[2] != 1 || q[3] != ch) { + continue; + } + if (!itv->search_pack_header) { + if ((q[6] & 0xc0) != 0x80) + continue; + if (((q[7] & 0xc0) == 0x80 && (q[9] & 0xf0) == 0x20) || + ((q[7] & 0xc0) == 0xc0 && (q[9] & 0xf0) == 0x30)) { + ch = 0xba; + itv->search_pack_header = 1; + p = q + 9; + } + continue; + } + stuffing = q[13] & 7; + /* all stuffing bytes must be 0xff */ + for (i = 0; i < stuffing; i++) + if (q[14 + i] != 0xff) + break; + if (i == stuffing && (q[4] & 0xc4) == 0x44 && (q[12] & 3) == 3 && + q[14 + stuffing] == 0 && q[15 + stuffing] == 0 && + q[16 + stuffing] == 1) { + itv->search_pack_header = 0; + len = (char *)q - start; + ivtv_setup_sliced_vbi_buf(itv); + break; + } + } + } + if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) { + IVTV_DEBUG_WARN("copy %zd bytes to user failed for %s\n", len, s->name); + return -EFAULT; + } + /*IVTV_INFO("copied %lld %d %d %d %d %d vbi %d\n", itv->mpg_data_received, len, ucount, + buf->readpos, buf->bytesused, buf->bytesused - buf->readpos - len, + buf == &itv->vbi.sliced_mpeg_buf); */ + buf->readpos += len; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && buf != &itv->vbi.sliced_mpeg_buf) + itv->mpg_data_received += len; + return len; +} + +static ssize_t ivtv_read(struct ivtv_stream *s, char __user *ubuf, size_t tot_count, int non_block) +{ + struct ivtv *itv = s->itv; + size_t tot_written = 0; + int single_frame = 0; + + if (atomic_read(&itv->capturing) == 0 && s->id == -1) { + /* shouldn't happen */ + IVTV_DEBUG_WARN("Stream %s not initialized before read\n", s->name); + return -EIO; + } + + /* Each VBI buffer is one frame, the v4l2 API says that for VBI the frames should + arrive one-by-one, so make sure we never output more than one VBI frame at a time */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI || + (s->type == IVTV_ENC_STREAM_TYPE_VBI && itv->vbi.sliced_in->service_set)) + single_frame = 1; + + for (;;) { + struct ivtv_buffer *buf; + int rc; + + buf = ivtv_get_buffer(s, non_block, &rc); + if (buf == NULL && rc == -EAGAIN && tot_written) + break; + if (buf == NULL) + return rc; + rc = ivtv_copy_buf_to_user(s, buf, ubuf + tot_written, tot_count - tot_written); + if (buf != &itv->vbi.sliced_mpeg_buf) { + ivtv_enqueue(s, buf, (buf->readpos == buf->bytesused) ? &s->q_free : &s->q_io); + } + else if (buf->readpos == buf->bytesused) { + int idx = itv->vbi.inserted_frame % IVTV_VBI_FRAMES; + itv->vbi.sliced_mpeg_size[idx] = 0; + itv->vbi.inserted_frame++; + itv->vbi_data_inserted += buf->bytesused; + } + if (rc < 0) + return rc; + tot_written += rc; + + if (tot_written == tot_count || single_frame) + break; + } + return tot_written; +} + +static ssize_t ivtv_read_pos(struct ivtv_stream *s, char __user *ubuf, size_t count, + loff_t *pos, int non_block) +{ + ssize_t rc = count ? ivtv_read(s, ubuf, count, non_block) : 0; + struct ivtv *itv = s->itv; + + IVTV_DEBUG_INFO("read %zd from %s, got %zd\n", count, s->name, rc); + if (rc > 0) + pos += rc; + return rc; +} + +int ivtv_start_capture(struct ivtv_open_id *id) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + struct ivtv_stream *s_vbi; + + if (s->type == IVTV_ENC_STREAM_TYPE_RAD || + s->type == IVTV_DEC_STREAM_TYPE_MPG || + s->type == IVTV_DEC_STREAM_TYPE_YUV || + s->type == IVTV_DEC_STREAM_TYPE_VOUT) { + /* you cannot read from these stream types. */ + return -EPERM; + } + + /* Try to claim this stream. */ + if (ivtv_claim_stream(id, s->type)) + return -EBUSY; + + /* This stream does not need to start capturing */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* If capture is already in progress, then we also have to + do nothing extra. */ + if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return 0; + } + + /* Start VBI capture if required */ + s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_INTERNAL_USE, &s_vbi->s_flags) && + !test_and_set_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags)) { + /* Note: the IVTV_ENC_STREAM_TYPE_VBI is claimed + automatically when the MPG stream is claimed. + We only need to start the VBI capturing. */ + if (ivtv_start_v4l2_encode_stream(s_vbi)) { + IVTV_DEBUG_WARN("VBI capture start failed\n"); + + /* Failure, clean up and return an error */ + clear_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + /* also releases the associated VBI stream */ + ivtv_release_stream(s); + return -EIO; + } + IVTV_DEBUG_INFO("VBI insertion started\n"); + } + + /* Tell the card to start capturing */ + if (!ivtv_start_v4l2_encode_stream(s)) { + /* We're done */ + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + /* Resume a possibly paused encoder */ + if (test_and_clear_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 1); + return 0; + } + + /* failure, clean up */ + IVTV_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name); + + /* Note: the IVTV_ENC_STREAM_TYPE_VBI is released + automatically when the MPG stream is released. + We only need to stop the VBI capturing. */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags)) { + ivtv_stop_v4l2_encode_stream(s_vbi, 0); + clear_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags); + } + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + ivtv_release_stream(s); + return -EIO; +} + +ssize_t ivtv_v4l2_read(struct file * filp, char __user *buf, size_t count, loff_t * pos) +{ + struct ivtv_open_id *id = filp->private_data; + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int rc; + + IVTV_DEBUG_IOCTL("read %zd bytes from %s\n", count, s->name); + + rc = ivtv_start_capture(id); + if (rc) + return rc; + return ivtv_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK); +} + +int ivtv_start_decoding(struct ivtv_open_id *id, int speed) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + if (atomic_read(&itv->decoding) == 0) { + if (ivtv_claim_stream(id, s->type)) { + /* someone else is using this stream already */ + IVTV_DEBUG_WARN("start decode, stream already claimed\n"); + return -EBUSY; + } + ivtv_start_v4l2_decode_stream(s, 0); + } + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + return ivtv_set_speed(itv, speed); + return 0; +} + +ssize_t ivtv_v4l2_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *pos) +{ + struct ivtv_open_id *id = filp->private_data; + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + struct ivtv_buffer *buf; + struct ivtv_queue q; + int bytes_written = 0; + int mode; + int rc; + DEFINE_WAIT(wait); + + IVTV_DEBUG_IOCTL("write %zd bytes to %s\n", count, s->name); + + if (s->type != IVTV_DEC_STREAM_TYPE_MPG && + s->type != IVTV_DEC_STREAM_TYPE_YUV && + s->type != IVTV_DEC_STREAM_TYPE_VOUT) + /* not decoder streams */ + return -EPERM; + + /* Try to claim this stream */ + if (ivtv_claim_stream(id, s->type)) + return -EBUSY; + + /* This stream does not need to start any decoding */ + if (s->type == IVTV_DEC_STREAM_TYPE_VOUT) { + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return ivtv_write_vbi(itv, user_buf, count); + } + + mode = s->type == IVTV_DEC_STREAM_TYPE_MPG ? OUT_MPG : OUT_YUV; + + if (ivtv_set_output_mode(itv, mode) != mode) { + ivtv_release_stream(s); + return -EBUSY; + } + ivtv_queue_init(&q); + set_bit(IVTV_F_S_APPL_IO, &s->s_flags); + +retry: + for (;;) { + /* Gather buffers */ + while (q.length - q.bytesused < count && (buf = ivtv_dequeue(s, &s->q_io))) + ivtv_enqueue(s, buf, &q); + while (q.length - q.bytesused < count && (buf = ivtv_dequeue(s, &s->q_free))) { + ivtv_enqueue(s, buf, &q); + } + if (q.buffers) + break; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE); + /* New buffers might have become free before we were added to the waitqueue */ + if (!s->q_free.buffers) + schedule(); + finish_wait(&s->waitq, &wait); + if (signal_pending(current)) { + IVTV_DEBUG_INFO("User stopped %s\n", s->name); + return -EINTR; + } + } + + /* copy user data into buffers */ + while ((buf = ivtv_dequeue(s, &q))) { + /* Make sure we really got all the user data */ + rc = ivtv_buf_copy_from_user(s, buf, user_buf, count); + + if (rc < 0) { + ivtv_queue_move(s, &q, NULL, &s->q_free, 0); + return rc; + } + user_buf += rc; + count -= rc; + bytes_written += rc; + + if (buf->bytesused != s->buf_size) { + /* incomplete, leave in q_io for next time */ + ivtv_enqueue(s, buf, &s->q_io); + break; + } + /* Byteswap MPEG buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_MPG) + ivtv_buf_swap(buf); + ivtv_enqueue(s, buf, &s->q_full); + } + + /* Start decoder (returns 0 if already started) */ + rc = ivtv_start_decoding(id, itv->speed); + if (rc) { + IVTV_DEBUG_WARN("Failed start decode stream %s\n", s->name); + + /* failure, clean up */ + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + return rc; + } + if (test_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags)) { + if (s->q_full.length >= itv->dma_data_req_size) { + int got_sig; + + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + while (!(got_sig = signal_pending(current)) && + test_bit(IVTV_F_S_DMA_PENDING, &s->s_flags)) { + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + if (got_sig) { + IVTV_DEBUG_INFO("User interrupted %s\n", s->name); + return -EINTR; + } + + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_predma, itv->dma_data_req_size); + ivtv_dma_stream_dec_prepare(s, itv->dma_data_req_offset + IVTV_DECODER_OFFSET, 1); + } + } + /* more user data is available, wait until buffers become free + to transfer the rest. */ + if (count && !(filp->f_flags & O_NONBLOCK)) + goto retry; + IVTV_DEBUG_INFO("Wrote %d bytes to %s (%d)\n", bytes_written, s->name, s->q_full.bytesused); + return bytes_written; +} + +unsigned int ivtv_v4l2_dec_poll(struct file *filp, poll_table *wait) +{ + struct ivtv_open_id *id = filp->private_data; + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int res = 0; + + /* add stream's waitq to the poll list */ + poll_wait(filp, &s->waitq, wait); + + set_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + if (test_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags) || + test_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags)) + res = POLLPRI; + + /* Allow write if buffers are available for writing */ + if (s->q_free.buffers) + res |= POLLOUT | POLLWRNORM; + return res; +} + +unsigned int ivtv_v4l2_enc_poll(struct file *filp, poll_table * wait) +{ + struct ivtv_open_id *id = filp->private_data; + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + int eof = test_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + /* Start a capture if there is none */ + if (!eof && !test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + int rc = ivtv_start_capture(id); + + if (rc) { + IVTV_DEBUG_INFO("Could not start capture for %s (%d)\n", + s->name, rc); + return POLLERR; + } + } + + /* add stream's waitq to the poll list */ + poll_wait(filp, &s->waitq, wait); + + if (eof || s->q_full.length) + return POLLIN | POLLRDNORM; + return 0; +} + +void ivtv_stop_capture(struct ivtv_open_id *id, int gop_end) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_IOCTL("close() of %s\n", s->name); + + /* 'Unclaim' this stream */ + + /* Stop capturing */ + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + + IVTV_DEBUG_INFO("close stopping capture\n"); + /* Special case: a running VBI capture for VBI insertion + in the mpeg stream. Need to stop that too. */ + if (id->type == IVTV_ENC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_STREAMING, &s_vbi->s_flags) && + !test_bit(IVTV_F_S_APPL_IO, &s_vbi->s_flags)) { + IVTV_DEBUG_INFO("close stopping embedded VBI capture\n"); + ivtv_stop_v4l2_encode_stream(s_vbi, 0); + } + if ((id->type == IVTV_DEC_STREAM_TYPE_VBI || + id->type == IVTV_ENC_STREAM_TYPE_VBI) && + test_bit(IVTV_F_S_INTERNAL_USE, &s->s_flags)) { + /* Also used internally, don't stop capturing */ + s->id = -1; + } + else { + ivtv_stop_v4l2_encode_stream(s, gop_end); + } + } + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + ivtv_release_stream(s); +} + +static void ivtv_stop_decoding(struct ivtv_open_id *id, int flags, u64 pts) +{ + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_IOCTL("close() of %s\n", s->name); + + /* Stop decoding */ + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + IVTV_DEBUG_INFO("close stopping decode\n"); + + ivtv_stop_v4l2_decode_stream(s, flags, pts); + } + clear_bit(IVTV_F_S_APPL_IO, &s->s_flags); + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + if (id->type == IVTV_DEC_STREAM_TYPE_YUV && test_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags)) { + /* Restore registers we've changed & clean up any mess we've made */ + ivtv_yuv_close(itv); + } + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && itv->output_mode == OUT_YUV) + itv->output_mode = OUT_NONE; + else if (s->type == IVTV_DEC_STREAM_TYPE_MPG && itv->output_mode == OUT_MPG) + itv->output_mode = OUT_NONE; + + itv->speed = 0; + ivtv_release_stream(s); +} + +int ivtv_v4l2_close(struct inode *inode, struct file *filp) +{ + struct ivtv_open_id *id = filp->private_data; + struct ivtv *itv = id->itv; + struct ivtv_stream *s = &itv->streams[id->type]; + + IVTV_DEBUG_IOCTL("close() of %s\n", s->name); + + v4l2_prio_close(&itv->prio, &id->prio); + + /* Easy case first: this stream was never claimed by us */ + if (s->id != id->open_id) { + kfree(id); + return 0; + } + + /* 'Unclaim' this stream */ + + /* Stop radio */ + if (id->type == IVTV_ENC_STREAM_TYPE_RAD) { + /* Closing radio device, return to TV mode */ + ivtv_mute(itv); + /* Mark that the radio is no longer in use */ + clear_bit(IVTV_F_I_RADIO_USER, &itv->i_flags); + /* Switch tuner to TV */ + ivtv_call_i2c_clients(itv, VIDIOC_S_STD, &itv->std); + /* Select correct audio input (i.e. TV tuner or Line in) */ + ivtv_audio_set_io(itv); + /* Done! Unmute and continue. */ + ivtv_unmute(itv); + ivtv_release_stream(s); + } else if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) { + ivtv_stop_decoding(id, VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY, 0); + } else { + ivtv_stop_capture(id, 0); + } + kfree(id); + return 0; +} + +int ivtv_v4l2_open(struct inode *inode, struct file *filp) +{ + int x, y = 0; + struct ivtv_open_id *item; + struct ivtv *itv = NULL; + struct ivtv_stream *s = NULL; + int minor = MINOR(inode->i_rdev); + + /* Find which card this open was on */ + spin_lock(&ivtv_cards_lock); + for (x = 0; itv == NULL && x < ivtv_cards_active; x++) { + /* find out which stream this open was on */ + for (y = 0; y < IVTV_MAX_STREAMS; y++) { + s = &ivtv_cards[x]->streams[y]; + if (s->v4l2dev && s->v4l2dev->minor == minor) { + itv = ivtv_cards[x]; + break; + } + } + } + spin_unlock(&ivtv_cards_lock); + + if (itv == NULL) { + /* Couldn't find a device registered + on that minor, shouldn't happen! */ + printk(KERN_WARNING "ivtv: no ivtv device found on minor %d\n", minor); + return -ENXIO; + } + + if (y == IVTV_DEC_STREAM_TYPE_MPG && + test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_YUV].s_flags)) + return -EBUSY; + + if (y == IVTV_DEC_STREAM_TYPE_YUV && + test_bit(IVTV_F_S_CLAIMED, &itv->streams[IVTV_DEC_STREAM_TYPE_MPG].s_flags)) + return -EBUSY; + + if (y == IVTV_DEC_STREAM_TYPE_YUV) { + if (read_reg(0x82c) == 0) { + IVTV_ERR("Tried to open YUV output device but need to send data to mpeg decoder before it can be used\n"); + /* return -ENODEV; */ + } + ivtv_udma_alloc(itv); + } + + /* Allocate memory */ + item = kmalloc(sizeof(struct ivtv_open_id), GFP_KERNEL); + if (NULL == item) { + IVTV_DEBUG_WARN("nomem on v4l2 open\n"); + return -ENOMEM; + } + item->itv = itv; + item->type = y; + v4l2_prio_open(&itv->prio, &item->prio); + + item->open_id = itv->open_id++; + filp->private_data = item; + + if (item->type == IVTV_ENC_STREAM_TYPE_RAD) { + /* Try to claim this stream */ + if (ivtv_claim_stream(item, item->type)) { + /* No, it's already in use */ + kfree(item); + return -EBUSY; + } + + /* We have the radio */ + ivtv_mute(itv); + /* Switch tuner to radio */ + ivtv_call_i2c_clients(itv, AUDC_SET_RADIO, NULL); + /* Mark that the radio is being used. */ + set_bit(IVTV_F_I_RADIO_USER, &itv->i_flags); + /* Select the correct audio input (i.e. radio tuner) */ + ivtv_audio_set_io(itv); + /* Done! Unmute and continue. */ + ivtv_unmute(itv); + } + + /* YUV or MPG Decoding Mode? */ + if (y == IVTV_DEC_STREAM_TYPE_MPG) + clear_bit(IVTV_F_I_DEC_YUV, &itv->i_flags); + else if (y == IVTV_DEC_STREAM_TYPE_YUV) + { + set_bit(IVTV_F_I_DEC_YUV, &itv->i_flags); + } + + return 0; +} + +void ivtv_mute(struct ivtv *itv) +{ + struct v4l2_control ctrl = { V4L2_CID_AUDIO_MUTE, 1 }; + + /* Mute sound to avoid pop */ + ivtv_control_ioctls(itv, VIDIOC_S_CTRL, &ctrl); + + if (atomic_read(&itv->capturing)) + ivtv_vapi(itv, CX2341X_ENC_MUTE_AUDIO, 1, 1); + + IVTV_DEBUG_INFO("Mute\n"); +} + +void ivtv_unmute(struct ivtv *itv) +{ + struct v4l2_control ctrl = { V4L2_CID_AUDIO_MUTE, 0 }; + + /* initialize or refresh input */ + if (atomic_read(&itv->capturing) == 0) + ivtv_vapi(itv, CX2341X_ENC_INITIALIZE_INPUT, 0); + + ivtv_sleep_timeout(HZ / 10, 0); + + if (atomic_read(&itv->capturing)) { + ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12); + ivtv_vapi(itv, CX2341X_ENC_MUTE_AUDIO, 1, 0); + } + + /* Unmute */ + ivtv_control_ioctls(itv, VIDIOC_S_CTRL, &ctrl); + IVTV_DEBUG_INFO("Unmute\n"); +} diff --git a/drivers/media/video/ivtv/ivtv-fileops.h b/drivers/media/video/ivtv/ivtv-fileops.h new file mode 100644 index 00000000000..74a1745fabb --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-fileops.h @@ -0,0 +1,44 @@ +/* + file operation functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Testing/Debugging */ +int ivtv_v4l2_open(struct inode *inode, struct file *filp); +ssize_t ivtv_v4l2_read(struct file *filp, char __user *buf, size_t count, + loff_t * pos); +ssize_t ivtv_v4l2_write(struct file *filp, const char __user *buf, size_t count, + loff_t * pos); +int ivtv_v4l2_close(struct inode *inode, struct file *filp); +unsigned int ivtv_v4l2_enc_poll(struct file *filp, poll_table * wait); +unsigned int ivtv_v4l2_dec_poll(struct file *filp, poll_table * wait); +int ivtv_start_capture(struct ivtv_open_id *id); +void ivtv_stop_capture(struct ivtv_open_id *id, int gop_end); +int ivtv_start_decoding(struct ivtv_open_id *id, int speed); +void ivtv_mute(struct ivtv *itv); +void ivtv_unmute(struct ivtv *itv); + +/* Utilities */ + +/* Try to claim a stream for the filehandle. Return 0 on success, + -EBUSY if stream already claimed. Once a stream is claimed, it + remains claimed until the associated filehandle is closed. */ +int ivtv_claim_stream(struct ivtv_open_id *id, int type); + +/* Release a previously claimed stream. */ +void ivtv_release_stream(struct ivtv_stream *s); diff --git a/drivers/media/video/ivtv/ivtv-firmware.c b/drivers/media/video/ivtv/ivtv-firmware.c new file mode 100644 index 00000000000..d4c910b782a --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-firmware.c @@ -0,0 +1,272 @@ +/* + ivtv firmware functions. + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-mailbox.h" +#include "ivtv-firmware.h" +#include <linux/firmware.h> + +#define IVTV_MASK_SPU_ENABLE 0xFFFFFFFE +#define IVTV_MASK_VPU_ENABLE15 0xFFFFFFF6 +#define IVTV_MASK_VPU_ENABLE16 0xFFFFFFFB +#define IVTV_CMD_VDM_STOP 0x00000000 +#define IVTV_CMD_AO_STOP 0x00000005 +#define IVTV_CMD_APU_PING 0x00000000 +#define IVTV_CMD_VPU_STOP15 0xFFFFFFFE +#define IVTV_CMD_VPU_STOP16 0xFFFFFFEE +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF +#define IVTV_CMD_SPU_STOP 0x00000001 +#define IVTV_CMD_SDRAM_PRECHARGE_INIT 0x0000001A +#define IVTV_CMD_SDRAM_REFRESH_INIT 0x80000640 +#define IVTV_SDRAM_SLEEPTIME (60 * HZ / 100) /* 600 ms */ + +#define IVTV_DECODE_INIT_MPEG_FILENAME "v4l-cx2341x-init.mpg" +#define IVTV_DECODE_INIT_MPEG_SIZE (152*1024) + +/* Encoder/decoder firmware sizes */ +#define IVTV_FW_ENC_SIZE (376836) +#define IVTV_FW_DEC_SIZE (256*1024) + +static int load_fw_direct(const char *fn, volatile u8 __iomem *mem, struct ivtv *itv, long size) +{ + const struct firmware *fw = NULL; + int retries = 3; + +retry: + if (retries && request_firmware(&fw, fn, &itv->dev->dev) == 0) { + int i; + volatile u32 __iomem *dst = (volatile u32 __iomem *)mem; + const u32 *src = (const u32 *)fw->data; + + /* temporarily allow 256 KB encoding firmwares as well for + compatibility with blackbird cards */ + if (fw->size != size && fw->size != 256 * 1024) { + /* Due to race conditions in firmware loading (esp. with udev <0.95) + the wrong file was sometimes loaded. So we check filesizes to + see if at least the right-sized file was loaded. If not, then we + retry. */ + IVTV_INFO("retry: file loaded was not %s (expected size %ld, got %zd)\n", fn, size, fw->size); + release_firmware(fw); + retries--; + goto retry; + } + for (i = 0; i < fw->size; i += 4) { + /* no need for endianness conversion on the ppc */ + __raw_writel(*src, dst); + dst++; + src++; + } + release_firmware(fw); + IVTV_INFO("loaded %s firmware (%zd bytes)\n", fn, fw->size); + return size; + } + IVTV_ERR("unable to open firmware %s (must be %ld bytes)\n", fn, size); + IVTV_ERR("did you put the firmware in the hotplug firmware directory?\n"); + return -ENOMEM; +} + +void ivtv_halt_firmware(struct ivtv *itv) +{ + IVTV_DEBUG_INFO("Preparing for firmware halt.\n"); + if (itv->has_cx23415 && itv->dec_mbox.mbox) + ivtv_vapi(itv, CX2341X_DEC_HALT_FW, 0); + if (itv->enc_mbox.mbox) + ivtv_vapi(itv, CX2341X_ENC_HALT_FW, 0); + + ivtv_sleep_timeout(HZ / 100, 0); + itv->enc_mbox.mbox = itv->dec_mbox.mbox = NULL; + + IVTV_DEBUG_INFO("Stopping VDM\n"); + write_reg(IVTV_CMD_VDM_STOP, IVTV_REG_VDM); + + IVTV_DEBUG_INFO("Stopping AO\n"); + write_reg(IVTV_CMD_AO_STOP, IVTV_REG_AO); + + IVTV_DEBUG_INFO("pinging (?) APU\n"); + write_reg(IVTV_CMD_APU_PING, IVTV_REG_APU); + + IVTV_DEBUG_INFO("Stopping VPU\n"); + if (!itv->has_cx23415) + write_reg(IVTV_CMD_VPU_STOP16, IVTV_REG_VPU); + else + write_reg(IVTV_CMD_VPU_STOP15, IVTV_REG_VPU); + + IVTV_DEBUG_INFO("Resetting Hw Blocks\n"); + write_reg(IVTV_CMD_HW_BLOCKS_RST, IVTV_REG_HW_BLOCKS); + + IVTV_DEBUG_INFO("Stopping SPU\n"); + write_reg(IVTV_CMD_SPU_STOP, IVTV_REG_SPU); + + ivtv_sleep_timeout(HZ / 100, 0); + + IVTV_DEBUG_INFO("init Encoder SDRAM pre-charge\n"); + write_reg(IVTV_CMD_SDRAM_PRECHARGE_INIT, IVTV_REG_ENC_SDRAM_PRECHARGE); + + IVTV_DEBUG_INFO("init Encoder SDRAM refresh to 1us\n"); + write_reg(IVTV_CMD_SDRAM_REFRESH_INIT, IVTV_REG_ENC_SDRAM_REFRESH); + + if (itv->has_cx23415) { + IVTV_DEBUG_INFO("init Decoder SDRAM pre-charge\n"); + write_reg(IVTV_CMD_SDRAM_PRECHARGE_INIT, IVTV_REG_DEC_SDRAM_PRECHARGE); + + IVTV_DEBUG_INFO("init Decoder SDRAM refresh to 1us\n"); + write_reg(IVTV_CMD_SDRAM_REFRESH_INIT, IVTV_REG_DEC_SDRAM_REFRESH); + } + + IVTV_DEBUG_INFO("Sleeping for %dms (600 recommended)\n", + (int)(IVTV_SDRAM_SLEEPTIME * 1000 / HZ)); + ivtv_sleep_timeout(IVTV_SDRAM_SLEEPTIME, 0); +} + +void ivtv_firmware_versions(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + /* Encoder */ + ivtv_vapi_result(itv, data, CX2341X_ENC_GET_VERSION, 0); + IVTV_INFO("Encoder revision: 0x%08x\n", data[0]); + + if (data[0] != 0x02060039) + IVTV_WARN("Recommended firmware version is 0x02060039.\n"); + + if (itv->has_cx23415) { + /* Decoder */ + ivtv_vapi_result(itv, data, CX2341X_DEC_GET_VERSION, 0); + IVTV_INFO("Decoder revision: 0x%08x\n", data[0]); + } +} + +static int ivtv_firmware_copy(struct ivtv *itv) +{ + IVTV_DEBUG_INFO("Loading encoder image\n"); + if (load_fw_direct(CX2341X_FIRM_ENC_FILENAME, + itv->enc_mem, itv, IVTV_FW_ENC_SIZE) != IVTV_FW_ENC_SIZE) { + IVTV_DEBUG_WARN("failed loading encoder firmware\n"); + return -3; + } + if (!itv->has_cx23415) + return 0; + + IVTV_DEBUG_INFO("Loading decoder image\n"); + if (load_fw_direct(CX2341X_FIRM_DEC_FILENAME, + itv->dec_mem, itv, IVTV_FW_DEC_SIZE) != IVTV_FW_DEC_SIZE) { + IVTV_DEBUG_WARN("failed loading decoder firmware\n"); + return -1; + } + return 0; +} + +static volatile struct ivtv_mailbox __iomem *ivtv_search_mailbox(const volatile u8 __iomem *mem, u32 size) +{ + int i; + + /* mailbox is preceeded by a 16 byte 'magic cookie' starting at a 256-byte + address boundary */ + for (i = 0; i < size; i += 0x100) { + if (readl(mem + i) == 0x12345678 && + readl(mem + i + 4) == 0x34567812 && + readl(mem + i + 8) == 0x56781234 && + readl(mem + i + 12) == 0x78123456) { + return (volatile struct ivtv_mailbox __iomem *)(mem + i + 16); + } + } + return NULL; +} + +int ivtv_firmware_init(struct ivtv *itv) +{ + int err; + + ivtv_halt_firmware(itv); + + /* load firmware */ + err = ivtv_firmware_copy(itv); + if (err) { + IVTV_DEBUG_WARN("Error %d loading firmware\n", err); + return err; + } + + /* start firmware */ + write_reg(read_reg(IVTV_REG_SPU) & IVTV_MASK_SPU_ENABLE, IVTV_REG_SPU); + ivtv_sleep_timeout(HZ / 10, 0); + if (itv->has_cx23415) + write_reg(read_reg(IVTV_REG_VPU) & IVTV_MASK_VPU_ENABLE15, IVTV_REG_VPU); + else + write_reg(read_reg(IVTV_REG_VPU) & IVTV_MASK_VPU_ENABLE16, IVTV_REG_VPU); + ivtv_sleep_timeout(HZ / 10, 0); + + /* find mailboxes and ping firmware */ + itv->enc_mbox.mbox = ivtv_search_mailbox(itv->enc_mem, IVTV_ENCODER_SIZE); + if (itv->enc_mbox.mbox == NULL) + IVTV_ERR("Encoder mailbox not found\n"); + else if (ivtv_vapi(itv, CX2341X_ENC_PING_FW, 0)) { + IVTV_ERR("Encoder firmware dead!\n"); + itv->enc_mbox.mbox = NULL; + } + if (itv->enc_mbox.mbox == NULL) + return -ENODEV; + + if (!itv->has_cx23415) + return 0; + + itv->dec_mbox.mbox = ivtv_search_mailbox(itv->dec_mem, IVTV_DECODER_SIZE); + if (itv->dec_mbox.mbox == NULL) + IVTV_ERR("Decoder mailbox not found\n"); + else if (itv->has_cx23415 && ivtv_vapi(itv, CX2341X_DEC_PING_FW, 0)) { + IVTV_ERR("Decoder firmware dead!\n"); + itv->dec_mbox.mbox = NULL; + } + return itv->dec_mbox.mbox ? 0 : -ENODEV; +} + +void ivtv_init_mpeg_decoder(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + long readbytes; + volatile u8 __iomem *mem_offset; + + data[0] = 0; + data[1] = itv->params.width; /* YUV source width */ + data[2] = itv->params.height; + data[3] = itv->params.audio_properties; /* Audio settings to use, + bitmap. see docs. */ + if (ivtv_api(itv, CX2341X_DEC_SET_DECODER_SOURCE, 4, data)) { + IVTV_ERR("ivtv_init_mpeg_decoder failed to set decoder source\n"); + return; + } + + if (ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 1) != 0) { + IVTV_ERR("ivtv_init_mpeg_decoder failed to start playback\n"); + return; + } + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, data); + mem_offset = itv->dec_mem + data[1]; + + if ((readbytes = load_fw_direct(IVTV_DECODE_INIT_MPEG_FILENAME, + mem_offset, itv, IVTV_DECODE_INIT_MPEG_SIZE)) <= 0) { + IVTV_DEBUG_WARN("failed to read mpeg decoder initialisation file %s\n", + IVTV_DECODE_INIT_MPEG_FILENAME); + } else { + ivtv_vapi(itv, CX2341X_DEC_SCHED_DMA_FROM_HOST, 3, 0, readbytes, 0); + ivtv_sleep_timeout(HZ / 10, 0); + } + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 4, 0, 0, 0, 1); +} diff --git a/drivers/media/video/ivtv/ivtv-firmware.h b/drivers/media/video/ivtv/ivtv-firmware.h new file mode 100644 index 00000000000..8b2ffe65890 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-firmware.h @@ -0,0 +1,25 @@ +/* + ivtv firmware functions. + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +int ivtv_firmware_init(struct ivtv *itv); +void ivtv_firmware_versions(struct ivtv *itv); +void ivtv_halt_firmware(struct ivtv *itv); +void ivtv_init_mpeg_decoder(struct ivtv *itv); diff --git a/drivers/media/video/ivtv/ivtv-gpio.c b/drivers/media/video/ivtv/ivtv-gpio.c new file mode 100644 index 00000000000..bc8f8ca2961 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-gpio.c @@ -0,0 +1,307 @@ +/* + gpio functions. + Merging GPIO support into driver: + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-gpio.h" +#include <media/tuner.h> + +/* + * GPIO assignment of Yuan MPG600/MPG160 + * + * bit 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0 + * OUTPUT IN1 IN0 AM3 AM2 AM1 AM0 + * INPUT DM1 DM0 + * + * IN* : Input selection + * IN1 IN0 + * 1 1 N/A + * 1 0 Line + * 0 1 N/A + * 0 0 Tuner + * + * AM* : Audio Mode + * AM3 0: Normal 1: Mixed(Sub+Main channel) + * AM2 0: Subchannel 1: Main channel + * AM1 0: Stereo 1: Mono + * AM0 0: Normal 1: Mute + * + * DM* : Detected tuner audio Mode + * DM1 0: Stereo 1: Mono + * DM0 0: Multiplex 1: Normal + * + * GPIO Initial Settings + * MPG600 MPG160 + * DIR 0x3080 0x7080 + * OUTPUT 0x000C 0x400C + * + * Special thanks to Makoto Iguchi <iguchi@tahoo.org> and Mr. Anonymous + * for analyzing GPIO of MPG160. + * + ***************************************************************************** + * + * GPIO assignment of Avermedia M179 (per information direct from AVerMedia) + * + * bit 15 14 13 12 | 11 10 9 8 | 7 6 5 4 | 3 2 1 0 + * OUTPUT IN0 AM0 IN1 AM1 AM2 IN2 BR0 BR1 + * INPUT + * + * IN* : Input selection + * IN0 IN1 IN2 + * * 1 * Mute + * 0 0 0 Line-In + * 1 0 0 TV Tuner Audio + * 0 0 1 FM Audio + * 1 0 1 Mute + * + * AM* : Audio Mode + * AM0 AM1 AM2 + * 0 0 0 TV Tuner Audio: L_OUT=(L+R)/2, R_OUT=SAP + * 0 0 1 TV Tuner Audio: L_OUT=R_OUT=SAP (SAP) + * 0 1 0 TV Tuner Audio: L_OUT=L, R_OUT=R (stereo) + * 0 1 1 TV Tuner Audio: mute + * 1 * * TV Tuner Audio: L_OUT=R_OUT=(L+R)/2 (mono) + * + * BR* : Audio Sample Rate (BR stands for bitrate for some reason) + * BR0 BR1 + * 0 0 32 kHz + * 0 1 44.1 kHz + * 1 0 48 kHz + * + * DM* : Detected tuner audio Mode + * Unknown currently + * + * Special thanks to AVerMedia Technologies, Inc. and Jiun-Kuei Jung at + * AVerMedia for providing the GPIO information used to add support + * for the M179 cards. + */ + +/********************* GPIO stuffs *********************/ + +/* GPIO registers */ +#define IVTV_REG_GPIO_IN 0x9008 +#define IVTV_REG_GPIO_OUT 0x900c +#define IVTV_REG_GPIO_DIR 0x9020 + +void ivtv_reset_ir_gpio(struct ivtv *itv) +{ + int curdir, curout; + + if (itv->card->type != IVTV_CARD_PVR_150) + return; + IVTV_DEBUG_INFO("Resetting PVR150 IR\n"); + curout = read_reg(IVTV_REG_GPIO_OUT); + curdir = read_reg(IVTV_REG_GPIO_DIR); + curdir |= 0x80; + write_reg(curdir, IVTV_REG_GPIO_DIR); + curout = (curout & ~0xF) | 1; + write_reg(curout, IVTV_REG_GPIO_OUT); + /* We could use something else for smaller time */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + curout |= 2; + write_reg(curout, IVTV_REG_GPIO_OUT); + curdir &= ~0x80; + write_reg(curdir, IVTV_REG_GPIO_DIR); +} + +#ifdef HAVE_XC3028 +int ivtv_reset_tuner_gpio(enum v4l2_tuner_type mode, void *priv, int ptr) +{ + int curdir, curout; + struct ivtv *itv = (struct ivtv *) priv; + + if (itv->card->type != IVTV_CARD_PG600V2 || itv->options.tuner != TUNER_XCEIVE_XC3028) + return -EINVAL; + IVTV_INFO("Resetting tuner.\n"); + curout = read_reg(IVTV_REG_GPIO_OUT); + curdir = read_reg(IVTV_REG_GPIO_DIR); + curdir |= (1 << 12); /* GPIO bit 12 */ + + curout &= ~(1 << 12); + write_reg(curout, IVTV_REG_GPIO_OUT); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + + curout |= (1 << 12); + write_reg(curout, IVTV_REG_GPIO_OUT); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(1); + + return 0; +} +#endif + +void ivtv_gpio_init(struct ivtv *itv) +{ + if (itv->card->gpio_init.direction == 0) + return; + + IVTV_DEBUG_INFO("GPIO initial dir: %08x out: %08x\n", + read_reg(IVTV_REG_GPIO_DIR), read_reg(IVTV_REG_GPIO_OUT)); + + /* init output data then direction */ + write_reg(itv->card->gpio_init.initial_value, IVTV_REG_GPIO_OUT); + write_reg(itv->card->gpio_init.direction, IVTV_REG_GPIO_DIR); +} + +static struct v4l2_queryctrl gpio_ctrl_mute = { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + .flags = 0, +}; + +int ivtv_gpio(struct ivtv *itv, unsigned int command, void *arg) +{ + struct v4l2_tuner *tuner = arg; + struct v4l2_control *ctrl = arg; + struct v4l2_routing *route = arg; + u16 mask, data; + + switch (command) { + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + mask = itv->card->gpio_audio_freq.mask; + switch (*(u32 *)arg) { + case 32000: + data = itv->card->gpio_audio_freq.f32000; + break; + case 44100: + data = itv->card->gpio_audio_freq.f44100; + break; + case 48000: + default: + data = itv->card->gpio_audio_freq.f48000; + break; + } + break; + + case VIDIOC_G_TUNER: + mask = itv->card->gpio_audio_detect.mask; + if (mask == 0 || (read_reg(IVTV_REG_GPIO_IN) & mask)) + tuner->rxsubchans = V4L2_TUNER_MODE_STEREO | + V4L2_TUNER_MODE_LANG1 | V4L2_TUNER_MODE_LANG2; + else + tuner->rxsubchans = V4L2_TUNER_SUB_MONO; + return 0; + + case VIDIOC_S_TUNER: + mask = itv->card->gpio_audio_mode.mask; + switch (tuner->audmode) { + case V4L2_TUNER_MODE_LANG1: + data = itv->card->gpio_audio_mode.lang1; + break; + case V4L2_TUNER_MODE_LANG2: + data = itv->card->gpio_audio_mode.lang2; + break; + case V4L2_TUNER_MODE_MONO: + data = itv->card->gpio_audio_mode.mono; + break; + case V4L2_TUNER_MODE_STEREO: + case V4L2_TUNER_MODE_LANG1_LANG2: + default: + data = itv->card->gpio_audio_mode.stereo; + break; + } + break; + + case AUDC_SET_RADIO: + mask = itv->card->gpio_audio_input.mask; + data = itv->card->gpio_audio_input.radio; + break; + + case VIDIOC_S_STD: + mask = itv->card->gpio_audio_input.mask; + data = itv->card->gpio_audio_input.tuner; + break; + + case VIDIOC_INT_S_AUDIO_ROUTING: + if (route->input > 2) + return -EINVAL; + mask = itv->card->gpio_audio_input.mask; + switch (route->input) { + case 0: + data = itv->card->gpio_audio_input.tuner; + break; + case 1: + data = itv->card->gpio_audio_input.linein; + break; + case 2: + default: + data = itv->card->gpio_audio_input.radio; + break; + } + break; + + case VIDIOC_G_CTRL: + if (ctrl->id != V4L2_CID_AUDIO_MUTE) + return -EINVAL; + mask = itv->card->gpio_audio_mute.mask; + data = itv->card->gpio_audio_mute.mute; + ctrl->value = (read_reg(IVTV_REG_GPIO_OUT) & mask) == data; + return 0; + + case VIDIOC_S_CTRL: + if (ctrl->id != V4L2_CID_AUDIO_MUTE) + return -EINVAL; + mask = itv->card->gpio_audio_mute.mask; + data = ctrl->value ? itv->card->gpio_audio_mute.mute : 0; + break; + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *qc = arg; + + if (qc->id != V4L2_CID_AUDIO_MUTE) + return -EINVAL; + *qc = gpio_ctrl_mute; + return 0; + } + + case VIDIOC_LOG_STATUS: + IVTV_INFO("GPIO status: DIR=0x%04x OUT=0x%04x IN=0x%04x\n", + read_reg(IVTV_REG_GPIO_DIR), read_reg(IVTV_REG_GPIO_OUT), + read_reg(IVTV_REG_GPIO_IN)); + return 0; + + case VIDIOC_INT_S_VIDEO_ROUTING: + if (route->input > 2) /* 0:Tuner 1:Composite 2:S-Video */ + return -EINVAL; + mask = itv->card->gpio_video_input.mask; + if (route->input == 0) + data = itv->card->gpio_video_input.tuner; + else if (route->input == 1) + data = itv->card->gpio_video_input.composite; + else + data = itv->card->gpio_video_input.svideo; + break; + + default: + return -EINVAL; + } + if (mask) + write_reg((read_reg(IVTV_REG_GPIO_OUT) & ~mask) | (data & mask), IVTV_REG_GPIO_OUT); + return 0; +} diff --git a/drivers/media/video/ivtv/ivtv-gpio.h b/drivers/media/video/ivtv/ivtv-gpio.h new file mode 100644 index 00000000000..c301d2a3934 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-gpio.h @@ -0,0 +1,25 @@ +/* + gpio functions. + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* GPIO stuff */ +void ivtv_gpio_init(struct ivtv *itv); +void ivtv_reset_ir_gpio(struct ivtv *itv); +int ivtv_reset_tuner_gpio(enum v4l2_tuner_type mode, void *priv, int ptr); +int ivtv_gpio(struct ivtv *itv, unsigned int command, void *arg); diff --git a/drivers/media/video/ivtv/ivtv-i2c.c b/drivers/media/video/ivtv/ivtv-i2c.c new file mode 100644 index 00000000000..50624c6a62a --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-i2c.c @@ -0,0 +1,748 @@ +/* + I2C functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + This file includes an i2c implementation that was reverse engineered + from the Hauppauge windows driver. Older ivtv versions used i2c-algo-bit, + which whilst fine under most circumstances, had trouble with the Zilog + CPU on the PVR-150 which handles IR functions (occasional inability to + communicate with the chip until it was reset) and also with the i2c + bus being completely unreachable when multiple PVR cards were present. + + The implementation is very similar to i2c-algo-bit, but there are enough + subtle differences that the two are hard to merge. The general strategy + employed by i2c-algo-bit is to use udelay() to implement the timing + when putting out bits on the scl/sda lines. The general strategy taken + here is to poll the lines for state changes (see ivtv_waitscl and + ivtv_waitsda). In addition there are small delays at various locations + which poll the SCL line 5 times (ivtv_scldelay). I would guess that + since this is memory mapped I/O that the length of those delays is tied + to the PCI bus clock. There is some extra code to do with recovery + and retries. Since it is not known what causes the actual i2c problems + in the first place, the only goal if one was to attempt to use + i2c-algo-bit would be to try to make it follow the same code path. + This would be a lot of work, and I'm also not convinced that it would + provide a generic benefit to i2c-algo-bit. Therefore consider this + an engineering solution -- not pretty, but it works. + + Some more general comments about what we are doing: + + The i2c bus is a 2 wire serial bus, with clock (SCL) and data (SDA) + lines. To communicate on the bus (as a master, we don't act as a slave), + we first initiate a start condition (ivtv_start). We then write the + address of the device that we want to communicate with, along with a flag + that indicates whether this is a read or a write. The slave then issues + an ACK signal (ivtv_ack), which tells us that it is ready for reading / + writing. We then proceed with reading or writing (ivtv_read/ivtv_write), + and finally issue a stop condition (ivtv_stop) to make the bus available + to other masters. + + There is an additional form of transaction where a write may be + immediately followed by a read. In this case, there is no intervening + stop condition. (Only the msp3400 chip uses this method of data transfer). + */ + +#include "ivtv-driver.h" +#include "ivtv-cards.h" +#include "ivtv-gpio.h" +#include "ivtv-i2c.h" + +#include <media/ir-kbd-i2c.h> + +/* i2c implementation for cx23415/6 chip, ivtv project. + * Author: Kevin Thayer (nufan_wfk at yahoo.com) + */ +/* i2c stuff */ +#define IVTV_REG_I2C_SETSCL_OFFSET 0x7000 +#define IVTV_REG_I2C_SETSDA_OFFSET 0x7004 +#define IVTV_REG_I2C_GETSCL_OFFSET 0x7008 +#define IVTV_REG_I2C_GETSDA_OFFSET 0x700c + +#ifndef I2C_ADAP_CLASS_TV_ANALOG +#define I2C_ADAP_CLASS_TV_ANALOG I2C_CLASS_TV_ANALOG +#endif /* I2C_ADAP_CLASS_TV_ANALOG */ + +#define IVTV_CS53L32A_I2C_ADDR 0x11 +#define IVTV_CX25840_I2C_ADDR 0x44 +#define IVTV_SAA7115_I2C_ADDR 0x21 +#define IVTV_SAA7127_I2C_ADDR 0x44 +#define IVTV_SAA717x_I2C_ADDR 0x21 +#define IVTV_MSP3400_I2C_ADDR 0x40 +#define IVTV_HAUPPAUGE_I2C_ADDR 0x50 +#define IVTV_WM8739_I2C_ADDR 0x1a +#define IVTV_WM8775_I2C_ADDR 0x1b +#define IVTV_TEA5767_I2C_ADDR 0x60 +#define IVTV_UPD64031A_I2C_ADDR 0x12 +#define IVTV_UPD64083_I2C_ADDR 0x5c +#define IVTV_TDA985X_I2C_ADDR 0x5b + +/* This array should match the IVTV_HW_ defines */ +static const u8 hw_driverids[] = { + I2C_DRIVERID_CX25840, + I2C_DRIVERID_SAA711X, + I2C_DRIVERID_SAA7127, + I2C_DRIVERID_MSP3400, + I2C_DRIVERID_TUNER, + I2C_DRIVERID_WM8775, + I2C_DRIVERID_CS53L32A, + I2C_DRIVERID_TVEEPROM, + I2C_DRIVERID_SAA711X, + I2C_DRIVERID_TVAUDIO, + I2C_DRIVERID_UPD64031A, + I2C_DRIVERID_UPD64083, + I2C_DRIVERID_SAA717X, + I2C_DRIVERID_WM8739, + 0 /* IVTV_HW_GPIO dummy driver ID */ +}; + +/* This array should match the IVTV_HW_ defines */ +static const char * const hw_drivernames[] = { + "cx2584x", + "saa7115", + "saa7127", + "msp3400", + "tuner", + "wm8775", + "cs53l32a", + "tveeprom", + "saa7114", + "tvaudio", + "upd64031a", + "upd64083", + "saa717x", + "wm8739", + "gpio", +}; + +static int attach_inform(struct i2c_client *client) +{ + struct ivtv *itv = (struct ivtv *)i2c_get_adapdata(client->adapter); + int i; + + IVTV_DEBUG_I2C("i2c client attach\n"); + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + if (itv->i2c_clients[i] == NULL) { + itv->i2c_clients[i] = client; + break; + } + } + if (i == I2C_CLIENTS_MAX) { + IVTV_ERR("insufficient room for new I2C client!\n"); + } + return 0; +} + +static int detach_inform(struct i2c_client *client) +{ + int i; + struct ivtv *itv = (struct ivtv *)i2c_get_adapdata(client->adapter); + + IVTV_DEBUG_I2C("i2c client detach\n"); + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + if (itv->i2c_clients[i] == client) { + itv->i2c_clients[i] = NULL; + break; + } + } + IVTV_DEBUG_I2C("i2c detach [client=%s,%s]\n", + client->name, (i < I2C_CLIENTS_MAX) ? "ok" : "failed"); + + return 0; +} + +/* Set the serial clock line to the desired state */ +static void ivtv_setscl(struct ivtv *itv, int state) +{ + /* write them out */ + /* write bits are inverted */ + write_reg(~state, IVTV_REG_I2C_SETSCL_OFFSET); +} + +/* Set the serial data line to the desired state */ +static void ivtv_setsda(struct ivtv *itv, int state) +{ + /* write them out */ + /* write bits are inverted */ + write_reg(~state & 1, IVTV_REG_I2C_SETSDA_OFFSET); +} + +/* Read the serial clock line */ +static int ivtv_getscl(struct ivtv *itv) +{ + return read_reg(IVTV_REG_I2C_GETSCL_OFFSET) & 1; +} + +/* Read the serial data line */ +static int ivtv_getsda(struct ivtv *itv) +{ + return read_reg(IVTV_REG_I2C_GETSDA_OFFSET) & 1; +} + +/* Implement a short delay by polling the serial clock line */ +static void ivtv_scldelay(struct ivtv *itv) +{ + int i; + + for (i = 0; i < 5; ++i) + ivtv_getscl(itv); +} + +/* Wait for the serial clock line to become set to a specific value */ +static int ivtv_waitscl(struct ivtv *itv, int val) +{ + int i; + + ivtv_scldelay(itv); + for (i = 0; i < 1000; ++i) { + if (ivtv_getscl(itv) == val) + return 1; + } + return 0; +} + +/* Wait for the serial data line to become set to a specific value */ +static int ivtv_waitsda(struct ivtv *itv, int val) +{ + int i; + + ivtv_scldelay(itv); + for (i = 0; i < 1000; ++i) { + if (ivtv_getsda(itv) == val) + return 1; + } + return 0; +} + +/* Wait for the slave to issue an ACK */ +static int ivtv_ack(struct ivtv *itv) +{ + int ret = 0; + + if (ivtv_getscl(itv) == 1) { + IVTV_DEBUG_I2C("SCL was high starting an ack\n"); + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Could not set SCL low starting an ack\n"); + return -EREMOTEIO; + } + } + ivtv_setsda(itv, 1); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + if (!ivtv_waitsda(itv, 0)) { + IVTV_DEBUG_I2C("Slave did not ack\n"); + ret = -EREMOTEIO; + } + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Failed to set SCL low after ACK\n"); + ret = -EREMOTEIO; + } + return ret; +} + +/* Write a single byte to the i2c bus and wait for the slave to ACK */ +static int ivtv_sendbyte(struct ivtv *itv, unsigned char byte) +{ + int i, bit; + + IVTV_DEBUG_I2C("write %x\n",byte); + for (i = 0; i < 8; ++i, byte<<=1) { + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Error setting SCL low\n"); + return -EREMOTEIO; + } + bit = (byte>>7)&1; + ivtv_setsda(itv, bit); + if (!ivtv_waitsda(itv, bit)) { + IVTV_DEBUG_I2C("Error setting SDA\n"); + return -EREMOTEIO; + } + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("Slave not ready for bit\n"); + return -EREMOTEIO; + } + } + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("Error setting SCL low\n"); + return -EREMOTEIO; + } + return ivtv_ack(itv); +} + +/* Read a byte from the i2c bus and send a NACK if applicable (i.e. for the + final byte) */ +static int ivtv_readbyte(struct ivtv *itv, unsigned char *byte, int nack) +{ + int i; + + *byte = 0; + + ivtv_setsda(itv, 1); + ivtv_scldelay(itv); + for (i = 0; i < 8; ++i) { + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("Error setting SCL high\n"); + return -EREMOTEIO; + } + *byte = ((*byte)<<1)|ivtv_getsda(itv); + } + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + ivtv_setsda(itv, nack); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + ivtv_scldelay(itv); + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + IVTV_DEBUG_I2C("read %x\n",*byte); + return 0; +} + +/* Issue a start condition on the i2c bus to alert slaves to prepare for + an address write */ +static int ivtv_start(struct ivtv *itv) +{ + int sda; + + sda = ivtv_getsda(itv); + if (sda != 1) { + IVTV_DEBUG_I2C("SDA was low at start\n"); + ivtv_setsda(itv, 1); + if (!ivtv_waitsda(itv, 1)) { + IVTV_DEBUG_I2C("SDA stuck low\n"); + return -EREMOTEIO; + } + } + if (ivtv_getscl(itv) != 1) { + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("SCL stuck low at start\n"); + return -EREMOTEIO; + } + } + ivtv_setsda(itv, 0); + ivtv_scldelay(itv); + return 0; +} + +/* Issue a stop condition on the i2c bus to release it */ +static int ivtv_stop(struct ivtv *itv) +{ + int i; + + if (ivtv_getscl(itv) != 0) { + IVTV_DEBUG_I2C("SCL not low when stopping\n"); + ivtv_setscl(itv, 0); + if (!ivtv_waitscl(itv, 0)) { + IVTV_DEBUG_I2C("SCL could not be set low\n"); + } + } + ivtv_setsda(itv, 0); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + if (!ivtv_waitscl(itv, 1)) { + IVTV_DEBUG_I2C("SCL could not be set high\n"); + return -EREMOTEIO; + } + ivtv_scldelay(itv); + ivtv_setsda(itv, 1); + if (!ivtv_waitsda(itv, 1)) { + IVTV_DEBUG_I2C("resetting I2C\n"); + for (i = 0; i < 16; ++i) { + ivtv_setscl(itv, 0); + ivtv_scldelay(itv); + ivtv_setscl(itv, 1); + ivtv_scldelay(itv); + ivtv_setsda(itv, 1); + } + ivtv_waitsda(itv, 1); + return -EREMOTEIO; + } + return 0; +} + +/* Write a message to the given i2c slave. do_stop may be 0 to prevent + issuing the i2c stop condition (when following with a read) */ +static int ivtv_write(struct ivtv *itv, unsigned char addr, unsigned char *data, u32 len, int do_stop) +{ + int retry, ret = -EREMOTEIO; + u32 i; + + for (retry = 0; ret != 0 && retry < 8; ++retry) { + ret = ivtv_start(itv); + + if (ret == 0) { + ret = ivtv_sendbyte(itv, addr<<1); + for (i = 0; ret == 0 && i < len; ++i) + ret = ivtv_sendbyte(itv, data[i]); + } + if (ret != 0 || do_stop) { + ivtv_stop(itv); + } + } + if (ret) + IVTV_DEBUG_I2C("i2c write to %x failed\n", addr); + return ret; +} + +/* Read data from the given i2c slave. A stop condition is always issued. */ +static int ivtv_read(struct ivtv *itv, unsigned char addr, unsigned char *data, u32 len) +{ + int retry, ret = -EREMOTEIO; + u32 i; + + for (retry = 0; ret != 0 && retry < 8; ++retry) { + ret = ivtv_start(itv); + if (ret == 0) + ret = ivtv_sendbyte(itv, (addr << 1) | 1); + for (i = 0; ret == 0 && i < len; ++i) { + ret = ivtv_readbyte(itv, &data[i], i == len - 1); + } + ivtv_stop(itv); + } + if (ret) + IVTV_DEBUG_I2C("i2c read from %x failed\n", addr); + return ret; +} + +/* Kernel i2c transfer implementation. Takes a number of messages to be read + or written. If a read follows a write, this will occur without an + intervening stop condition */ +static int ivtv_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct ivtv *itv = i2c_get_adapdata(i2c_adap); + int retval; + int i; + + mutex_lock(&itv->i2c_bus_lock); + for (i = retval = 0; retval == 0 && i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + retval = ivtv_read(itv, msgs[i].addr, msgs[i].buf, msgs[i].len); + else { + /* if followed by a read, don't stop */ + int stop = !(i + 1 < num && msgs[i + 1].flags == I2C_M_RD); + + retval = ivtv_write(itv, msgs[i].addr, msgs[i].buf, msgs[i].len, stop); + } + } + mutex_unlock(&itv->i2c_bus_lock); + return retval ? retval : num; +} + +/* Kernel i2c capabilities */ +static u32 ivtv_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm ivtv_algo = { + .master_xfer = ivtv_xfer, + .functionality = ivtv_functionality, +}; + +/* template for our-bit banger */ +static struct i2c_adapter ivtv_i2c_adap_hw_template = { + .name = "ivtv i2c driver", + .id = I2C_HW_B_CX2341X, + .algo = &ivtv_algo, + .algo_data = NULL, /* filled from template */ + .client_register = attach_inform, + .client_unregister = detach_inform, + .owner = THIS_MODULE, +#ifdef I2C_ADAP_CLASS_TV_ANALOG + .class = I2C_ADAP_CLASS_TV_ANALOG, +#endif +}; + +static void ivtv_setscl_old(void *data, int state) +{ + struct ivtv *itv = (struct ivtv *)data; + + if (state) + itv->i2c_state |= 0x01; + else + itv->i2c_state &= ~0x01; + + /* write them out */ + /* write bits are inverted */ + write_reg(~itv->i2c_state, IVTV_REG_I2C_SETSCL_OFFSET); +} + +static void ivtv_setsda_old(void *data, int state) +{ + struct ivtv *itv = (struct ivtv *)data; + + if (state) + itv->i2c_state |= 0x01; + else + itv->i2c_state &= ~0x01; + + /* write them out */ + /* write bits are inverted */ + write_reg(~itv->i2c_state, IVTV_REG_I2C_SETSDA_OFFSET); +} + +static int ivtv_getscl_old(void *data) +{ + struct ivtv *itv = (struct ivtv *)data; + + return read_reg(IVTV_REG_I2C_GETSCL_OFFSET) & 1; +} + +static int ivtv_getsda_old(void *data) +{ + struct ivtv *itv = (struct ivtv *)data; + + return read_reg(IVTV_REG_I2C_GETSDA_OFFSET) & 1; +} + +/* template for i2c-bit-algo */ +static struct i2c_adapter ivtv_i2c_adap_template = { + .name = "ivtv i2c driver", + .id = I2C_HW_B_CX2341X, /* algo-bit is OR'd with this */ + .algo = NULL, /* set by i2c-algo-bit */ + .algo_data = NULL, /* filled from template */ + .client_register = attach_inform, + .client_unregister = detach_inform, + .owner = THIS_MODULE, +#ifdef I2C_ADAP_CLASS_TV_ANALOG + .class = I2C_ADAP_CLASS_TV_ANALOG, +#endif +}; + +static struct i2c_algo_bit_data ivtv_i2c_algo_template = { + NULL, /* ?? */ + ivtv_setsda_old, /* setsda function */ + ivtv_setscl_old, /* " */ + ivtv_getsda_old, /* " */ + ivtv_getscl_old, /* " */ + 10, /* udelay */ + 200 /* timeout */ +}; + +static struct i2c_client ivtv_i2c_client_template = { + .name = "ivtv internal", +}; + +int ivtv_call_i2c_client(struct ivtv *itv, int addr, unsigned int cmd, void *arg) +{ + struct i2c_client *client; + int retval; + int i; + + IVTV_DEBUG_I2C("call_i2c_client addr=%02x\n", addr); + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + client = itv->i2c_clients[i]; + if (client == NULL) { + continue; + } + if (client->driver->command == NULL) { + continue; + } + if (addr == client->addr) { + retval = client->driver->command(client, cmd, arg); + return retval; + } + } + if (cmd != VIDIOC_G_CHIP_IDENT) + IVTV_ERR("i2c addr 0x%02x not found for command 0x%x!\n", addr, cmd); + return -ENODEV; +} + +/* Find the i2c device based on the driver ID and return + its i2c address or -ENODEV if no matching device was found. */ +static int ivtv_i2c_id_addr(struct ivtv *itv, u32 id) +{ + struct i2c_client *client; + int retval = -ENODEV; + int i; + + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + client = itv->i2c_clients[i]; + if (client == NULL) + continue; + if (id == client->driver->id) { + retval = client->addr; + break; + } + } + return retval; +} + +/* Find the i2c device name matching the DRIVERID */ +static const char *ivtv_i2c_id_name(u32 id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hw_driverids); i++) + if (hw_driverids[i] == id) + return hw_drivernames[i]; + return "unknown device"; +} + +/* Find the i2c device name matching the IVTV_HW_ flag */ +static const char *ivtv_i2c_hw_name(u32 hw) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hw_driverids); i++) + if (1 << i == hw) + return hw_drivernames[i]; + return "unknown device"; +} + +/* Find the i2c device matching the IVTV_HW_ flag and return + its i2c address or -ENODEV if no matching device was found. */ +int ivtv_i2c_hw_addr(struct ivtv *itv, u32 hw) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hw_driverids); i++) + if (1 << i == hw) + return ivtv_i2c_id_addr(itv, hw_driverids[i]); + return -ENODEV; +} + +/* Calls i2c device based on IVTV_HW_ flag. If hw == 0, then do nothing. + If hw == IVTV_HW_GPIO then call the gpio handler. */ +int ivtv_i2c_hw(struct ivtv *itv, u32 hw, unsigned int cmd, void *arg) +{ + int addr; + + if (hw == IVTV_HW_GPIO) + return ivtv_gpio(itv, cmd, arg); + if (hw == 0) + return 0; + + addr = ivtv_i2c_hw_addr(itv, hw); + if (addr < 0) { + IVTV_ERR("i2c hardware 0x%08x (%s) not found for command 0x%x!\n", + hw, ivtv_i2c_hw_name(hw), cmd); + return addr; + } + return ivtv_call_i2c_client(itv, addr, cmd, arg); +} + +/* Calls i2c device based on I2C driver ID. */ +int ivtv_i2c_id(struct ivtv *itv, u32 id, unsigned int cmd, void *arg) +{ + int addr; + + addr = ivtv_i2c_id_addr(itv, id); + if (addr < 0) { + if (cmd != VIDIOC_G_CHIP_IDENT) + IVTV_ERR("i2c ID 0x%08x (%s) not found for command 0x%x!\n", + id, ivtv_i2c_id_name(id), cmd); + return addr; + } + return ivtv_call_i2c_client(itv, addr, cmd, arg); +} + +int ivtv_cx25840(struct ivtv *itv, unsigned int cmd, void *arg) +{ + return ivtv_call_i2c_client(itv, IVTV_CX25840_I2C_ADDR, cmd, arg); +} + +int ivtv_saa7115(struct ivtv *itv, unsigned int cmd, void *arg) +{ + return ivtv_call_i2c_client(itv, IVTV_SAA7115_I2C_ADDR, cmd, arg); +} + +int ivtv_saa7127(struct ivtv *itv, unsigned int cmd, void *arg) +{ + return ivtv_call_i2c_client(itv, IVTV_SAA7127_I2C_ADDR, cmd, arg); +} + +int ivtv_saa717x(struct ivtv *itv, unsigned int cmd, void *arg) +{ + return ivtv_call_i2c_client(itv, IVTV_SAA717x_I2C_ADDR, cmd, arg); +} + +int ivtv_upd64031a(struct ivtv *itv, unsigned int cmd, void *arg) +{ + return ivtv_call_i2c_client(itv, IVTV_UPD64031A_I2C_ADDR, cmd, arg); +} + +int ivtv_upd64083(struct ivtv *itv, unsigned int cmd, void *arg) +{ + return ivtv_call_i2c_client(itv, IVTV_UPD64083_I2C_ADDR, cmd, arg); +} + +/* broadcast cmd for all I2C clients and for the gpio subsystem */ +void ivtv_call_i2c_clients(struct ivtv *itv, unsigned int cmd, void *arg) +{ + if (itv->i2c_adap.algo == NULL) { + IVTV_ERR("adapter is not set"); + return; + } + i2c_clients_command(&itv->i2c_adap, cmd, arg); + if (itv->hw_flags & IVTV_HW_GPIO) + ivtv_gpio(itv, cmd, arg); +} + +/* init + register i2c algo-bit adapter */ +int __devinit init_ivtv_i2c(struct ivtv *itv) +{ + IVTV_DEBUG_I2C("i2c init\n"); + + if (itv->options.newi2c > 0) { + memcpy(&itv->i2c_adap, &ivtv_i2c_adap_hw_template, + sizeof(struct i2c_adapter)); + } else { + memcpy(&itv->i2c_adap, &ivtv_i2c_adap_template, + sizeof(struct i2c_adapter)); + memcpy(&itv->i2c_algo, &ivtv_i2c_algo_template, + sizeof(struct i2c_algo_bit_data)); + itv->i2c_algo.data = itv; + itv->i2c_adap.algo_data = &itv->i2c_algo; + } + + sprintf(itv->i2c_adap.name + strlen(itv->i2c_adap.name), " #%d", + itv->num); + i2c_set_adapdata(&itv->i2c_adap, itv); + + memcpy(&itv->i2c_client, &ivtv_i2c_client_template, + sizeof(struct i2c_client)); + itv->i2c_client.adapter = &itv->i2c_adap; + itv->i2c_adap.dev.parent = &itv->dev->dev; + + IVTV_DEBUG_I2C("setting scl and sda to 1\n"); + ivtv_setscl(itv, 1); + ivtv_setsda(itv, 1); + + if (itv->options.newi2c > 0) + return i2c_add_adapter(&itv->i2c_adap); + else + return i2c_bit_add_bus(&itv->i2c_adap); +} + +void __devexit exit_ivtv_i2c(struct ivtv *itv) +{ + IVTV_DEBUG_I2C("i2c exit\n"); + + i2c_del_adapter(&itv->i2c_adap); +} diff --git a/drivers/media/video/ivtv/ivtv-i2c.h b/drivers/media/video/ivtv/ivtv-i2c.h new file mode 100644 index 00000000000..5d210adb5c5 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-i2c.h @@ -0,0 +1,36 @@ +/* + I2C functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +int ivtv_cx25840(struct ivtv *itv, unsigned int cmd, void *arg); +int ivtv_saa7115(struct ivtv *itv, unsigned int cmd, void *arg); +int ivtv_saa7127(struct ivtv *itv, unsigned int cmd, void *arg); +int ivtv_saa717x(struct ivtv *itv, unsigned int cmd, void *arg); +int ivtv_upd64031a(struct ivtv *itv, unsigned int cmd, void *arg); +int ivtv_upd64083(struct ivtv *itv, unsigned int cmd, void *arg); + +int ivtv_i2c_hw_addr(struct ivtv *itv, u32 hw); +int ivtv_i2c_hw(struct ivtv *itv, u32 hw, unsigned int cmd, void *arg); +int ivtv_i2c_id(struct ivtv *itv, u32 id, unsigned int cmd, void *arg); +int ivtv_call_i2c_client(struct ivtv *itv, int addr, unsigned int cmd, void *arg); +void ivtv_call_i2c_clients(struct ivtv *itv, unsigned int cmd, void *arg); + +/* init + register i2c algo-bit adapter */ +int __devinit init_ivtv_i2c(struct ivtv *itv); +void __devexit exit_ivtv_i2c(struct ivtv *itv); diff --git a/drivers/media/video/ivtv/ivtv-ioctl.c b/drivers/media/video/ivtv/ivtv-ioctl.c new file mode 100644 index 00000000000..794a6a02f82 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-ioctl.c @@ -0,0 +1,1567 @@ +/* + ioctl system call + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-version.h" +#include "ivtv-mailbox.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-fileops.h" +#include "ivtv-vbi.h" +#include "ivtv-audio.h" +#include "ivtv-video.h" +#include "ivtv-streams.h" +#include "ivtv-yuv.h" +#include "ivtv-ioctl.h" +#include "ivtv-gpio.h" +#include "ivtv-controls.h" +#include "ivtv-cards.h" +#include <media/saa7127.h> +#include <media/tveeprom.h> +#include <media/v4l2-chip-ident.h> +#include <linux/dvb/audio.h> +#include <linux/i2c-id.h> + +u16 service2vbi(int type) +{ + switch (type) { + case V4L2_SLICED_TELETEXT_B: + return IVTV_SLICED_TYPE_TELETEXT_B; + case V4L2_SLICED_CAPTION_525: + return IVTV_SLICED_TYPE_CAPTION_525; + case V4L2_SLICED_WSS_625: + return IVTV_SLICED_TYPE_WSS_625; + case V4L2_SLICED_VPS: + return IVTV_SLICED_TYPE_VPS; + default: + return 0; + } +} + +static int valid_service_line(int field, int line, int is_pal) +{ + return (is_pal && line >= 6 && (line != 23 || field == 0)) || + (!is_pal && line >= 10 && line < 22); +} + +static u16 select_service_from_set(int field, int line, u16 set, int is_pal) +{ + u16 valid_set = (is_pal ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525); + int i; + + set = set & valid_set; + if (set == 0 || !valid_service_line(field, line, is_pal)) { + return 0; + } + if (!is_pal) { + if (line == 21 && (set & V4L2_SLICED_CAPTION_525)) + return V4L2_SLICED_CAPTION_525; + } + else { + if (line == 16 && field == 0 && (set & V4L2_SLICED_VPS)) + return V4L2_SLICED_VPS; + if (line == 23 && field == 0 && (set & V4L2_SLICED_WSS_625)) + return V4L2_SLICED_WSS_625; + if (line == 23) + return 0; + } + for (i = 0; i < 32; i++) { + if ((1 << i) & set) + return 1 << i; + } + return 0; +} + +void expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + u16 set = fmt->service_set; + int f, l; + + fmt->service_set = 0; + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, set, is_pal); + } + } +} + +static int check_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + fmt->service_lines[f][l] = select_service_from_set(f, l, fmt->service_lines[f][l], is_pal); + set |= fmt->service_lines[f][l]; + } + } + return set != 0; +} + +u16 get_service_set(struct v4l2_sliced_vbi_format *fmt) +{ + int f, l; + u16 set = 0; + + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + set |= fmt->service_lines[f][l]; + } + } + return set; +} + +static const struct { + v4l2_std_id std; + char *name; +} enum_stds[] = { + { V4L2_STD_PAL_BG | V4L2_STD_PAL_H, "PAL-BGH" }, + { V4L2_STD_PAL_DK, "PAL-DK" }, + { V4L2_STD_PAL_I, "PAL-I" }, + { V4L2_STD_PAL_M, "PAL-M" }, + { V4L2_STD_PAL_N, "PAL-N" }, + { V4L2_STD_PAL_Nc, "PAL-Nc" }, + { V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H, "SECAM-BGH" }, + { V4L2_STD_SECAM_DK, "SECAM-DK" }, + { V4L2_STD_SECAM_L, "SECAM-L" }, + { V4L2_STD_SECAM_LC, "SECAM-L'" }, + { V4L2_STD_NTSC_M, "NTSC-M" }, + { V4L2_STD_NTSC_M_JP, "NTSC-J" }, + { V4L2_STD_NTSC_M_KR, "NTSC-K" }, +}; + +static const struct v4l2_standard ivtv_std_60hz = +{ + .frameperiod = {.numerator = 1001, .denominator = 30000}, + .framelines = 525, +}; + +static const struct v4l2_standard ivtv_std_50hz = +{ + .frameperiod = {.numerator = 1, .denominator = 25}, + .framelines = 625, +}; + +void ivtv_set_osd_alpha(struct ivtv *itv) +{ + ivtv_vapi(itv, CX2341X_OSD_SET_GLOBAL_ALPHA, 3, + itv->osd_global_alpha_state, itv->osd_global_alpha, !itv->osd_local_alpha_state); + ivtv_vapi(itv, CX2341X_OSD_SET_CHROMA_KEY, 2, itv->osd_color_key_state, itv->osd_color_key); +} + +int ivtv_set_speed(struct ivtv *itv, int speed) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + int single_step = (speed == 1 || speed == -1); + DEFINE_WAIT(wait); + + if (speed == 0) speed = 1000; + + /* No change? */ + if (speed == itv->speed && !single_step) + return 0; + + s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + + if (single_step && (speed < 0) == (itv->speed < 0)) { + /* Single step video and no need to change direction */ + ivtv_vapi(itv, CX2341X_DEC_STEP_VIDEO, 1, 0); + itv->speed = speed; + return 0; + } + if (single_step) + /* Need to change direction */ + speed = speed < 0 ? -1000 : 1000; + + data[0] = (speed > 1000 || speed < -1000) ? 0x80000000 : 0; + data[0] |= (speed > 1000 || speed < -1500) ? 0x40000000 : 0; + data[1] = (speed < 0); + data[2] = speed < 0 ? 3 : 7; + data[3] = itv->params.video_b_frames; + data[4] = (speed == 1500 || speed == 500) ? itv->speed_mute_audio : 0; + data[5] = 0; + data[6] = 0; + + if (speed == 1500 || speed == -1500) data[0] |= 1; + else if (speed == 2000 || speed == -2000) data[0] |= 2; + else if (speed > -1000 && speed < 0) data[0] |= (-1000 / speed); + else if (speed < 1000 && speed > 0) data[0] |= (1000 / speed); + + /* If not decoding, just change speed setting */ + if (atomic_read(&itv->decoding) > 0) { + int got_sig = 0; + + /* Stop all DMA and decoding activity */ + ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, 0); + + /* Wait for any DMA to finish */ + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + while (itv->i_flags & IVTV_F_I_DMA) { + got_sig = signal_pending(current); + if (got_sig) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + if (got_sig) + return -EINTR; + + /* Change Speed safely */ + ivtv_api(itv, CX2341X_DEC_SET_PLAYBACK_SPEED, 7, data); + IVTV_DEBUG_INFO("Setting Speed to 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", + data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + } + if (single_step) { + speed = (speed < 0) ? -1 : 1; + ivtv_vapi(itv, CX2341X_DEC_STEP_VIDEO, 1, 0); + } + itv->speed = speed; + return 0; +} + +static int ivtv_validate_speed(int cur_speed, int new_speed) +{ + int fact = new_speed < 0 ? -1 : 1; + int s; + + if (new_speed < 0) new_speed = -new_speed; + if (cur_speed < 0) cur_speed = -cur_speed; + + if (cur_speed <= new_speed) { + if (new_speed > 1500) return fact * 2000; + if (new_speed > 1000) return fact * 1500; + } + else { + if (new_speed >= 2000) return fact * 2000; + if (new_speed >= 1500) return fact * 1500; + if (new_speed >= 1000) return fact * 1000; + } + if (new_speed == 0) return 1000; + if (new_speed == 1 || new_speed == 1000) return fact * new_speed; + + s = new_speed; + new_speed = 1000 / new_speed; + if (1000 / cur_speed == new_speed) + new_speed += (cur_speed < s) ? -1 : 1; + if (new_speed > 60) return 1000 / (fact * 60); + return 1000 / (fact * new_speed); +} + +static int ivtv_video_command(struct ivtv *itv, struct ivtv_open_id *id, + struct video_command *vc, int try) +{ + struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + switch (vc->cmd) { + case VIDEO_CMD_PLAY: { + vc->flags = 0; + vc->play.speed = ivtv_validate_speed(itv->speed, vc->play.speed); + if (vc->play.speed < 0) + vc->play.format = VIDEO_PLAY_FMT_GOP; + if (try) break; + + if (ivtv_set_output_mode(itv, OUT_MPG) != OUT_MPG) + return -EBUSY; + return ivtv_start_decoding(id, vc->play.speed); + } + + case VIDEO_CMD_STOP: + vc->flags &= VIDEO_CMD_STOP_IMMEDIATELY|VIDEO_CMD_STOP_TO_BLACK; + if (vc->flags & VIDEO_CMD_STOP_IMMEDIATELY) + vc->stop.pts = 0; + if (try) break; + if (atomic_read(&itv->decoding) == 0) + return 0; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + + itv->output_mode = OUT_NONE; + return ivtv_stop_v4l2_decode_stream(s, vc->flags, vc->stop.pts); + + case VIDEO_CMD_FREEZE: + vc->flags &= VIDEO_CMD_FREEZE_TO_BLACK; + if (try) break; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + if (atomic_read(&itv->decoding) > 0) { + ivtv_vapi(itv, CX2341X_DEC_PAUSE_PLAYBACK, 1, + (vc->flags & VIDEO_CMD_FREEZE_TO_BLACK) ? 1 : 0); + } + break; + + case VIDEO_CMD_CONTINUE: + vc->flags = 0; + if (try) break; + if (itv->output_mode != OUT_MPG) + return -EBUSY; + if (atomic_read(&itv->decoding) > 0) { + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 0); + } + break; + + default: + return -EINVAL; + } + return 0; +} + +static int ivtv_itvc(struct ivtv *itv, unsigned int cmd, void *arg) +{ + struct v4l2_register *regs = arg; + unsigned long flags; + volatile u8 __iomem *reg_start; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (regs->reg >= IVTV_REG_OFFSET && regs->reg < IVTV_REG_OFFSET + IVTV_REG_SIZE) + reg_start = itv->reg_mem - IVTV_REG_OFFSET; + else if (itv->has_cx23415 && regs->reg >= IVTV_DECODER_OFFSET && + regs->reg < IVTV_DECODER_OFFSET + IVTV_DECODER_SIZE) + reg_start = itv->dec_mem - IVTV_DECODER_OFFSET; + else if (regs->reg >= 0 && regs->reg < IVTV_ENCODER_SIZE) + reg_start = itv->enc_mem; + else + return -EINVAL; + + spin_lock_irqsave(&ivtv_cards_lock, flags); + if (cmd == VIDIOC_DBG_G_REGISTER) { + regs->val = readl(regs->reg + reg_start); + } else { + writel(regs->val, regs->reg + reg_start); + } + spin_unlock_irqrestore(&ivtv_cards_lock, flags); + return 0; +} + +static int ivtv_get_fmt(struct ivtv *itv, int streamtype, struct v4l2_format *fmt) +{ + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + fmt->fmt.pix.left = itv->main_rect.left; + fmt->fmt.pix.top = itv->main_rect.top; + fmt->fmt.pix.width = itv->main_rect.width; + fmt->fmt.pix.height = itv->main_rect.height; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + if (itv->output_mode == OUT_UDMA_YUV) { + switch (itv->yuv_info.lace_mode & IVTV_YUV_MODE_MASK) { + case IVTV_YUV_MODE_INTERLACED: + fmt->fmt.pix.field = (itv->yuv_info.lace_mode & IVTV_YUV_SYNC_MASK) ? + V4L2_FIELD_INTERLACED_BT : V4L2_FIELD_INTERLACED_TB; + break; + case IVTV_YUV_MODE_PROGRESSIVE: + fmt->fmt.pix.field = V4L2_FIELD_NONE; + break; + default: + fmt->fmt.pix.field = V4L2_FIELD_ANY; + break; + } + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_HM12; + /* YUV size is (Y=(h*w) + UV=(h*(w/2))) */ + fmt->fmt.pix.sizeimage = + fmt->fmt.pix.height * fmt->fmt.pix.width + + fmt->fmt.pix.height * (fmt->fmt.pix.width / 2); + } + else if (itv->output_mode == OUT_YUV || + streamtype == IVTV_ENC_STREAM_TYPE_YUV || + streamtype == IVTV_DEC_STREAM_TYPE_YUV) { + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_HM12; + /* YUV size is (Y=(h*w) + UV=(h*(w/2))) */ + fmt->fmt.pix.sizeimage = + fmt->fmt.pix.height * fmt->fmt.pix.width + + fmt->fmt.pix.height * (fmt->fmt.pix.width / 2); + } else { + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + fmt->fmt.pix.sizeimage = 128 * 1024; + } + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + fmt->fmt.pix.left = 0; + fmt->fmt.pix.top = 0; + fmt->fmt.pix.width = itv->params.width; + fmt->fmt.pix.height = itv->params.height; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt->fmt.pix.field = V4L2_FIELD_INTERLACED; + if (streamtype == IVTV_ENC_STREAM_TYPE_YUV || + streamtype == IVTV_DEC_STREAM_TYPE_YUV) { + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_HM12; + /* YUV size is (Y=(h*w) + UV=(h*(w/2))) */ + fmt->fmt.pix.sizeimage = + fmt->fmt.pix.height * fmt->fmt.pix.width + + fmt->fmt.pix.height * (fmt->fmt.pix.width / 2); + } else { + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + fmt->fmt.pix.sizeimage = 128 * 1024; + } + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + fmt->fmt.win.chromakey = itv->osd_color_key; + fmt->fmt.win.global_alpha = itv->osd_global_alpha; + break; + + case V4L2_BUF_TYPE_VBI_CAPTURE: + fmt->fmt.vbi.sampling_rate = 27000000; + fmt->fmt.vbi.offset = 248; + fmt->fmt.vbi.samples_per_line = itv->vbi.raw_decoder_line_size - 4; + fmt->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + fmt->fmt.vbi.start[0] = itv->vbi.start[0]; + fmt->fmt.vbi.start[1] = itv->vbi.start[1]; + fmt->fmt.vbi.count[0] = fmt->fmt.vbi.count[1] = itv->vbi.count; + break; + + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + { + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_OUTPUT)) + return -EINVAL; + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->reserved, 0, sizeof(vbifmt->reserved)); + memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines)); + if (itv->is_60hz) { + vbifmt->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + vbifmt->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } else { + vbifmt->service_lines[0][23] = V4L2_SLICED_WSS_625; + vbifmt->service_lines[0][16] = V4L2_SLICED_VPS; + } + vbifmt->service_set = get_service_set(vbifmt); + break; + } + + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + { + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->reserved, 0, sizeof(vbifmt->reserved)); + memset(vbifmt->service_lines, 0, sizeof(vbifmt->service_lines)); + + if (streamtype == IVTV_DEC_STREAM_TYPE_VBI) { + vbifmt->service_set = itv->is_50hz ? V4L2_SLICED_VBI_625 : + V4L2_SLICED_VBI_525; + expand_service_set(vbifmt, itv->is_50hz); + break; + } + + itv->video_dec_func(itv, VIDIOC_G_FMT, fmt); + vbifmt->service_set = get_service_set(vbifmt); + break; + } + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + return 0; +} + +static int ivtv_try_or_set_fmt(struct ivtv *itv, int streamtype, + struct v4l2_format *fmt, int set_fmt) +{ + struct v4l2_sliced_vbi_format *vbifmt = &fmt->fmt.sliced; + u16 set; + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + struct v4l2_rect r; + int field; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + field = fmt->fmt.pix.field; + r.top = fmt->fmt.pix.top; + r.left = fmt->fmt.pix.left; + r.width = fmt->fmt.pix.width; + r.height = fmt->fmt.pix.height; + ivtv_get_fmt(itv, streamtype, fmt); + if (itv->output_mode != OUT_UDMA_YUV) { + /* TODO: would setting the rect also be valid for this mode? */ + fmt->fmt.pix.top = r.top; + fmt->fmt.pix.left = r.left; + fmt->fmt.pix.width = r.width; + fmt->fmt.pix.height = r.height; + } + if (itv->output_mode == OUT_UDMA_YUV) { + /* TODO: add checks for validity */ + fmt->fmt.pix.field = field; + } + if (set_fmt) { + if (itv->output_mode == OUT_UDMA_YUV) { + switch (field) { + case V4L2_FIELD_NONE: + itv->yuv_info.lace_mode = IVTV_YUV_MODE_PROGRESSIVE; + break; + case V4L2_FIELD_ANY: + itv->yuv_info.lace_mode = IVTV_YUV_MODE_AUTO; + break; + case V4L2_FIELD_INTERLACED_BT: + itv->yuv_info.lace_mode = + IVTV_YUV_MODE_INTERLACED|IVTV_YUV_SYNC_ODD; + break; + case V4L2_FIELD_INTERLACED_TB: + default: + itv->yuv_info.lace_mode = IVTV_YUV_MODE_INTERLACED; + break; + } + itv->yuv_info.lace_sync_field = (itv->yuv_info.lace_mode & IVTV_YUV_SYNC_MASK) == IVTV_YUV_SYNC_EVEN ? 0 : 1; + + /* Force update of yuv registers */ + itv->yuv_info.yuv_forced_update = 1; + return 0; + } + if (!ivtv_vapi(itv, CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, 4, + r.width, r.height, r.left, r.top)) + itv->main_rect = r; + else + return -EINVAL; + } + return 0; + } + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) { + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + if (set_fmt) { + itv->osd_color_key = fmt->fmt.win.chromakey; + itv->osd_global_alpha = fmt->fmt.win.global_alpha; + ivtv_set_osd_alpha(itv); + } + return 0; + } + + /* set window size */ + if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + int w = fmt->fmt.pix.width; + int h = fmt->fmt.pix.height; + + if (w > 720) w = 720; + else if (w < 1) w = 1; + if (h > (itv->is_50hz ? 576 : 480)) h = (itv->is_50hz ? 576 : 480); + else if (h < 2) h = 2; + ivtv_get_fmt(itv, streamtype, fmt); + fmt->fmt.pix.width = w; + fmt->fmt.pix.height = h; + + if (!set_fmt || (itv->params.width == w && itv->params.height == h)) + return 0; + if (atomic_read(&itv->capturing) > 0) + return -EBUSY; + + itv->params.width = w; + itv->params.height = h; + if (w != 720 || h != (itv->is_50hz ? 576 : 480)) + itv->params.video_temporal_filter = 0; + else + itv->params.video_temporal_filter = 8; + itv->video_dec_func(itv, VIDIOC_S_FMT, fmt); + return ivtv_get_fmt(itv, streamtype, fmt); + } + + /* set raw VBI format */ + if (fmt->type == V4L2_BUF_TYPE_VBI_CAPTURE) { + if (set_fmt && streamtype == IVTV_ENC_STREAM_TYPE_VBI && + itv->vbi.sliced_in->service_set && + atomic_read(&itv->capturing) > 0) { + return -EBUSY; + } + if (set_fmt) { + itv->vbi.sliced_in->service_set = 0; + itv->video_dec_func(itv, VIDIOC_S_FMT, &itv->vbi.in); + } + return ivtv_get_fmt(itv, streamtype, fmt); + } + + /* set sliced VBI output + In principle the user could request that only certain + VBI types are output and that the others are ignored. + I.e., suppress CC in the even fields or only output + WSS and no VPS. Currently though there is no choice. */ + if (fmt->type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) + return ivtv_get_fmt(itv, streamtype, fmt); + + /* any else but sliced VBI capture is an error */ + if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + + if (streamtype == IVTV_DEC_STREAM_TYPE_VBI) + return ivtv_get_fmt(itv, streamtype, fmt); + + /* set sliced VBI capture format */ + vbifmt->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36; + memset(vbifmt->reserved, 0, sizeof(vbifmt->reserved)); + + if (vbifmt->service_set) + expand_service_set(vbifmt, itv->is_50hz); + set = check_service_set(vbifmt, itv->is_50hz); + vbifmt->service_set = get_service_set(vbifmt); + + if (!set_fmt) + return 0; + if (set == 0) + return -EINVAL; + if (atomic_read(&itv->capturing) > 0 && itv->vbi.sliced_in->service_set == 0) { + return -EBUSY; + } + itv->video_dec_func(itv, VIDIOC_S_FMT, fmt); + memcpy(itv->vbi.sliced_in, vbifmt, sizeof(*itv->vbi.sliced_in)); + return 0; +} + +static int ivtv_debug_ioctls(struct file *filp, unsigned int cmd, void *arg) +{ + struct ivtv_open_id *id = (struct ivtv_open_id *)filp->private_data; + struct ivtv *itv = id->itv; + struct v4l2_register *reg = arg; + + switch (cmd) { + /* ioctls to allow direct access to the encoder registers for testing */ + case VIDIOC_DBG_G_REGISTER: + if (v4l2_chip_match_host(reg->match_type, reg->match_chip)) + return ivtv_itvc(itv, cmd, arg); + if (reg->match_type == V4L2_CHIP_MATCH_I2C_DRIVER) + return ivtv_i2c_id(itv, reg->match_chip, cmd, arg); + return ivtv_call_i2c_client(itv, reg->match_chip, cmd, arg); + + case VIDIOC_DBG_S_REGISTER: + if (v4l2_chip_match_host(reg->match_type, reg->match_chip)) + return ivtv_itvc(itv, cmd, arg); + if (reg->match_type == V4L2_CHIP_MATCH_I2C_DRIVER) + return ivtv_i2c_id(itv, reg->match_chip, cmd, arg); + return ivtv_call_i2c_client(itv, reg->match_chip, cmd, arg); + + case VIDIOC_G_CHIP_IDENT: { + struct v4l2_chip_ident *chip = arg; + + chip->ident = V4L2_IDENT_NONE; + chip->revision = 0; + if (reg->match_type == V4L2_CHIP_MATCH_HOST) { + if (v4l2_chip_match_host(reg->match_type, reg->match_chip)) { + struct v4l2_chip_ident *chip = arg; + + chip->ident = itv->has_cx23415 ? V4L2_IDENT_CX23415 : V4L2_IDENT_CX23416; + } + return 0; + } + if (reg->match_type == V4L2_CHIP_MATCH_I2C_DRIVER) + return ivtv_i2c_id(itv, reg->match_chip, cmd, arg); + if (reg->match_type == V4L2_CHIP_MATCH_I2C_ADDR) + return ivtv_call_i2c_client(itv, reg->match_chip, cmd, arg); + return -EINVAL; + } + + case VIDIOC_INT_S_AUDIO_ROUTING: { + struct v4l2_routing *route = arg; + + ivtv_audio_set_route(itv, route); + break; + } + + case VIDIOC_INT_RESET: + ivtv_reset_ir_gpio(itv); + break; + + default: + return -EINVAL; + } + return 0; +} + +int ivtv_v4l2_ioctls(struct ivtv *itv, struct file *filp, unsigned int cmd, void *arg) +{ + struct ivtv_open_id *id = NULL; + + if (filp) id = (struct ivtv_open_id *)filp->private_data; + + switch (cmd) { + case VIDIOC_G_PRIORITY: + { + enum v4l2_priority *p = arg; + + *p = v4l2_prio_max(&itv->prio); + break; + } + + case VIDIOC_S_PRIORITY: + { + enum v4l2_priority *prio = arg; + + return v4l2_prio_change(&itv->prio, &id->prio, *prio); + } + + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *vcap = arg; + + memset(vcap, 0, sizeof(*vcap)); + strcpy(vcap->driver, IVTV_DRIVER_NAME); /* driver name */ + strcpy(vcap->card, itv->card_name); /* card type */ + strcpy(vcap->bus_info, pci_name(itv->dev)); /* bus info... */ + vcap->version = IVTV_DRIVER_VERSION; /* version */ + vcap->capabilities = itv->v4l2_cap; /* capabilities */ + + /* reserved.. must set to 0! */ + vcap->reserved[0] = vcap->reserved[1] = + vcap->reserved[2] = vcap->reserved[3] = 0; + break; + } + + case VIDIOC_ENUMAUDIO:{ + struct v4l2_audio *vin = arg; + + return ivtv_get_audio_input(itv, vin->index, vin); + } + + case VIDIOC_G_AUDIO:{ + struct v4l2_audio *vin = arg; + + vin->index = itv->audio_input; + return ivtv_get_audio_input(itv, vin->index, vin); + } + + case VIDIOC_S_AUDIO:{ + struct v4l2_audio *vout = arg; + + if (vout->index >= itv->nof_audio_inputs) + return -EINVAL; + itv->audio_input = vout->index; + ivtv_audio_set_io(itv); + break; + } + + case VIDIOC_ENUMAUDOUT:{ + struct v4l2_audioout *vin = arg; + + /* set it to defaults from our table */ + return ivtv_get_audio_output(itv, vin->index, vin); + } + + case VIDIOC_G_AUDOUT:{ + struct v4l2_audioout *vin = arg; + + vin->index = 0; + return ivtv_get_audio_output(itv, vin->index, vin); + } + + case VIDIOC_S_AUDOUT:{ + struct v4l2_audioout *vout = arg; + + return ivtv_get_audio_output(itv, vout->index, vout); + } + + case VIDIOC_ENUMINPUT:{ + struct v4l2_input *vin = arg; + + /* set it to defaults from our table */ + return ivtv_get_input(itv, vin->index, vin); + } + + case VIDIOC_ENUMOUTPUT:{ + struct v4l2_output *vout = arg; + + return ivtv_get_output(itv, vout->index, vout); + } + + case VIDIOC_TRY_FMT: + case VIDIOC_S_FMT: { + struct v4l2_format *fmt = arg; + + return ivtv_try_or_set_fmt(itv, id->type, fmt, cmd == VIDIOC_S_FMT); + } + + case VIDIOC_G_FMT: { + struct v4l2_format *fmt = arg; + int type = fmt->type; + + memset(fmt, 0, sizeof(*fmt)); + fmt->type = type; + return ivtv_get_fmt(itv, id->type, fmt); + } + + case VIDIOC_S_CROP: { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + return itv->video_dec_func(itv, VIDIOC_S_CROP, arg); + } + + case VIDIOC_G_CROP: { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + return itv->video_dec_func(itv, VIDIOC_G_CROP, arg); + } + + case VIDIOC_ENUM_FMT: { + static struct v4l2_fmtdesc formats[] = { + { 0, 0, 0, + "HM12 (YUV 4:1:1)", V4L2_PIX_FMT_HM12, + { 0, 0, 0, 0 } + }, + { 1, 0, V4L2_FMT_FLAG_COMPRESSED, + "MPEG", V4L2_PIX_FMT_MPEG, + { 0, 0, 0, 0 } + } + }; + struct v4l2_fmtdesc *fmt = arg; + enum v4l2_buf_type type = fmt->type; + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + break; + default: + return -EINVAL; + } + if (fmt->index > 1) + return -EINVAL; + *fmt = formats[fmt->index]; + fmt->type = type; + return 0; + } + + case VIDIOC_G_INPUT:{ + *(int *)arg = itv->active_input; + break; + } + + case VIDIOC_S_INPUT:{ + int inp = *(int *)arg; + + if (inp < 0 || inp >= itv->nof_inputs) + return -EINVAL; + + if (inp == itv->active_input) { + IVTV_DEBUG_INFO("Input unchanged\n"); + break; + } + IVTV_DEBUG_INFO("Changing input from %d to %d\n", + itv->active_input, inp); + + itv->active_input = inp; + /* Set the audio input to whatever is appropriate for the + input type. */ + itv->audio_input = itv->card->video_inputs[inp].audio_index; + + /* prevent others from messing with the streams until + we're finished changing inputs. */ + ivtv_mute(itv); + ivtv_video_set_io(itv); + ivtv_audio_set_io(itv); + ivtv_unmute(itv); + break; + } + + case VIDIOC_G_OUTPUT:{ + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + *(int *)arg = itv->active_output; + break; + } + + case VIDIOC_S_OUTPUT:{ + int outp = *(int *)arg; + struct v4l2_routing route; + + if (outp >= itv->card->nof_outputs) + return -EINVAL; + + if (outp == itv->active_output) { + IVTV_DEBUG_INFO("Output unchanged\n"); + break; + } + IVTV_DEBUG_INFO("Changing output from %d to %d\n", + itv->active_output, outp); + + itv->active_output = outp; + route.input = SAA7127_INPUT_TYPE_NORMAL; + route.output = itv->card->video_outputs[outp].video_output; + ivtv_saa7127(itv, VIDIOC_INT_S_VIDEO_ROUTING, &route); + break; + } + + case VIDIOC_G_FREQUENCY:{ + struct v4l2_frequency *vf = arg; + + if (vf->tuner != 0) + return -EINVAL; + ivtv_call_i2c_clients(itv, cmd, arg); + break; + } + + case VIDIOC_S_FREQUENCY:{ + struct v4l2_frequency vf = *(struct v4l2_frequency *)arg; + + if (vf.tuner != 0) + return -EINVAL; + + ivtv_mute(itv); + IVTV_DEBUG_INFO("v4l2 ioctl: set frequency %d\n", vf.frequency); + ivtv_call_i2c_clients(itv, cmd, &vf); + ivtv_unmute(itv); + break; + } + + case VIDIOC_ENUMSTD:{ + struct v4l2_standard *vs = arg; + int idx = vs->index; + + if (idx < 0 || idx >= ARRAY_SIZE(enum_stds)) + return -EINVAL; + + *vs = (enum_stds[idx].std & V4L2_STD_525_60) ? + ivtv_std_60hz : ivtv_std_50hz; + vs->index = idx; + vs->id = enum_stds[idx].std; + strcpy(vs->name, enum_stds[idx].name); + break; + } + + case VIDIOC_G_STD:{ + *(v4l2_std_id *) arg = itv->std; + break; + } + + case VIDIOC_S_STD: { + v4l2_std_id std = *(v4l2_std_id *) arg; + + if ((std & V4L2_STD_ALL) == 0) + return -EINVAL; + + if (std == itv->std) + break; + + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) || + atomic_read(&itv->capturing) > 0 || + atomic_read(&itv->decoding) > 0) { + /* Switching standard would turn off the radio or mess + with already running streams, prevent that by + returning EBUSY. */ + return -EBUSY; + } + + itv->std = std; + itv->is_60hz = (std & V4L2_STD_525_60) ? 1 : 0; + itv->params.is_50hz = itv->is_50hz = !itv->is_60hz; + itv->params.width = 720; + itv->params.height = itv->is_50hz ? 576 : 480; + itv->vbi.count = itv->is_50hz ? 18 : 12; + itv->vbi.start[0] = itv->is_50hz ? 6 : 10; + itv->vbi.start[1] = itv->is_50hz ? 318 : 273; + if (itv->hw_flags & IVTV_HW_CX25840) { + itv->vbi.sliced_decoder_line_size = itv->is_60hz ? 272 : 284; + } + IVTV_DEBUG_INFO("Switching standard to %llx.\n", itv->std); + + /* Tuner */ + ivtv_call_i2c_clients(itv, VIDIOC_S_STD, &itv->std); + + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + /* set display standard */ + itv->std_out = std; + itv->is_out_60hz = itv->is_60hz; + itv->is_out_50hz = itv->is_50hz; + ivtv_call_i2c_clients(itv, VIDIOC_INT_S_STD_OUTPUT, &itv->std_out); + ivtv_vapi(itv, CX2341X_DEC_SET_STANDARD, 1, itv->is_out_50hz); + itv->main_rect.left = itv->main_rect.top = 0; + itv->main_rect.width = 720; + itv->main_rect.height = itv->params.height; + ivtv_vapi(itv, CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, 4, + 720, itv->main_rect.height, 0, 0); + } + break; + } + + case VIDIOC_S_TUNER: { /* Setting tuner can only set audio mode */ + struct v4l2_tuner *vt = arg; + + if (vt->index != 0) + return -EINVAL; + + ivtv_call_i2c_clients(itv, VIDIOC_S_TUNER, vt); + break; + } + + case VIDIOC_G_TUNER: { + struct v4l2_tuner *vt = arg; + + if (vt->index != 0) + return -EINVAL; + + memset(vt, 0, sizeof(*vt)); + ivtv_call_i2c_clients(itv, VIDIOC_G_TUNER, vt); + + if (test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags)) { + strcpy(vt->name, "ivtv Radio Tuner"); + vt->type = V4L2_TUNER_RADIO; + } else { + strcpy(vt->name, "ivtv TV Tuner"); + vt->type = V4L2_TUNER_ANALOG_TV; + } + break; + } + + case VIDIOC_G_SLICED_VBI_CAP: { + struct v4l2_sliced_vbi_cap *cap = arg; + int set = itv->is_50hz ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525; + int f, l; + enum v4l2_buf_type type = cap->type; + + memset(cap, 0, sizeof(*cap)); + cap->type = type; + if (type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { + for (f = 0; f < 2; f++) { + for (l = 0; l < 24; l++) { + if (valid_service_line(f, l, itv->is_50hz)) { + cap->service_lines[f][l] = set; + } + } + } + return 0; + } + if (type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) { + if (!(itv->v4l2_cap & V4L2_CAP_SLICED_VBI_OUTPUT)) + return -EINVAL; + if (itv->is_60hz) { + cap->service_lines[0][21] = V4L2_SLICED_CAPTION_525; + cap->service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } else { + cap->service_lines[0][23] = V4L2_SLICED_WSS_625; + cap->service_lines[0][16] = V4L2_SLICED_VPS; + } + return 0; + } + return -EINVAL; + } + + case VIDIOC_G_ENC_INDEX: { + struct v4l2_enc_idx *idx = arg; + int i; + + idx->entries = (itv->pgm_info_write_idx + IVTV_MAX_PGM_INDEX - itv->pgm_info_read_idx) % + IVTV_MAX_PGM_INDEX; + if (idx->entries > V4L2_ENC_IDX_ENTRIES) + idx->entries = V4L2_ENC_IDX_ENTRIES; + for (i = 0; i < idx->entries; i++) { + idx->entry[i] = itv->pgm_info[(itv->pgm_info_read_idx + i) % IVTV_MAX_PGM_INDEX]; + } + itv->pgm_info_read_idx = (itv->pgm_info_read_idx + idx->entries) % IVTV_MAX_PGM_INDEX; + break; + } + + case VIDIOC_ENCODER_CMD: + case VIDIOC_TRY_ENCODER_CMD: { + struct v4l2_encoder_cmd *enc = arg; + int try = cmd == VIDIOC_TRY_ENCODER_CMD; + + memset(&enc->raw, 0, sizeof(enc->raw)); + switch (enc->cmd) { + case V4L2_ENC_CMD_START: + enc->flags = 0; + if (try) + return 0; + return ivtv_start_capture(id); + + case V4L2_ENC_CMD_STOP: + enc->flags &= V4L2_ENC_CMD_STOP_AT_GOP_END; + if (try) + return 0; + ivtv_stop_capture(id, enc->flags & V4L2_ENC_CMD_STOP_AT_GOP_END); + return 0; + + case V4L2_ENC_CMD_PAUSE: + enc->flags = 0; + if (try) + return 0; + if (!atomic_read(&itv->capturing)) + return -EPERM; + if (test_and_set_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + return 0; + ivtv_mute(itv); + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 0); + break; + + case V4L2_ENC_CMD_RESUME: + enc->flags = 0; + if (try) + return 0; + if (!atomic_read(&itv->capturing)) + return -EPERM; + if (!test_and_clear_bit(IVTV_F_I_ENC_PAUSED, &itv->i_flags)) + return 0; + ivtv_vapi(itv, CX2341X_ENC_PAUSE_ENCODER, 1, 1); + ivtv_unmute(itv); + break; + default: + return -EINVAL; + } + break; + } + + case VIDIOC_G_FBUF: { + struct v4l2_framebuffer *fb = arg; + + memset(fb, 0, sizeof(*fb)); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + break; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_LOCAL_ALPHA | V4L2_FBUF_CAP_GLOBAL_ALPHA; + fb->fmt.pixelformat = itv->osd_pixelformat; + fb->fmt.width = itv->osd_rect.width; + fb->fmt.height = itv->osd_rect.height; + fb->fmt.left = itv->osd_rect.left; + fb->fmt.top = itv->osd_rect.top; + fb->base = (void *)itv->osd_video_pbase; + if (itv->osd_global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + if (itv->osd_local_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; + if (itv->osd_color_key_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + break; + } + + case VIDIOC_S_FBUF: { + struct v4l2_framebuffer *fb = arg; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)) + break; + itv->osd_global_alpha_state = (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + itv->osd_local_alpha_state = (fb->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) != 0; + itv->osd_color_key_state = (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + break; + } + + case VIDIOC_LOG_STATUS: + { + int has_output = itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT; + struct v4l2_input vidin; + struct v4l2_audio audin; + int i; + + IVTV_INFO("================= START STATUS CARD #%d =================\n", itv->num); + if (itv->hw_flags & IVTV_HW_TVEEPROM) { + struct tveeprom tv; + + ivtv_read_eeprom(itv, &tv); + } + ivtv_call_i2c_clients(itv, VIDIOC_LOG_STATUS, NULL); + ivtv_get_input(itv, itv->active_input, &vidin); + ivtv_get_audio_input(itv, itv->audio_input, &audin); + IVTV_INFO("Video Input: %s\n", vidin.name); + IVTV_INFO("Audio Input: %s\n", audin.name); + if (has_output) { + struct v4l2_output vidout; + struct v4l2_audioout audout; + int mode = itv->output_mode; + static const char * const output_modes[] = { + "None", + "MPEG Streaming", + "YUV Streaming", + "YUV Frames", + "Passthrough", + }; + + ivtv_get_output(itv, itv->active_output, &vidout); + ivtv_get_audio_output(itv, 0, &audout); + IVTV_INFO("Video Output: %s\n", vidout.name); + IVTV_INFO("Audio Output: %s\n", audout.name); + if (mode < 0 || mode > OUT_PASSTHROUGH) + mode = OUT_NONE; + IVTV_INFO("Output Mode: %s\n", output_modes[mode]); + } + IVTV_INFO("Tuner: %s\n", + test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) ? "Radio" : "TV"); + cx2341x_log_status(&itv->params, itv->name); + IVTV_INFO("Status flags: 0x%08lx\n", itv->i_flags); + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + struct ivtv_stream *s = &itv->streams[i]; + + if (s->v4l2dev == NULL || s->buffers == 0) + continue; + IVTV_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n", s->name, s->s_flags, + (s->buffers - s->q_free.buffers) * 100 / s->buffers, + (s->buffers * s->buf_size) / 1024, s->buffers); + } + IVTV_INFO("Read MPEG/VBI: %lld/%lld bytes\n", itv->mpg_data_received, itv->vbi_data_inserted); + IVTV_INFO("================== END STATUS CARD #%d ==================\n", itv->num); + break; + } + + default: + return -EINVAL; + } + return 0; +} + +static int ivtv_decoder_ioctls(struct file *filp, unsigned int cmd, void *arg) +{ + struct ivtv_open_id *id = (struct ivtv_open_id *)filp->private_data; + struct ivtv *itv = id->itv; + int nonblocking = filp->f_flags & O_NONBLOCK; + struct ivtv_stream *s = &itv->streams[id->type]; + + switch (cmd) { + case IVTV_IOC_DMA_FRAME: { + struct ivtv_dma_frame *args = arg; + + IVTV_DEBUG_IOCTL("IVTV_IOC_DMA_FRAME\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + if (args->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + if (itv->output_mode == OUT_UDMA_YUV && args->y_source == NULL) + return 0; + if (ivtv_claim_stream(id, id->type)) { + return -EBUSY; + } + if (ivtv_set_output_mode(itv, OUT_UDMA_YUV) != OUT_UDMA_YUV) { + ivtv_release_stream(s); + return -EBUSY; + } + if (args->y_source == NULL) + return 0; + return ivtv_yuv_prep_frame(itv, args); + } + + case VIDEO_GET_PTS: { + u32 data[CX2341X_MBOX_MAX_DATA]; + u64 *pts = arg; + + IVTV_DEBUG_IOCTL("VIDEO_GET_PTS\n"); + if (s->type < IVTV_DEC_STREAM_TYPE_MPG) { + *pts = s->dma_pts; + break; + } + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + if (test_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags)) { + *pts = (u64) ((u64)itv->last_dec_timing[2] << 32) | + (u64)itv->last_dec_timing[1]; + break; + } + *pts = 0; + if (atomic_read(&itv->decoding)) { + if (ivtv_api(itv, CX2341X_DEC_GET_TIMING_INFO, 5, data)) { + IVTV_DEBUG_WARN("GET_TIMING: couldn't read clock\n"); + return -EIO; + } + memcpy(itv->last_dec_timing, data, sizeof(itv->last_dec_timing)); + set_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + *pts = (u64) ((u64) data[2] << 32) | (u64) data[1]; + /*timing->scr = (u64) (((u64) data[4] << 32) | (u64) (data[3]));*/ + } + break; + } + + case VIDEO_GET_FRAME_COUNT: { + u32 data[CX2341X_MBOX_MAX_DATA]; + u64 *frame = arg; + + IVTV_DEBUG_IOCTL("VIDEO_GET_FRAME_COUNT\n"); + if (s->type < IVTV_DEC_STREAM_TYPE_MPG) { + *frame = 0; + break; + } + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + + if (test_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags)) { + *frame = itv->last_dec_timing[0]; + break; + } + *frame = 0; + if (atomic_read(&itv->decoding)) { + if (ivtv_api(itv, CX2341X_DEC_GET_TIMING_INFO, 5, data)) { + IVTV_DEBUG_WARN("GET_TIMING: couldn't read clock\n"); + return -EIO; + } + memcpy(itv->last_dec_timing, data, sizeof(itv->last_dec_timing)); + set_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + *frame = data[0]; + } + break; + } + + case VIDEO_PLAY: { + struct video_command vc; + + IVTV_DEBUG_IOCTL("VIDEO_PLAY\n"); + memset(&vc, 0, sizeof(vc)); + vc.cmd = VIDEO_CMD_PLAY; + return ivtv_video_command(itv, id, &vc, 0); + } + + case VIDEO_STOP: { + struct video_command vc; + + IVTV_DEBUG_IOCTL("VIDEO_STOP\n"); + memset(&vc, 0, sizeof(vc)); + vc.cmd = VIDEO_CMD_STOP; + vc.flags = VIDEO_CMD_STOP_TO_BLACK | VIDEO_CMD_STOP_IMMEDIATELY; + return ivtv_video_command(itv, id, &vc, 0); + } + + case VIDEO_FREEZE: { + struct video_command vc; + + IVTV_DEBUG_IOCTL("VIDEO_FREEZE\n"); + memset(&vc, 0, sizeof(vc)); + vc.cmd = VIDEO_CMD_FREEZE; + return ivtv_video_command(itv, id, &vc, 0); + } + + case VIDEO_CONTINUE: { + struct video_command vc; + + IVTV_DEBUG_IOCTL("VIDEO_CONTINUE\n"); + memset(&vc, 0, sizeof(vc)); + vc.cmd = VIDEO_CMD_CONTINUE; + return ivtv_video_command(itv, id, &vc, 0); + } + + case VIDEO_COMMAND: + case VIDEO_TRY_COMMAND: { + struct video_command *vc = arg; + int try = (cmd == VIDEO_TRY_COMMAND); + + if (try) + IVTV_DEBUG_IOCTL("VIDEO_TRY_COMMAND\n"); + else + IVTV_DEBUG_IOCTL("VIDEO_COMMAND\n"); + return ivtv_video_command(itv, id, vc, try); + } + + case VIDEO_GET_EVENT: { + struct video_event *ev = arg; + DEFINE_WAIT(wait); + + IVTV_DEBUG_IOCTL("VIDEO_GET_EVENT\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + memset(ev, 0, sizeof(*ev)); + set_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags); + + while (1) { + if (test_and_clear_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags)) + ev->type = VIDEO_EVENT_DECODER_STOPPED; + else if (test_and_clear_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags)) { + ev->type = VIDEO_EVENT_VSYNC; + ev->u.vsync_field = test_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags) ? + VIDEO_VSYNC_FIELD_ODD : VIDEO_VSYNC_FIELD_EVEN; + if (itv->output_mode == OUT_UDMA_YUV && + (itv->yuv_info.lace_mode & IVTV_YUV_MODE_MASK) == + IVTV_YUV_MODE_PROGRESSIVE) { + ev->u.vsync_field = VIDEO_VSYNC_FIELD_PROGRESSIVE; + } + } + if (ev->type) + return 0; + if (nonblocking) + return -EAGAIN; + /* wait for event */ + prepare_to_wait(&itv->event_waitq, &wait, TASK_INTERRUPTIBLE); + if ((itv->i_flags & (IVTV_F_I_EV_DEC_STOPPED|IVTV_F_I_EV_VSYNC)) == 0) + schedule(); + finish_wait(&itv->event_waitq, &wait); + if (signal_pending(current)) { + /* return if a signal was received */ + IVTV_DEBUG_INFO("User stopped wait for event\n"); + return -EINTR; + } + } + break; + } + + default: + return -EINVAL; + } + return 0; +} + +static int ivtv_v4l2_do_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, void *arg) +{ + struct ivtv_open_id *id = (struct ivtv_open_id *)filp->private_data; + struct ivtv *itv = id->itv; + int ret; + + /* check priority */ + switch (cmd) { + case VIDIOC_S_CTRL: + case VIDIOC_S_STD: + case VIDIOC_S_INPUT: + case VIDIOC_S_OUTPUT: + case VIDIOC_S_TUNER: + case VIDIOC_S_FREQUENCY: + case VIDIOC_S_FMT: + case VIDIOC_S_CROP: + case VIDIOC_S_AUDIO: + case VIDIOC_S_AUDOUT: + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_S_FBUF: + ret = v4l2_prio_check(&itv->prio, &id->prio); + if (ret) + return ret; + } + + switch (cmd) { + case VIDIOC_DBG_G_REGISTER: + case VIDIOC_DBG_S_REGISTER: + case VIDIOC_G_CHIP_IDENT: + case VIDIOC_INT_S_AUDIO_ROUTING: + case VIDIOC_INT_RESET: + if (ivtv_debug & IVTV_DBGFLG_IOCTL) { + printk(KERN_INFO "ivtv%d ioctl: ", itv->num); + v4l_printk_ioctl(cmd); + } + return ivtv_debug_ioctls(filp, cmd, arg); + + case VIDIOC_G_PRIORITY: + case VIDIOC_S_PRIORITY: + case VIDIOC_QUERYCAP: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_ENUMOUTPUT: + case VIDIOC_G_OUTPUT: + case VIDIOC_S_OUTPUT: + case VIDIOC_G_FMT: + case VIDIOC_S_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_ENUM_FMT: + case VIDIOC_G_CROP: + case VIDIOC_S_CROP: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_S_TUNER: + case VIDIOC_G_TUNER: + case VIDIOC_ENUMAUDIO: + case VIDIOC_S_AUDIO: + case VIDIOC_G_AUDIO: + case VIDIOC_ENUMAUDOUT: + case VIDIOC_S_AUDOUT: + case VIDIOC_G_AUDOUT: + case VIDIOC_G_SLICED_VBI_CAP: + case VIDIOC_LOG_STATUS: + case VIDIOC_G_ENC_INDEX: + case VIDIOC_ENCODER_CMD: + case VIDIOC_TRY_ENCODER_CMD: + case VIDIOC_G_FBUF: + case VIDIOC_S_FBUF: + if (ivtv_debug & IVTV_DBGFLG_IOCTL) { + printk(KERN_INFO "ivtv%d ioctl: ", itv->num); + v4l_printk_ioctl(cmd); + } + return ivtv_v4l2_ioctls(itv, filp, cmd, arg); + + case VIDIOC_QUERYMENU: + case VIDIOC_QUERYCTRL: + case VIDIOC_S_CTRL: + case VIDIOC_G_CTRL: + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_TRY_EXT_CTRLS: + if (ivtv_debug & IVTV_DBGFLG_IOCTL) { + printk(KERN_INFO "ivtv%d ioctl: ", itv->num); + v4l_printk_ioctl(cmd); + } + return ivtv_control_ioctls(itv, cmd, arg); + + case IVTV_IOC_DMA_FRAME: + case VIDEO_GET_PTS: + case VIDEO_GET_FRAME_COUNT: + case VIDEO_GET_EVENT: + case VIDEO_PLAY: + case VIDEO_STOP: + case VIDEO_FREEZE: + case VIDEO_CONTINUE: + case VIDEO_COMMAND: + case VIDEO_TRY_COMMAND: + return ivtv_decoder_ioctls(filp, cmd, arg); + + case 0x00005401: /* Handle isatty() calls */ + return -EINVAL; + default: + return v4l_compat_translate_ioctl(inode, filp, cmd, arg, + ivtv_v4l2_do_ioctl); + } + return 0; +} + +int ivtv_v4l2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct ivtv_open_id *id = (struct ivtv_open_id *)filp->private_data; + struct ivtv *itv = id->itv; + + /* Filter dvb ioctls that cannot be handled by video_usercopy */ + switch (cmd) { + case VIDEO_SELECT_SOURCE: + IVTV_DEBUG_IOCTL("VIDEO_SELECT_SOURCE\n"); + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return -EINVAL; + return ivtv_passthrough_mode(itv, arg == VIDEO_SOURCE_DEMUX); + + case AUDIO_SET_MUTE: + IVTV_DEBUG_IOCTL("AUDIO_SET_MUTE\n"); + itv->speed_mute_audio = arg; + return 0; + + case AUDIO_CHANNEL_SELECT: + IVTV_DEBUG_IOCTL("AUDIO_CHANNEL_SELECT\n"); + if (arg > AUDIO_STEREO_SWAPPED) + return -EINVAL; + itv->audio_stereo_mode = arg; + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + return 0; + + case AUDIO_BILINGUAL_CHANNEL_SELECT: + IVTV_DEBUG_IOCTL("AUDIO_BILINGUAL_CHANNEL_SELECT\n"); + if (arg > AUDIO_STEREO_SWAPPED) + return -EINVAL; + itv->audio_bilingual_mode = arg; + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + return 0; + + default: + break; + } + return video_usercopy(inode, filp, cmd, arg, ivtv_v4l2_do_ioctl); +} diff --git a/drivers/media/video/ivtv/ivtv-ioctl.h b/drivers/media/video/ivtv/ivtv-ioctl.h new file mode 100644 index 00000000000..cbccf7a9f65 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-ioctl.h @@ -0,0 +1,28 @@ +/* + ioctl system call + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +u16 service2vbi(int type); +void expand_service_set(struct v4l2_sliced_vbi_format *fmt, int is_pal); +u16 get_service_set(struct v4l2_sliced_vbi_format *fmt); +int ivtv_v4l2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, + unsigned long arg); +int ivtv_v4l2_ioctls(struct ivtv *itv, struct file *filp, unsigned int cmd, void *arg); +void ivtv_set_osd_alpha(struct ivtv *itv); +int ivtv_set_speed(struct ivtv *itv, int speed); diff --git a/drivers/media/video/ivtv/ivtv-irq.c b/drivers/media/video/ivtv/ivtv-irq.c new file mode 100644 index 00000000000..c3a047b381b --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-irq.c @@ -0,0 +1,838 @@ +/* interrupt handling + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-firmware.h" +#include "ivtv-fileops.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-ioctl.h" +#include "ivtv-mailbox.h" +#include "ivtv-vbi.h" +#include "ivtv-yuv.h" + +#define DMA_MAGIC_COOKIE 0x000001fe + +#define SLICED_VBI_PIO 1 + +static void ivtv_dma_dec_start(struct ivtv_stream *s); + +static const int ivtv_stream_map[] = { + IVTV_ENC_STREAM_TYPE_MPG, + IVTV_ENC_STREAM_TYPE_YUV, + IVTV_ENC_STREAM_TYPE_PCM, + IVTV_ENC_STREAM_TYPE_VBI, +}; + +static inline int ivtv_use_pio(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + return s->dma == PCI_DMA_NONE || + (SLICED_VBI_PIO && s->type == IVTV_ENC_STREAM_TYPE_VBI && itv->vbi.sliced_in->service_set); +} + +void ivtv_irq_work_handler(struct work_struct *work) +{ + struct ivtv *itv = container_of(work, struct ivtv, irq_work_queue); + + DEFINE_WAIT(wait); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags)) + vbi_work_handler(itv); + + if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags)) + ivtv_yuv_work_handler(itv); +} + +/* Determine the required DMA size, setup enough buffers in the predma queue and + actually copy the data from the card to the buffers in case a PIO transfer is + required for this stream. + */ +static int stream_enc_dma_append(struct ivtv_stream *s, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf; + struct list_head *p; + u32 bytes_needed = 0; + u32 offset, size; + u32 UVoffset = 0, UVsize = 0; + int skip_bufs = s->q_predma.buffers; + int idx = s->SG_length; + int rc; + + /* sanity checks */ + if (s->v4l2dev == NULL) { + IVTV_DEBUG_WARN("Stream %s not started\n", s->name); + return -1; + } + if (!test_bit(IVTV_F_S_CLAIMED, &s->s_flags)) { + IVTV_DEBUG_WARN("Stream %s not open\n", s->name); + return -1; + } + + /* determine offset, size and PTS for the various streams */ + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_MPG: + offset = data[1]; + size = data[2]; + s->dma_pts = 0; + break; + + case IVTV_ENC_STREAM_TYPE_YUV: + offset = data[1]; + size = data[2]; + UVoffset = data[3]; + UVsize = data[4]; + s->dma_pts = ((u64) data[5] << 32) | data[6]; + break; + + case IVTV_ENC_STREAM_TYPE_PCM: + offset = data[1] + 12; + size = data[2] - 12; + s->dma_pts = read_dec(offset - 8) | + ((u64)(read_dec(offset - 12)) << 32); + if (itv->has_cx23415) + offset += IVTV_DECODER_OFFSET; + break; + + case IVTV_ENC_STREAM_TYPE_VBI: + size = itv->vbi.enc_size * itv->vbi.fpi; + offset = read_enc(itv->vbi.enc_start - 4) + 12; + if (offset == 12) { + IVTV_DEBUG_INFO("VBI offset == 0\n"); + return -1; + } + s->dma_pts = read_enc(offset - 4) | ((u64)read_enc(offset - 8) << 32); + break; + + case IVTV_DEC_STREAM_TYPE_VBI: + size = read_dec(itv->vbi.dec_start + 4) + 8; + offset = read_dec(itv->vbi.dec_start) + itv->vbi.dec_start; + s->dma_pts = 0; + offset += IVTV_DECODER_OFFSET; + break; + default: + /* shouldn't happen */ + return -1; + } + + /* if this is the start of the DMA then fill in the magic cookie */ + if (s->SG_length == 0) { + if (itv->has_cx23415 && (s->type == IVTV_ENC_STREAM_TYPE_PCM || + s->type == IVTV_DEC_STREAM_TYPE_VBI)) { + s->dma_backup = read_dec(offset - IVTV_DECODER_OFFSET); + write_dec_sync(cpu_to_le32(DMA_MAGIC_COOKIE), offset - IVTV_DECODER_OFFSET); + } + else { + s->dma_backup = read_enc(offset); + write_enc_sync(cpu_to_le32(DMA_MAGIC_COOKIE), offset); + } + s->dma_offset = offset; + } + + bytes_needed = size; + if (s->type == IVTV_ENC_STREAM_TYPE_YUV) { + /* The size for the Y samples needs to be rounded upwards to a + multiple of the buf_size. The UV samples then start in the + next buffer. */ + bytes_needed = s->buf_size * ((bytes_needed + s->buf_size - 1) / s->buf_size); + bytes_needed += UVsize; + } + + IVTV_DEBUG_DMA("%s %s: 0x%08x bytes at 0x%08x\n", + ivtv_use_pio(s) ? "PIO" : "DMA", s->name, bytes_needed, offset); + + rc = ivtv_queue_move(s, &s->q_free, &s->q_full, &s->q_predma, bytes_needed); + if (rc < 0) { /* Insufficient buffers */ + IVTV_DEBUG_WARN("Cannot obtain %d bytes for %s data transfer\n", + bytes_needed, s->name); + return -1; + } + if (rc && !s->buffers_stolen && (s->s_flags & IVTV_F_S_APPL_IO)) { + IVTV_WARN("All %s stream buffers are full. Dropping data.\n", s->name); + IVTV_WARN("Cause: the application is not reading fast enough.\n"); + } + s->buffers_stolen = rc; + + /* got the buffers, now fill in SGarray (DMA) or copy the data from the card + to the buffers (PIO). */ + buf = list_entry(s->q_predma.list.next, struct ivtv_buffer, list); + memset(buf->buf, 0, 128); + list_for_each(p, &s->q_predma.list) { + struct ivtv_buffer *buf = list_entry(p, struct ivtv_buffer, list); + + if (skip_bufs-- > 0) + continue; + if (!ivtv_use_pio(s)) { + s->SGarray[idx].dst = cpu_to_le32(buf->dma_handle); + s->SGarray[idx].src = cpu_to_le32(offset); + s->SGarray[idx].size = cpu_to_le32(s->buf_size); + } + buf->bytesused = (size < s->buf_size) ? size : s->buf_size; + + /* If PIO, then copy the data from the card to the buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_VBI) { + memcpy_fromio(buf->buf, itv->dec_mem + offset - IVTV_DECODER_OFFSET, buf->bytesused); + } + else if (ivtv_use_pio(s)) { + memcpy_fromio(buf->buf, itv->enc_mem + offset, buf->bytesused); + } + + s->q_predma.bytesused += buf->bytesused; + size -= buf->bytesused; + offset += s->buf_size; + + /* Sync SG buffers */ + ivtv_buf_sync_for_device(s, buf); + + if (size == 0) { /* YUV */ + /* process the UV section */ + offset = UVoffset; + size = UVsize; + } + idx++; + } + s->SG_length = idx; + return 0; +} + +static void dma_post(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf = NULL; + struct list_head *p; + u32 offset; + u32 *u32buf; + int x = 0; + + if (ivtv_use_pio(s)) { + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + s->SG_length = 0; + } + IVTV_DEBUG_DMA("%s %s completed (%x)\n", ivtv_use_pio(s) ? "PIO" : "DMA", + s->name, s->dma_offset); + list_for_each(p, &s->q_dma.list) { + buf = list_entry(p, struct ivtv_buffer, list); + u32buf = (u32 *)buf->buf; + + /* Sync Buffer */ + ivtv_buf_sync_for_cpu(s, buf); + + if (x == 0) { + offset = s->dma_last_offset; + if (u32buf[offset / 4] != DMA_MAGIC_COOKIE) + { + for (offset = 0; offset < 64; offset++) { + if (u32buf[offset] == DMA_MAGIC_COOKIE) { + break; + } + } + offset *= 4; + if (offset == 256) { + IVTV_DEBUG_WARN("%s: Couldn't find start of buffer within the first 256 bytes\n", s->name); + offset = s->dma_last_offset; + } + if (s->dma_last_offset != offset) + IVTV_DEBUG_WARN("%s: offset %d -> %d\n", s->name, s->dma_last_offset, offset); + s->dma_last_offset = offset; + } + if (itv->has_cx23415 && (s->type == IVTV_ENC_STREAM_TYPE_PCM || + s->type == IVTV_DEC_STREAM_TYPE_VBI)) { + write_dec_sync(0, s->dma_offset - IVTV_DECODER_OFFSET); + } + else { + write_enc_sync(0, s->dma_offset); + } + if (offset) { + buf->bytesused -= offset; + memcpy(buf->buf, buf->buf + offset, buf->bytesused + offset); + } + *u32buf = cpu_to_le32(s->dma_backup); + } + x++; + /* flag byteswap ABCD -> DCBA for MPG & VBI data outside irq */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG || + s->type == IVTV_ENC_STREAM_TYPE_VBI) + set_bit(IVTV_F_B_NEED_BUF_SWAP, &buf->b_flags); + } + if (buf) + buf->bytesused += s->dma_last_offset; + if (buf && s->type == IVTV_DEC_STREAM_TYPE_VBI) { + /* Parse and Groom VBI Data */ + s->q_dma.bytesused -= buf->bytesused; + ivtv_process_vbi_data(itv, buf, 0, s->type); + s->q_dma.bytesused += buf->bytesused; + if (s->id == -1) { + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); + return; + } + } + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_full, s->q_dma.bytesused); + if (s->id != -1) + wake_up(&s->waitq); +} + +void ivtv_dma_stream_dec_prepare(struct ivtv_stream *s, u32 offset, int lock) +{ + struct ivtv *itv = s->itv; + struct ivtv_buffer *buf; + struct list_head *p; + u32 y_size = itv->params.height * itv->params.width; + u32 uv_offset = offset + IVTV_YUV_BUFFER_UV_OFFSET; + int y_done = 0; + int bytes_written = 0; + unsigned long flags = 0; + int idx = 0; + + IVTV_DEBUG_DMA("DEC PREPARE DMA %s: %08x %08x\n", s->name, s->q_predma.bytesused, offset); + buf = list_entry(s->q_predma.list.next, struct ivtv_buffer, list); + list_for_each(p, &s->q_predma.list) { + struct ivtv_buffer *buf = list_entry(p, struct ivtv_buffer, list); + + /* YUV UV Offset from Y Buffer */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV && !y_done && bytes_written >= y_size) { + offset = uv_offset; + y_done = 1; + } + s->SGarray[idx].src = cpu_to_le32(buf->dma_handle); + s->SGarray[idx].dst = cpu_to_le32(offset); + s->SGarray[idx].size = cpu_to_le32(buf->bytesused); + + offset += buf->bytesused; + bytes_written += buf->bytesused; + + /* Sync SG buffers */ + ivtv_buf_sync_for_device(s, buf); + idx++; + } + s->SG_length = idx; + + /* Mark last buffer size for Interrupt flag */ + s->SGarray[s->SG_length - 1].size |= cpu_to_le32(0x80000000); + + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + if (lock) + spin_lock_irqsave(&itv->dma_reg_lock, flags); + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + ivtv_dma_dec_start(s); + } + else { + set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags); + } + if (lock) + spin_unlock_irqrestore(&itv->dma_reg_lock, flags); +} + +/* start the encoder DMA */ +static void ivtv_dma_enc_start(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + int i; + + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + IVTV_DEBUG_DMA("start DMA for %s\n", s->name); + s->SGarray[s->SG_length - 1].size = cpu_to_le32(le32_to_cpu(s->SGarray[s->SG_length - 1].size) + 256); + + /* If this is an MPEG stream, and VBI data is also pending, then append the + VBI DMA to the MPEG DMA and transfer both sets of data at once. + + VBI DMA is a second class citizen compared to MPEG and mixing them together + will confuse the firmware (the end of a VBI DMA is seen as the end of a + MPEG DMA, thus effectively dropping an MPEG frame). So instead we make + sure we only use the MPEG DMA to transfer the VBI DMA if both are in + use. This way no conflicts occur. */ + clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags); + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && s_vbi->SG_length && + s->SG_length + s_vbi->SG_length <= s->buffers) { + ivtv_queue_move(s_vbi, &s_vbi->q_predma, NULL, &s_vbi->q_dma, s_vbi->q_predma.bytesused); + s_vbi->SGarray[s_vbi->SG_length - 1].size = cpu_to_le32(le32_to_cpu(s_vbi->SGarray[s->SG_length - 1].size) + 256); + for (i = 0; i < s_vbi->SG_length; i++) { + s->SGarray[s->SG_length++] = s_vbi->SGarray[i]; + } + itv->vbi.dma_offset = s_vbi->dma_offset; + s_vbi->SG_length = 0; + set_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags); + IVTV_DEBUG_DMA("include DMA for %s\n", s->name); + } + + /* Mark last buffer size for Interrupt flag */ + s->SGarray[s->SG_length - 1].size |= cpu_to_le32(0x80000000); + + /* Sync Hardware SG List of buffers */ + ivtv_stream_sync_for_device(s); + write_reg(s->SG_handle, IVTV_REG_ENCDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x02, IVTV_REG_DMAXFER); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = s->type; + itv->dma_timer.expires = jiffies + HZ / 10; + add_timer(&itv->dma_timer); +} + +static void ivtv_dma_dec_start(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + IVTV_DEBUG_DMA("start DMA for %s\n", s->name); + /* put SG Handle into register 0x0c */ + write_reg(s->SG_handle, IVTV_REG_DECDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = s->type; + itv->dma_timer.expires = jiffies + HZ / 10; + add_timer(&itv->dma_timer); +} + +static void ivtv_irq_dma_read(struct ivtv *itv) +{ + struct ivtv_stream *s = NULL; + struct ivtv_buffer *buf; + int hw_stream_type; + + IVTV_DEBUG_IRQ("DEC DMA READ\n"); + del_timer(&itv->dma_timer); + if (read_reg(IVTV_REG_DMASTATUS) & 0x14) { + IVTV_DEBUG_WARN("DEC DMA ERROR %x\n", read_reg(IVTV_REG_DMASTATUS)); + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + } + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) { + s = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + hw_stream_type = 2; + } + else { + s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + hw_stream_type = 0; + } + IVTV_DEBUG_DMA("DEC DATA READ %s: %d\n", s->name, s->q_dma.bytesused); + + ivtv_stream_sync_for_cpu(s); + + /* For some reason must kick the firmware, like PIO mode, + I think this tells the firmware we are done and the size + of the xfer so it can calculate what we need next. + I think we can do this part ourselves but would have to + fully calculate xfer info ourselves and not use interrupts + */ + ivtv_vapi(itv, CX2341X_DEC_SCHED_DMA_FROM_HOST, 3, 0, s->q_dma.bytesused, + hw_stream_type); + + /* Free last DMA call */ + while ((buf = ivtv_dequeue(s, &s->q_dma)) != NULL) { + ivtv_buf_sync_for_cpu(s, buf); + ivtv_enqueue(s, buf, &s->q_free); + } + wake_up(&s->waitq); + } + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_dma_complete(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + del_timer(&itv->dma_timer); + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA_END, data); + IVTV_DEBUG_IRQ("ENC DMA COMPLETE %x %d\n", data[0], data[1]); + if (test_and_clear_bit(IVTV_F_I_ENC_VBI, &itv->i_flags)) + data[1] = 3; + else if (data[1] > 2) + return; + s = &itv->streams[ivtv_stream_map[data[1]]]; + if (data[0] & 0x18) { + IVTV_DEBUG_WARN("ENC DMA ERROR %x\n", data[0]); + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, data[1]); + } + s->SG_length = 0; + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + dma_post(s); + ivtv_stream_sync_for_cpu(s); + if (test_and_clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags)) { + u32 tmp; + + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + tmp = s->dma_offset; + s->dma_offset = itv->vbi.dma_offset; + dma_post(s); + s->dma_offset = tmp; + } + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_dma_err(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + del_timer(&itv->dma_timer); + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA_END, data); + IVTV_DEBUG_WARN("DMA ERROR %08x %08x %08x %d\n", data[0], data[1], + read_reg(IVTV_REG_DMASTATUS), itv->cur_dma_stream); + if (!test_bit(IVTV_F_I_UDMA, &itv->i_flags) && + itv->cur_dma_stream >= 0 && itv->cur_dma_stream < IVTV_MAX_STREAMS) { + struct ivtv_stream *s = &itv->streams[itv->cur_dma_stream]; + + /* retry */ + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) + ivtv_dma_dec_start(s); + else + ivtv_dma_enc_start(s); + return; + } + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} + +static void ivtv_irq_enc_start_cap(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + /* Get DMA destination and size arguments from card */ + ivtv_api_get_data(&itv->enc_mbox, IVTV_MBOX_DMA, data); + IVTV_DEBUG_IRQ("ENC START CAP %d: %08x %08x\n", data[0], data[1], data[2]); + + if (data[0] > 2 || data[1] == 0 || data[2] == 0) { + IVTV_DEBUG_WARN("Unknown input: %08x %08x %08x\n", + data[0], data[1], data[2]); + return; + } + clear_bit(IVTV_F_I_ENC_VBI, &itv->i_flags); + s = &itv->streams[ivtv_stream_map[data[0]]]; + if (!stream_enc_dma_append(s, data)) { + if (ivtv_use_pio(s)) { + dma_post(s); + ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, data[0]); + } + else { + set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags); + } + } +} + +static void ivtv_irq_enc_vbi_cap(struct ivtv *itv) +{ + struct ivtv_stream *s_mpg = &itv->streams[IVTV_ENC_STREAM_TYPE_MPG]; + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + IVTV_DEBUG_IRQ("ENC START VBI CAP\n"); + s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI]; + + if (ivtv_use_pio(s)) { + if (stream_enc_dma_append(s, data)) + return; + if (s->q_predma.bytesused) + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused); + s->SG_length = 0; + dma_post(s); + return; + } + /* If more than two VBI buffers are pending, then + clear the old ones and start with this new one. + This can happen during transition stages when MPEG capturing is + started, but the first interrupts haven't arrived yet. During + that period VBI requests can accumulate without being able to + DMA the data. Since at most four VBI DMA buffers are available, + we just drop the old requests when there are already three + requests queued. */ + if (s->SG_length > 2) { + struct list_head *p; + list_for_each(p, &s->q_predma.list) { + struct ivtv_buffer *buf = list_entry(p, struct ivtv_buffer, list); + ivtv_buf_sync_for_cpu(s, buf); + } + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_free, 0); + s->SG_length = 0; + } + /* if we can append the data, and the MPEG stream isn't capturing, + then start a DMA request for just the VBI data. */ + if (!stream_enc_dma_append(s, data) && + !test_bit(IVTV_F_S_STREAMING, &s_mpg->s_flags)) { + set_bit(IVTV_F_I_ENC_VBI, &itv->i_flags); + set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags); + } +} + +static void ivtv_irq_dev_vbi_reinsert(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI]; + + IVTV_DEBUG_IRQ("DEC VBI REINSERT\n"); + if (test_bit(IVTV_F_S_CLAIMED, &s->s_flags) && + !stream_enc_dma_append(s, data)) { + dma_post(s); + } +} + +static void ivtv_irq_dec_data_req(struct ivtv *itv) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv_stream *s; + + /* YUV or MPG */ + ivtv_api_get_data(&itv->dec_mbox, IVTV_MBOX_DMA, data); + + if (test_bit(IVTV_F_I_DEC_YUV, &itv->i_flags)) { + itv->dma_data_req_size = itv->params.width * itv->params.height * 3 / 2; + itv->dma_data_req_offset = data[1] ? data[1] : yuv_offset[0]; + s = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + } + else { + itv->dma_data_req_size = data[2] >= 0x10000 ? 0x10000 : data[2]; + itv->dma_data_req_offset = data[1]; + s = &itv->streams[IVTV_DEC_STREAM_TYPE_MPG]; + } + IVTV_DEBUG_IRQ("DEC DATA REQ %s: %d %08x %u\n", s->name, s->q_full.bytesused, + itv->dma_data_req_offset, itv->dma_data_req_size); + if (itv->dma_data_req_size == 0 || s->q_full.bytesused < itv->dma_data_req_size) { + set_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + } + else { + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_predma, itv->dma_data_req_size); + ivtv_dma_stream_dec_prepare(s, itv->dma_data_req_offset + IVTV_DECODER_OFFSET, 0); + } +} + +static void ivtv_irq_vsync(struct ivtv *itv) +{ + /* The vsync interrupt is unusual in that it won't clear until + * the end of the first line for the current field, at which + * point it clears itself. This can result in repeated vsync + * interrupts, or a missed vsync. Read some of the registers + * to determine the line being displayed and ensure we handle + * one vsync per frame. + */ + unsigned int frame = read_reg(0x28c0) & 1; + int last_dma_frame = atomic_read(&itv->yuv_info.next_dma_frame); + + if (0) IVTV_DEBUG_IRQ("DEC VSYNC\n"); + + if (((frame ^ itv->yuv_info.lace_sync_field) == 0 && ((itv->lastVsyncFrame & 1) ^ itv->yuv_info.lace_sync_field)) || + (frame != (itv->lastVsyncFrame & 1) && !itv->yuv_info.frame_interlaced)) { + int next_dma_frame = last_dma_frame; + + if (next_dma_frame >= 0 && next_dma_frame != atomic_read(&itv->yuv_info.next_fill_frame)) { + write_reg(yuv_offset[next_dma_frame] >> 4, 0x82c); + write_reg((yuv_offset[next_dma_frame] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x830); + write_reg(yuv_offset[next_dma_frame] >> 4, 0x834); + write_reg((yuv_offset[next_dma_frame] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x838); + next_dma_frame = (next_dma_frame + 1) & 0x3; + atomic_set(&itv->yuv_info.next_dma_frame, next_dma_frame); + } + } + if (frame != (itv->lastVsyncFrame & 1)) { + struct ivtv_stream *s = ivtv_get_output_stream(itv); + int work = 0; + + itv->lastVsyncFrame += 1; + if (frame == 0) { + clear_bit(IVTV_F_I_VALID_DEC_TIMINGS, &itv->i_flags); + clear_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags); + } + else { + set_bit(IVTV_F_I_EV_VSYNC_FIELD, &itv->i_flags); + } + if (test_bit(IVTV_F_I_EV_VSYNC_ENABLED, &itv->i_flags)) { + set_bit(IVTV_F_I_EV_VSYNC, &itv->i_flags); + wake_up(&itv->event_waitq); + } + wake_up(&itv->vsync_waitq); + if (s) + wake_up(&s->waitq); + + /* Send VBI to saa7127 */ + if (frame) { + set_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags); + work = 1; + } + + /* Check if we need to update the yuv registers */ + if ((itv->yuv_info.yuv_forced_update || itv->yuv_info.new_frame_info[last_dma_frame].update) && last_dma_frame != -1) { + if (!itv->yuv_info.new_frame_info[last_dma_frame].update) + last_dma_frame = (last_dma_frame - 1) & 3; + + if (itv->yuv_info.new_frame_info[last_dma_frame].src_w) { + itv->yuv_info.update_frame = last_dma_frame; + itv->yuv_info.new_frame_info[last_dma_frame].update = 0; + itv->yuv_info.yuv_forced_update = 0; + set_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags); + work = 1; + } + } + if (work) + queue_work(itv->irq_work_queues, &itv->irq_work_queue); + } +} + +#define IVTV_IRQ_DMA (IVTV_IRQ_DMA_READ | IVTV_IRQ_ENC_DMA_COMPLETE | IVTV_IRQ_DMA_ERR | IVTV_IRQ_ENC_START_CAP | IVTV_IRQ_ENC_VBI_CAP | IVTV_IRQ_DEC_DATA_REQ) + +irqreturn_t ivtv_irq_handler(int irq, void *dev_id) +{ + struct ivtv *itv = (struct ivtv *)dev_id; + u32 combo; + u32 stat; + int i; + u8 vsync_force = 0; + + spin_lock(&itv->dma_reg_lock); + /* get contents of irq status register */ + stat = read_reg(IVTV_REG_IRQSTATUS); + + combo = ~itv->irqmask & stat; + + /* Clear out IRQ */ + if (combo) write_reg(combo, IVTV_REG_IRQSTATUS); + + if (0 == combo) { + /* The vsync interrupt is unusual and clears itself. If we + * took too long, we may have missed it. Do some checks + */ + if (~itv->irqmask & IVTV_IRQ_DEC_VSYNC) { + /* vsync is enabled, see if we're in a new field */ + if ((itv->lastVsyncFrame & 1) != (read_reg(0x28c0) & 1)) { + /* New field, looks like we missed it */ + IVTV_DEBUG_YUV("VSync interrupt missed %d\n",read_reg(0x28c0)>>16); + vsync_force = 1; + } + } + + if (!vsync_force) { + /* No Vsync expected, wasn't for us */ + spin_unlock(&itv->dma_reg_lock); + return IRQ_NONE; + } + } + + /* Exclude interrupts noted below from the output, otherwise the log is flooded with + these messages */ + if (combo & ~0xff6d0400) + IVTV_DEBUG_IRQ("======= valid IRQ bits: 0x%08x ======\n", combo); + + if (combo & IVTV_IRQ_DEC_DMA_COMPLETE) { + IVTV_DEBUG_IRQ("DEC DMA COMPLETE\n"); + } + + if (combo & IVTV_IRQ_DMA_READ) { + ivtv_irq_dma_read(itv); + } + + if (combo & IVTV_IRQ_ENC_DMA_COMPLETE) { + ivtv_irq_enc_dma_complete(itv); + } + + if (combo & IVTV_IRQ_DMA_ERR) { + ivtv_irq_dma_err(itv); + } + + if (combo & IVTV_IRQ_ENC_START_CAP) { + ivtv_irq_enc_start_cap(itv); + } + + if (combo & IVTV_IRQ_ENC_VBI_CAP) { + ivtv_irq_enc_vbi_cap(itv); + } + + if (combo & IVTV_IRQ_DEC_VBI_RE_INSERT) { + ivtv_irq_dev_vbi_reinsert(itv); + } + + if (combo & IVTV_IRQ_ENC_EOS) { + IVTV_DEBUG_IRQ("ENC EOS\n"); + set_bit(IVTV_F_I_EOS, &itv->i_flags); + wake_up(&itv->cap_w); + } + + if (combo & IVTV_IRQ_DEC_DATA_REQ) { + ivtv_irq_dec_data_req(itv); + } + + /* Decoder Vertical Sync - We can't rely on 'combo', so check if vsync enabled */ + if (~itv->irqmask & IVTV_IRQ_DEC_VSYNC) { + ivtv_irq_vsync(itv); + } + + if (combo & IVTV_IRQ_ENC_VIM_RST) { + IVTV_DEBUG_IRQ("VIM RST\n"); + /*ivtv_vapi(itv, CX2341X_ENC_REFRESH_INPUT, 0); */ + } + + if (combo & IVTV_IRQ_DEC_AUD_MODE_CHG) { + IVTV_DEBUG_INFO("Stereo mode changed\n"); + } + + if ((combo & IVTV_IRQ_DMA) && !test_bit(IVTV_F_I_DMA, &itv->i_flags)) { + for (i = 0; i < IVTV_MAX_STREAMS; i++) { + int idx = (i + itv->irq_rr_idx++) % IVTV_MAX_STREAMS; + struct ivtv_stream *s = &itv->streams[idx]; + + if (!test_and_clear_bit(IVTV_F_S_DMA_PENDING, &s->s_flags)) + continue; + if (s->type >= IVTV_DEC_STREAM_TYPE_MPG) + ivtv_dma_dec_start(s); + else + ivtv_dma_enc_start(s); + break; + } + if (i == IVTV_MAX_STREAMS && test_and_clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) { + ivtv_udma_start(itv); + } + } + + spin_unlock(&itv->dma_reg_lock); + + /* If we've just handled a 'forced' vsync, it's safest to say it + * wasn't ours. Another device may have triggered it at just + * the right time. + */ + return vsync_force ? IRQ_NONE : IRQ_HANDLED; +} + +void ivtv_unfinished_dma(unsigned long arg) +{ + struct ivtv *itv = (struct ivtv *)arg; + + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + return; + IVTV_ERR("DMA TIMEOUT %08x %d\n", read_reg(IVTV_REG_DMASTATUS), itv->cur_dma_stream); + + write_reg(read_reg(IVTV_REG_DMASTATUS) & 3, IVTV_REG_DMASTATUS); + clear_bit(IVTV_F_I_UDMA, &itv->i_flags); + clear_bit(IVTV_F_I_DMA, &itv->i_flags); + itv->cur_dma_stream = -1; + wake_up(&itv->dma_waitq); +} diff --git a/drivers/media/video/ivtv/ivtv-irq.h b/drivers/media/video/ivtv/ivtv-irq.h new file mode 100644 index 00000000000..a43348a3030 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-irq.h @@ -0,0 +1,26 @@ +/* + interrupt handling + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +irqreturn_t ivtv_irq_handler(int irq, void *dev_id); + +void ivtv_irq_work_handler(struct work_struct *work); +void ivtv_dma_stream_dec_prepare(struct ivtv_stream *s, u32 offset, int lock); +void ivtv_unfinished_dma(unsigned long arg); diff --git a/drivers/media/video/ivtv/ivtv-mailbox.c b/drivers/media/video/ivtv/ivtv-mailbox.c new file mode 100644 index 00000000000..6ae42a3b03c --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-mailbox.c @@ -0,0 +1,360 @@ +/* + mailbox functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdarg.h> + +#include "ivtv-driver.h" +#include "ivtv-mailbox.h" + +/* Firmware mailbox flags*/ +#define IVTV_MBOX_FIRMWARE_DONE 0x00000004 +#define IVTV_MBOX_DRIVER_DONE 0x00000002 +#define IVTV_MBOX_DRIVER_BUSY 0x00000001 +#define IVTV_MBOX_FREE 0x00000000 + +/* Firmware mailbox standard timeout */ +#define IVTV_API_STD_TIMEOUT 0x02000000 + +#define API_CACHE (1 << 0) /* Allow the command to be stored in the cache */ +#define API_RESULT (1 << 1) /* Allow 1 second for this cmd to end */ +#define API_FAST_RESULT (3 << 1) /* Allow 0.1 second for this cmd to end */ +#define API_DMA (1 << 3) /* DMA mailbox, has special handling */ +#define API_NO_WAIT_MB (1 << 4) /* Command may not wait for a free mailbox */ +#define API_NO_WAIT_RES (1 << 5) /* Command may not wait for the result */ + +struct ivtv_api_info { + int flags; /* Flags, see above */ + const char *name; /* The name of the command */ +}; + +#define API_ENTRY(x, f) [x] = { (f), #x } + +static const struct ivtv_api_info api_info[256] = { + /* MPEG encoder API */ + API_ENTRY(CX2341X_ENC_PING_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_START_CAPTURE, API_RESULT), + API_ENTRY(CX2341X_ENC_STOP_CAPTURE, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_AUDIO_ID, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_VIDEO_ID, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_PCR_ID, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_FRAME_RATE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_FRAME_SIZE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_BIT_RATE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_GOP_PROPERTIES, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_ASPECT_RATIO, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_DNR_FILTER_MODE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_DNR_FILTER_PROPS, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_CORING_LEVELS, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_SPATIAL_FILTER_TYPE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_VBI_LINE, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_STREAM_TYPE, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_OUTPUT_PORT, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_AUDIO_PROPERTIES, API_CACHE), + API_ENTRY(CX2341X_ENC_HALT_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_GET_VERSION, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_SET_GOP_CLOSURE, API_CACHE), + API_ENTRY(CX2341X_ENC_GET_SEQ_END, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_PGM_INDEX_INFO, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_SET_VBI_CONFIG, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_DMA_BLOCK_SIZE, API_CACHE), + API_ENTRY(CX2341X_ENC_GET_PREV_DMA_INFO_MB_10, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_GET_PREV_DMA_INFO_MB_9, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_SCHED_DMA_TO_HOST, API_DMA), + API_ENTRY(CX2341X_ENC_INITIALIZE_INPUT, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_FRAME_DROP_RATE, API_CACHE), + API_ENTRY(CX2341X_ENC_PAUSE_ENCODER, API_RESULT), + API_ENTRY(CX2341X_ENC_REFRESH_INPUT, API_NO_WAIT_MB), + API_ENTRY(CX2341X_ENC_SET_COPYRIGHT, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_EVENT_NOTIFICATION, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_NUM_VSYNC_LINES, API_CACHE), + API_ENTRY(CX2341X_ENC_SET_PLACEHOLDER, API_CACHE), + API_ENTRY(CX2341X_ENC_MUTE_VIDEO, API_RESULT), + API_ENTRY(CX2341X_ENC_MUTE_AUDIO, API_RESULT), + API_ENTRY(CX2341X_ENC_SET_VERT_CROP_LINE, API_FAST_RESULT), + API_ENTRY(CX2341X_ENC_MISC, API_FAST_RESULT), + /* Obsolete PULLDOWN API command */ + API_ENTRY(0xb1, API_CACHE), + + /* MPEG decoder API */ + API_ENTRY(CX2341X_DEC_PING_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_START_PLAYBACK, API_RESULT), + API_ENTRY(CX2341X_DEC_STOP_PLAYBACK, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_PLAYBACK_SPEED, API_RESULT), + API_ENTRY(CX2341X_DEC_STEP_VIDEO, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_DMA_BLOCK_SIZE, API_CACHE), + API_ENTRY(CX2341X_DEC_GET_XFER_INFO, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_GET_DMA_STATUS, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SCHED_DMA_FROM_HOST, API_DMA), + API_ENTRY(CX2341X_DEC_PAUSE_PLAYBACK, API_RESULT), + API_ENTRY(CX2341X_DEC_HALT_FW, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SET_STANDARD, API_CACHE), + API_ENTRY(CX2341X_DEC_GET_VERSION, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SET_STREAM_INPUT, API_CACHE), + API_ENTRY(CX2341X_DEC_GET_TIMING_INFO, API_RESULT /*| API_NO_WAIT_RES*/), + API_ENTRY(CX2341X_DEC_SET_AUDIO_MODE, API_CACHE), + API_ENTRY(CX2341X_DEC_SET_EVENT_NOTIFICATION, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_DISPLAY_BUFFERS, API_CACHE), + API_ENTRY(CX2341X_DEC_EXTRACT_VBI, API_RESULT), + API_ENTRY(CX2341X_DEC_SET_DECODER_SOURCE, API_FAST_RESULT), + API_ENTRY(CX2341X_DEC_SET_PREBUFFERING, API_CACHE), + + /* OSD API */ + API_ENTRY(CX2341X_OSD_GET_FRAMEBUFFER, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_GET_PIXEL_FORMAT, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_PIXEL_FORMAT, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_STATE, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_STATE, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_OSD_COORDS, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_OSD_COORDS, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_SCREEN_COORDS, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_SCREEN_COORDS, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_GLOBAL_ALPHA, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_GLOBAL_ALPHA, API_CACHE), + API_ENTRY(CX2341X_OSD_SET_BLEND_COORDS, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_FLICKER_STATE, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_FLICKER_STATE, API_CACHE), + API_ENTRY(CX2341X_OSD_BLT_COPY, API_RESULT), + API_ENTRY(CX2341X_OSD_BLT_FILL, API_RESULT), + API_ENTRY(CX2341X_OSD_BLT_TEXT, API_RESULT), + API_ENTRY(CX2341X_OSD_SET_FRAMEBUFFER_WINDOW, API_CACHE), + API_ENTRY(CX2341X_OSD_SET_CHROMA_KEY, API_CACHE), + API_ENTRY(CX2341X_OSD_GET_ALPHA_CONTENT_INDEX, API_FAST_RESULT), + API_ENTRY(CX2341X_OSD_SET_ALPHA_CONTENT_INDEX, API_CACHE) +}; + +static int try_mailbox(struct ivtv *itv, struct ivtv_mailbox_data *mbdata, int mb) +{ + u32 flags = readl(&mbdata->mbox[mb].flags); + int is_free = flags == IVTV_MBOX_FREE || (flags & IVTV_MBOX_FIRMWARE_DONE); + + /* if the mailbox is free, then try to claim it */ + if (is_free && !test_and_set_bit(mb, &mbdata->busy)) { + write_sync(IVTV_MBOX_DRIVER_BUSY, &mbdata->mbox[mb].flags); + return 1; + } + return 0; +} + +/* Try to find a free mailbox. Note mailbox 0 is reserved for DMA and so is not + attempted here. */ +static int get_mailbox(struct ivtv *itv, struct ivtv_mailbox_data *mbdata, int flags) +{ + unsigned long then = jiffies; + int i, mb; + int max_mbox = mbdata->max_mbox; + int retries = 100; + + /* All slow commands use the same mailbox, serializing them and also + leaving the other mailbox free for simple fast commands. */ + if ((flags & API_FAST_RESULT) == API_RESULT) + max_mbox = 1; + + /* find free non-DMA mailbox */ + for (i = 0; i < retries; i++) { + for (mb = 1; mb <= max_mbox; mb++) + if (try_mailbox(itv, mbdata, mb)) + return mb; + + /* Sleep before a retry, if not atomic */ + if (!(flags & API_NO_WAIT_MB)) { + if (jiffies - then > retries * HZ / 100) + break; + ivtv_sleep_timeout(HZ / 100, 0); + } + } + return -ENODEV; +} + +static void write_mailbox(volatile struct ivtv_mailbox __iomem *mbox, int cmd, int args, u32 data[]) +{ + int i; + + write_sync(cmd, &mbox->cmd); + write_sync(IVTV_API_STD_TIMEOUT, &mbox->timeout); + + for (i = 0; i < CX2341X_MBOX_MAX_DATA; i++) + write_sync(data[i], &mbox->data[i]); + + write_sync(IVTV_MBOX_DRIVER_DONE | IVTV_MBOX_DRIVER_BUSY, &mbox->flags); +} + +static void clear_all_mailboxes(struct ivtv *itv, struct ivtv_mailbox_data *mbdata) +{ + int i; + + for (i = 0; i <= mbdata->max_mbox; i++) { + IVTV_DEBUG_WARN("Clearing mailbox %d: cmd 0x%08x flags 0x%08x\n", + i, readl(&mbdata->mbox[i].cmd), readl(&mbdata->mbox[i].flags)); + write_sync(0, &mbdata->mbox[i].flags); + clear_bit(i, &mbdata->busy); + } +} + +static int ivtv_api_call(struct ivtv *itv, int cmd, int args, u32 data[]) +{ + struct ivtv_mailbox_data *mbdata = (cmd >= 128) ? &itv->enc_mbox : &itv->dec_mbox; + volatile struct ivtv_mailbox __iomem *mbox; + int api_timeout = HZ; + int flags, mb, i; + unsigned long then; + + /* sanity checks */ + if (NULL == mbdata) { + IVTV_ERR("No mailbox allocated\n"); + return -ENODEV; + } + if (args < 0 || args > CX2341X_MBOX_MAX_DATA || + cmd < 0 || cmd > 255 || api_info[cmd].name == NULL) { + IVTV_ERR("Invalid API call: cmd = 0x%02x, args = %d\n", cmd, args); + return -EINVAL; + } + + IVTV_DEBUG_API("API Call: %s\n", api_info[cmd].name); + + /* clear possibly uninitialized part of data array */ + for (i = args; i < CX2341X_MBOX_MAX_DATA; i++) + data[i] = 0; + + /* If this command was issued within the last 30 minutes and with identical + data, then just return 0 as there is no need to issue this command again. + Just an optimization to prevent unnecessary use of mailboxes. */ + if (itv->api_cache[cmd].last_jiffies && + jiffies - itv->api_cache[cmd].last_jiffies < HZ * 1800 && + !memcmp(data, itv->api_cache[cmd].data, sizeof(itv->api_cache[cmd].data))) { + itv->api_cache[cmd].last_jiffies = jiffies; + return 0; + } + + flags = api_info[cmd].flags; + + if (flags & API_DMA) { + for (i = 0; i < 100; i++) { + mb = i % (mbdata->max_mbox + 1); + if (try_mailbox(itv, mbdata, mb)) { + write_mailbox(&mbdata->mbox[mb], cmd, args, data); + clear_bit(mb, &mbdata->busy); + return 0; + } + IVTV_DEBUG_WARN("%s: mailbox %d not free %08x\n", + api_info[cmd].name, mb, readl(&mbdata->mbox[mb].flags)); + } + IVTV_WARN("Could not find free DMA mailbox for %s\n", api_info[cmd].name); + clear_all_mailboxes(itv, mbdata); + return -EBUSY; + } + + if ((flags & API_FAST_RESULT) == API_FAST_RESULT) + api_timeout = HZ / 10; + + mb = get_mailbox(itv, mbdata, flags); + if (mb < 0) { + IVTV_DEBUG_WARN("No free mailbox found (%s)\n", api_info[cmd].name); + clear_all_mailboxes(itv, mbdata); + return -EBUSY; + } + mbox = &mbdata->mbox[mb]; + write_mailbox(mbox, cmd, args, data); + if (flags & API_CACHE) { + memcpy(itv->api_cache[cmd].data, data, sizeof(itv->api_cache[cmd].data)); + itv->api_cache[cmd].last_jiffies = jiffies; + } + if ((flags & API_RESULT) == 0) { + clear_bit(mb, &mbdata->busy); + return 0; + } + + /* Get results */ + then = jiffies; + + while (!(readl(&mbox->flags) & IVTV_MBOX_FIRMWARE_DONE)) { + if (jiffies - then > api_timeout) { + IVTV_DEBUG_WARN("Could not get result (%s)\n", api_info[cmd].name); + /* reset the mailbox, but it is likely too late already */ + write_sync(0, &mbox->flags); + clear_bit(mb, &mbdata->busy); + return -EIO; + } + if (flags & API_NO_WAIT_RES) + mdelay(1); + else + ivtv_sleep_timeout(HZ / 100, 0); + } + if (jiffies - then > HZ / 10) + IVTV_DEBUG_WARN("%s took %lu jiffies (%d per HZ)\n", + api_info[cmd].name, jiffies - then, HZ); + + for (i = 0; i < CX2341X_MBOX_MAX_DATA; i++) + data[i] = readl(&mbox->data[i]); + write_sync(0, &mbox->flags); + clear_bit(mb, &mbdata->busy); + return 0; +} + +int ivtv_api(struct ivtv *itv, int cmd, int args, u32 data[]) +{ + int res = ivtv_api_call(itv, cmd, args, data); + + /* Allow a single retry, probably already too late though. + If there is no free mailbox then that is usually an indication + of a more serious problem. */ + return (res == -EBUSY) ? ivtv_api_call(itv, cmd, args, data) : res; +} + +int ivtv_api_func(void *priv, int cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]) +{ + return ivtv_api(priv, cmd, in, data); +} + +int ivtv_vapi_result(struct ivtv *itv, u32 data[CX2341X_MBOX_MAX_DATA], int cmd, int args, ...) +{ + va_list ap; + int i; + + va_start(ap, args); + for (i = 0; i < args; i++) { + data[i] = va_arg(ap, u32); + } + va_end(ap); + return ivtv_api(itv, cmd, args, data); +} + +int ivtv_vapi(struct ivtv *itv, int cmd, int args, ...) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + va_list ap; + int i; + + va_start(ap, args); + for (i = 0; i < args; i++) { + data[i] = va_arg(ap, u32); + } + va_end(ap); + return ivtv_api(itv, cmd, args, data); +} + +/* This one is for stuff that can't sleep.. irq handlers, etc.. */ +void ivtv_api_get_data(struct ivtv_mailbox_data *mbdata, int mb, u32 data[]) +{ + int i; + + for (i = 0; i < CX2341X_MBOX_MAX_DATA; i++) + data[i] = readl(&mbdata->mbox[mb].data[i]); +} diff --git a/drivers/media/video/ivtv/ivtv-mailbox.h b/drivers/media/video/ivtv/ivtv-mailbox.h new file mode 100644 index 00000000000..79b8aec1437 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-mailbox.h @@ -0,0 +1,25 @@ +/* + mailbox functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +void ivtv_api_get_data(struct ivtv_mailbox_data *mbox, int mb, u32 data[]); +int ivtv_api(struct ivtv *itv, int cmd, int args, u32 data[]); +int ivtv_vapi_result(struct ivtv *itv, u32 data[CX2341X_MBOX_MAX_DATA], int cmd, int args, ...); +int ivtv_vapi(struct ivtv *itv, int cmd, int args, ...); +int ivtv_api_func(void *priv, int cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]); diff --git a/drivers/media/video/ivtv/ivtv-queue.c b/drivers/media/video/ivtv/ivtv-queue.c new file mode 100644 index 00000000000..ccfcef1ad91 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-queue.c @@ -0,0 +1,262 @@ +/* + buffer queues. + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-streams.h" +#include "ivtv-queue.h" +#include "ivtv-mailbox.h" + +int ivtv_buf_copy_from_user(struct ivtv_stream *s, struct ivtv_buffer *buf, const char __user *src, int copybytes) +{ + if (s->buf_size - buf->bytesused < copybytes) + copybytes = s->buf_size - buf->bytesused; + if (copy_from_user(buf->buf + buf->bytesused, src, copybytes)) { + return -EFAULT; + } + buf->bytesused += copybytes; + return copybytes; +} + +void ivtv_buf_swap(struct ivtv_buffer *buf) +{ + int i; + + for (i = 0; i < buf->bytesused; i += 4) + swab32s((u32 *)(buf->buf + i)); +} + +void ivtv_queue_init(struct ivtv_queue *q) +{ + INIT_LIST_HEAD(&q->list); + q->buffers = 0; + q->length = 0; + q->bytesused = 0; +} + +void ivtv_enqueue(struct ivtv_stream *s, struct ivtv_buffer *buf, struct ivtv_queue *q) +{ + unsigned long flags = 0; + + /* clear the buffer if it is going to be enqueued to the free queue */ + if (q == &s->q_free) { + buf->bytesused = 0; + buf->readpos = 0; + buf->b_flags = 0; + } + spin_lock_irqsave(&s->qlock, flags); + list_add_tail(&buf->list, &q->list); + q->buffers++; + q->length += s->buf_size; + q->bytesused += buf->bytesused - buf->readpos; + spin_unlock_irqrestore(&s->qlock, flags); +} + +struct ivtv_buffer *ivtv_dequeue(struct ivtv_stream *s, struct ivtv_queue *q) +{ + struct ivtv_buffer *buf = NULL; + unsigned long flags = 0; + + spin_lock_irqsave(&s->qlock, flags); + if (!list_empty(&q->list)) { + buf = list_entry(q->list.next, struct ivtv_buffer, list); + list_del_init(q->list.next); + q->buffers--; + q->length -= s->buf_size; + q->bytesused -= buf->bytesused - buf->readpos; + } + spin_unlock_irqrestore(&s->qlock, flags); + return buf; +} + +static void ivtv_queue_move_buf(struct ivtv_stream *s, struct ivtv_queue *from, + struct ivtv_queue *to, int clear, int full) +{ + struct ivtv_buffer *buf = list_entry(from->list.next, struct ivtv_buffer, list); + + list_move_tail(from->list.next, &to->list); + from->buffers--; + from->length -= s->buf_size; + from->bytesused -= buf->bytesused - buf->readpos; + /* special handling for q_free */ + if (clear) + buf->bytesused = buf->readpos = buf->b_flags = 0; + else if (full) { + /* special handling for stolen buffers, assume + all bytes are used. */ + buf->bytesused = s->buf_size; + buf->readpos = buf->b_flags = 0; + } + to->buffers++; + to->length += s->buf_size; + to->bytesused += buf->bytesused - buf->readpos; +} + +/* Move 'needed_bytes' worth of buffers from queue 'from' into queue 'to'. + If 'needed_bytes' == 0, then move all buffers from 'from' into 'to'. + If 'steal' != NULL, then buffers may also taken from that queue if + needed. + + The buffer is automatically cleared if it goes to the free queue. It is + also cleared if buffers need to be taken from the 'steal' queue and + the 'from' queue is the free queue. + + When 'from' is q_free, then needed_bytes is compared to the total + available buffer length, otherwise needed_bytes is compared to the + bytesused value. For the 'steal' queue the total available buffer + length is always used. + + -ENOMEM is returned if the buffers could not be obtained, 0 if all + buffers where obtained from the 'from' list and if non-zero then + the number of stolen buffers is returned. */ +int ivtv_queue_move(struct ivtv_stream *s, struct ivtv_queue *from, struct ivtv_queue *steal, + struct ivtv_queue *to, int needed_bytes) +{ + unsigned long flags; + int rc = 0; + int from_free = from == &s->q_free; + int to_free = to == &s->q_free; + int bytes_available; + + spin_lock_irqsave(&s->qlock, flags); + if (needed_bytes == 0) { + from_free = 1; + needed_bytes = from->length; + } + + bytes_available = from_free ? from->length : from->bytesused; + bytes_available += steal ? steal->length : 0; + + if (bytes_available < needed_bytes) { + spin_unlock_irqrestore(&s->qlock, flags); + return -ENOMEM; + } + if (from_free) { + u32 old_length = to->length; + + while (to->length - old_length < needed_bytes) { + if (list_empty(&from->list)) + from = steal; + if (from == steal) + rc++; /* keep track of 'stolen' buffers */ + ivtv_queue_move_buf(s, from, to, 1, 0); + } + } + else { + u32 old_bytesused = to->bytesused; + + while (to->bytesused - old_bytesused < needed_bytes) { + if (list_empty(&from->list)) + from = steal; + if (from == steal) + rc++; /* keep track of 'stolen' buffers */ + ivtv_queue_move_buf(s, from, to, to_free, rc); + } + } + spin_unlock_irqrestore(&s->qlock, flags); + return rc; +} + +void ivtv_flush_queues(struct ivtv_stream *s) +{ + ivtv_queue_move(s, &s->q_io, NULL, &s->q_free, 0); + ivtv_queue_move(s, &s->q_full, NULL, &s->q_free, 0); + ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); + ivtv_queue_move(s, &s->q_predma, NULL, &s->q_free, 0); +} + +int ivtv_stream_alloc(struct ivtv_stream *s) +{ + struct ivtv *itv = s->itv; + int SGsize = sizeof(struct ivtv_SG_element) * s->buffers; + int i; + + if (s->buffers == 0) + return 0; + + IVTV_DEBUG_INFO("Allocate %s%s stream: %d x %d buffers (%dkB total)\n", + s->dma != PCI_DMA_NONE ? "DMA " : "", + s->name, s->buffers, s->buf_size, s->buffers * s->buf_size / 1024); + + /* Allocate DMA SG Arrays */ + if (s->dma != PCI_DMA_NONE) { + s->SGarray = (struct ivtv_SG_element *)kzalloc(SGsize, GFP_KERNEL); + if (s->SGarray == NULL) { + IVTV_ERR("Could not allocate SGarray for %s stream\n", s->name); + return -ENOMEM; + } + s->SG_length = 0; + s->SG_handle = pci_map_single(itv->dev, s->SGarray, SGsize, s->dma); + ivtv_stream_sync_for_cpu(s); + } + + /* allocate stream buffers. Initially all buffers are in q_free. */ + for (i = 0; i < s->buffers; i++) { + struct ivtv_buffer *buf = kzalloc(sizeof(struct ivtv_buffer), GFP_KERNEL); + + if (buf == NULL) + break; + buf->buf = kmalloc(s->buf_size + 256, GFP_KERNEL); + if (buf->buf == NULL) { + kfree(buf); + break; + } + INIT_LIST_HEAD(&buf->list); + if (s->dma != PCI_DMA_NONE) { + buf->dma_handle = pci_map_single(s->itv->dev, + buf->buf, s->buf_size + 256, s->dma); + ivtv_buf_sync_for_cpu(s, buf); + } + ivtv_enqueue(s, buf, &s->q_free); + } + if (i == s->buffers) + return 0; + IVTV_ERR("Couldn't allocate buffers for %s stream\n", s->name); + ivtv_stream_free(s); + return -ENOMEM; +} + +void ivtv_stream_free(struct ivtv_stream *s) +{ + struct ivtv_buffer *buf; + + /* move all buffers to q_free */ + ivtv_flush_queues(s); + + /* empty q_free */ + while ((buf = ivtv_dequeue(s, &s->q_free))) { + if (s->dma != PCI_DMA_NONE) + pci_unmap_single(s->itv->dev, buf->dma_handle, + s->buf_size + 256, s->dma); + kfree(buf->buf); + kfree(buf); + } + + /* Free SG Array/Lists */ + if (s->SGarray != NULL) { + if (s->SG_handle != IVTV_DMA_UNMAPPED) { + pci_unmap_single(s->itv->dev, s->SG_handle, + sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE); + s->SG_handle = IVTV_DMA_UNMAPPED; + } + s->SGarray = NULL; + s->SG_length = 0; + } +} diff --git a/drivers/media/video/ivtv/ivtv-queue.h b/drivers/media/video/ivtv/ivtv-queue.h new file mode 100644 index 00000000000..903edd4b438 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-queue.h @@ -0,0 +1,64 @@ +/* + buffer queues. + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define IVTV_DMA_UNMAPPED ((u32) -1) + +/* ivtv_buffer utility functions */ +static inline void ivtv_buf_sync_for_cpu(struct ivtv_stream *s, struct ivtv_buffer *buf) +{ + if (s->dma != PCI_DMA_NONE) + pci_dma_sync_single_for_cpu(s->itv->dev, buf->dma_handle, + s->buf_size + 256, s->dma); +} + +static inline void ivtv_buf_sync_for_device(struct ivtv_stream *s, struct ivtv_buffer *buf) +{ + if (s->dma != PCI_DMA_NONE) + pci_dma_sync_single_for_device(s->itv->dev, buf->dma_handle, + s->buf_size + 256, s->dma); +} + +int ivtv_buf_copy_from_user(struct ivtv_stream *s, struct ivtv_buffer *buf, const char __user *src, int copybytes); +void ivtv_buf_swap(struct ivtv_buffer *buf); + +/* ivtv_queue utility functions */ +void ivtv_queue_init(struct ivtv_queue *q); +void ivtv_enqueue(struct ivtv_stream *s, struct ivtv_buffer *buf, struct ivtv_queue *q); +struct ivtv_buffer *ivtv_dequeue(struct ivtv_stream *s, struct ivtv_queue *q); +int ivtv_queue_move(struct ivtv_stream *s, struct ivtv_queue *from, struct ivtv_queue *steal, + struct ivtv_queue *to, int needed_bytes); +void ivtv_flush_queues(struct ivtv_stream *s); + +/* ivtv_stream utility functions */ +int ivtv_stream_alloc(struct ivtv_stream *s); +void ivtv_stream_free(struct ivtv_stream *s); + +static inline void ivtv_stream_sync_for_cpu(struct ivtv_stream *s) +{ + pci_dma_sync_single_for_cpu(s->itv->dev, s->SG_handle, + sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE); +} + +static inline void ivtv_stream_sync_for_device(struct ivtv_stream *s) +{ + pci_dma_sync_single_for_device(s->itv->dev, s->SG_handle, + sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE); +} diff --git a/drivers/media/video/ivtv/ivtv-streams.c b/drivers/media/video/ivtv/ivtv-streams.c new file mode 100644 index 00000000000..01a41a844a3 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-streams.c @@ -0,0 +1,977 @@ +/* + init/start/stop/exit stream functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* License: GPL + * Author: Kevin Thayer <nufan_wfk at yahoo dot com> + * + * This file will hold API related functions, both internal (firmware api) + * and external (v4l2, etc) + * + * ----- + * MPG600/MPG160 support by T.Adachi <tadachi@tadachi-net.com> + * and Takeru KOMORIYA<komoriya@paken.org> + * + * AVerMedia M179 GPIO info by Chris Pinkham <cpinkham@bc2va.org> + * using information provided by Jiun-Kuei Jung @ AVerMedia. + */ + +#include "ivtv-driver.h" +#include "ivtv-fileops.h" +#include "ivtv-i2c.h" +#include "ivtv-queue.h" +#include "ivtv-mailbox.h" +#include "ivtv-audio.h" +#include "ivtv-video.h" +#include "ivtv-vbi.h" +#include "ivtv-ioctl.h" +#include "ivtv-irq.h" +#include "ivtv-streams.h" +#include "ivtv-cards.h" + +static struct file_operations ivtv_v4l2_enc_fops = { + .owner = THIS_MODULE, + .read = ivtv_v4l2_read, + .write = ivtv_v4l2_write, + .open = ivtv_v4l2_open, + .ioctl = ivtv_v4l2_ioctl, + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_enc_poll, +}; + +static struct file_operations ivtv_v4l2_dec_fops = { + .owner = THIS_MODULE, + .read = ivtv_v4l2_read, + .write = ivtv_v4l2_write, + .open = ivtv_v4l2_open, + .ioctl = ivtv_v4l2_ioctl, + .release = ivtv_v4l2_close, + .poll = ivtv_v4l2_dec_poll, +}; + +static struct { + const char *name; + int vfl_type; + int minor_offset; + int dma, pio; + enum v4l2_buf_type buf_type; + struct file_operations *fops; +} ivtv_stream_info[] = { + { /* IVTV_ENC_STREAM_TYPE_MPG */ + "encoder MPEG", + VFL_TYPE_GRABBER, 0, + PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_YUV */ + "encoder YUV", + VFL_TYPE_GRABBER, IVTV_V4L2_ENC_YUV_OFFSET, + PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_VIDEO_CAPTURE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_VBI */ + "encoder VBI", + VFL_TYPE_VBI, 0, + PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_VBI_CAPTURE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_PCM */ + "encoder PCM audio", + VFL_TYPE_GRABBER, IVTV_V4L2_ENC_PCM_OFFSET, + PCI_DMA_FROMDEVICE, 0, V4L2_BUF_TYPE_PRIVATE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_ENC_STREAM_TYPE_RAD */ + "encoder radio", + VFL_TYPE_RADIO, 0, + PCI_DMA_NONE, 1, V4L2_BUF_TYPE_PRIVATE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_DEC_STREAM_TYPE_MPG */ + "decoder MPEG", + VFL_TYPE_GRABBER, IVTV_V4L2_DEC_MPG_OFFSET, + PCI_DMA_TODEVICE, 0, V4L2_BUF_TYPE_VIDEO_OUTPUT, + &ivtv_v4l2_dec_fops + }, + { /* IVTV_DEC_STREAM_TYPE_VBI */ + "decoder VBI", + VFL_TYPE_VBI, IVTV_V4L2_DEC_VBI_OFFSET, + PCI_DMA_NONE, 1, V4L2_BUF_TYPE_VBI_CAPTURE, + &ivtv_v4l2_enc_fops + }, + { /* IVTV_DEC_STREAM_TYPE_VOUT */ + "decoder VOUT", + VFL_TYPE_VBI, IVTV_V4L2_DEC_VOUT_OFFSET, + PCI_DMA_NONE, 1, V4L2_BUF_TYPE_VBI_OUTPUT, + &ivtv_v4l2_dec_fops + }, + { /* IVTV_DEC_STREAM_TYPE_YUV */ + "decoder YUV", + VFL_TYPE_GRABBER, IVTV_V4L2_DEC_YUV_OFFSET, + PCI_DMA_TODEVICE, 0, V4L2_BUF_TYPE_VIDEO_OUTPUT, + &ivtv_v4l2_dec_fops + } +}; + +static void ivtv_stream_init(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + struct video_device *dev = s->v4l2dev; + + /* we need to keep v4l2dev, so restore it afterwards */ + memset(s, 0, sizeof(*s)); + s->v4l2dev = dev; + + /* initialize ivtv_stream fields */ + s->itv = itv; + s->type = type; + s->name = ivtv_stream_info[type].name; + + if (ivtv_stream_info[type].pio) + s->dma = PCI_DMA_NONE; + else + s->dma = ivtv_stream_info[type].dma; + s->buf_size = itv->stream_buf_size[type]; + if (s->buf_size) + s->buffers = itv->options.megabytes[type] * 1024 * 1024 / s->buf_size; + spin_lock_init(&s->qlock); + init_waitqueue_head(&s->waitq); + s->id = -1; + s->SG_handle = IVTV_DMA_UNMAPPED; + ivtv_queue_init(&s->q_free); + ivtv_queue_init(&s->q_full); + ivtv_queue_init(&s->q_dma); + ivtv_queue_init(&s->q_predma); + ivtv_queue_init(&s->q_io); +} + +static int ivtv_reg_dev(struct ivtv *itv, int type) +{ + struct ivtv_stream *s = &itv->streams[type]; + int vfl_type = ivtv_stream_info[type].vfl_type; + int minor_offset = ivtv_stream_info[type].minor_offset; + int minor; + + /* These four fields are always initialized. If v4l2dev == NULL, then + this stream is not in use. In that case no other fields but these + four can be used. */ + s->v4l2dev = NULL; + s->itv = itv; + s->type = type; + s->name = ivtv_stream_info[type].name; + + /* Check whether the radio is supported */ + if (type == IVTV_ENC_STREAM_TYPE_RAD && !(itv->v4l2_cap & V4L2_CAP_RADIO)) + return 0; + if (type >= IVTV_DEC_STREAM_TYPE_MPG && !(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return 0; + + if (minor_offset >= 0) + /* card number + user defined offset + device offset */ + minor = itv->num + ivtv_first_minor + minor_offset; + else + minor = -1; + + /* User explicitly selected 0 buffers for these streams, so don't + create them. */ + if (minor >= 0 && ivtv_stream_info[type].dma != PCI_DMA_NONE && + itv->options.megabytes[type] == 0) { + IVTV_INFO("Disabled %s device\n", ivtv_stream_info[type].name); + return 0; + } + + ivtv_stream_init(itv, type); + + /* allocate and initialize the v4l2 video device structure */ + s->v4l2dev = video_device_alloc(); + if (s->v4l2dev == NULL) { + IVTV_ERR("Couldn't allocate v4l2 video_device for %s\n", s->name); + return -ENOMEM; + } + + s->v4l2dev->type = VID_TYPE_CAPTURE | VID_TYPE_TUNER | VID_TYPE_TELETEXT | + VID_TYPE_CLIPPING | VID_TYPE_SCALES | VID_TYPE_MPEG_ENCODER; + if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { + s->v4l2dev->type |= VID_TYPE_MPEG_DECODER; + } + snprintf(s->v4l2dev->name, sizeof(s->v4l2dev->name), "ivtv%d %s", + itv->num, s->name); + + s->v4l2dev->minor = minor; + s->v4l2dev->dev = &itv->dev->dev; + s->v4l2dev->fops = ivtv_stream_info[type].fops; + s->v4l2dev->release = video_device_release; + + if (minor >= 0) { + /* Register device. First try the desired minor, then any free one. */ + if (video_register_device(s->v4l2dev, vfl_type, minor) && + video_register_device(s->v4l2dev, vfl_type, -1)) { + IVTV_ERR("Couldn't register v4l2 device for %s minor %d\n", + s->name, minor); + video_device_release(s->v4l2dev); + s->v4l2dev = NULL; + return -ENOMEM; + } + } + else { + /* Don't register a 'hidden' stream (OSD) */ + IVTV_INFO("Created framebuffer stream for %s\n", s->name); + return 0; + } + + switch (vfl_type) { + case VFL_TYPE_GRABBER: + IVTV_INFO("Registered device video%d for %s (%d MB)\n", + s->v4l2dev->minor, s->name, itv->options.megabytes[type]); + break; + case VFL_TYPE_RADIO: + IVTV_INFO("Registered device radio%d for %s\n", + s->v4l2dev->minor - MINOR_VFL_TYPE_RADIO_MIN, s->name); + break; + case VFL_TYPE_VBI: + if (itv->options.megabytes[type]) + IVTV_INFO("Registered device vbi%d for %s (%d MB)\n", + s->v4l2dev->minor - MINOR_VFL_TYPE_VBI_MIN, + s->name, itv->options.megabytes[type]); + else + IVTV_INFO("Registered device vbi%d for %s\n", + s->v4l2dev->minor - MINOR_VFL_TYPE_VBI_MIN, s->name); + break; + } + return 0; +} + +/* Initialize v4l2 variables and register v4l2 devices */ +int ivtv_streams_setup(struct ivtv *itv) +{ + int type; + + /* Setup V4L2 Devices */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) { + /* Register Device */ + if (ivtv_reg_dev(itv, type)) + break; + + if (itv->streams[type].v4l2dev == NULL) + continue; + + /* Allocate Stream */ + if (ivtv_stream_alloc(&itv->streams[type])) + break; + } + if (type == IVTV_MAX_STREAMS) { + return 0; + } + + /* One or more streams could not be initialized. Clean 'em all up. */ + ivtv_streams_cleanup(itv); + return -ENOMEM; +} + +/* Unregister v4l2 devices */ +void ivtv_streams_cleanup(struct ivtv *itv) +{ + int type; + + /* Teardown all streams */ + for (type = 0; type < IVTV_MAX_STREAMS; type++) { + struct video_device *vdev = itv->streams[type].v4l2dev; + + itv->streams[type].v4l2dev = NULL; + if (vdev == NULL) + continue; + + ivtv_stream_free(&itv->streams[type]); + /* Free Device */ + if (vdev->minor == -1) /* 'Hidden' never registered stream (OSD) */ + video_device_release(vdev); + else /* All others, just unregister. */ + video_unregister_device(vdev); + } +} + +static void ivtv_vbi_setup(struct ivtv *itv) +{ + int raw = itv->vbi.sliced_in->service_set == 0; + u32 data[CX2341X_MBOX_MAX_DATA]; + int lines; + int i; + + /* If Embed then streamtype must be Program */ + /* TODO: should we really do this? */ + if (0 && !raw && itv->vbi.insert_mpeg) { + itv->params.stream_type = 0; + + /* assign stream type */ + ivtv_vapi(itv, CX2341X_ENC_SET_STREAM_TYPE, 1, itv->params.stream_type); + } + + /* Reset VBI */ + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, 0xffff , 0, 0, 0, 0); + + if (itv->is_60hz) { + itv->vbi.count = 12; + itv->vbi.start[0] = 10; + itv->vbi.start[1] = 273; + } else { /* PAL/SECAM */ + itv->vbi.count = 18; + itv->vbi.start[0] = 6; + itv->vbi.start[1] = 318; + } + + /* setup VBI registers */ + itv->video_dec_func(itv, VIDIOC_S_FMT, &itv->vbi.in); + + /* determine number of lines and total number of VBI bytes. + A raw line takes 1443 bytes: 2 * 720 + 4 byte frame header - 1 + The '- 1' byte is probably an unused U or V byte. Or something... + A sliced line takes 51 bytes: 4 byte frame header, 4 byte internal + header, 42 data bytes + checksum (to be confirmed) */ + if (raw) { + lines = itv->vbi.count * 2; + } else { + lines = itv->is_60hz ? 24 : 38; + if (itv->is_60hz && (itv->hw_flags & IVTV_HW_CX25840)) + lines += 2; + } + + itv->vbi.enc_size = lines * (raw ? itv->vbi.raw_size : itv->vbi.sliced_size); + + /* Note: sliced vs raw flag doesn't seem to have any effect + TODO: check mode (0x02) value with older ivtv versions. */ + data[0] = raw | 0x02 | (0xbd << 8); + + /* Every X number of frames a VBI interrupt arrives (frames as in 25 or 30 fps) */ + data[1] = 1; + /* The VBI frames are stored in a ringbuffer with this size (with a VBI frame as unit) */ + data[2] = raw ? 4 : 8; + /* The start/stop codes determine which VBI lines end up in the raw VBI data area. + The codes are from table 24 in the saa7115 datasheet. Each raw/sliced/video line + is framed with codes FF0000XX where XX is the SAV/EAV (Start/End of Active Video) + code. These values for raw VBI are obtained from a driver disassembly. The sliced + start/stop codes was deduced from this, but they do not appear in the driver. + Other code pairs that I found are: 0x250E6249/0x13545454 and 0x25256262/0x38137F54. + However, I have no idea what these values are for. */ + if (itv->hw_flags & IVTV_HW_CX25840) { + /* Setup VBI for the cx25840 digitizer */ + if (raw) { + data[3] = 0x20602060; + data[4] = 0x30703070; + } else { + data[3] = 0xB0F0B0F0; + data[4] = 0xA0E0A0E0; + } + /* Lines per frame */ + data[5] = lines; + /* bytes per line */ + data[6] = (raw ? itv->vbi.raw_size : itv->vbi.sliced_size); + } else { + /* Setup VBI for the saa7115 digitizer */ + if (raw) { + data[3] = 0x25256262; + data[4] = 0x387F7F7F; + } else { + data[3] = 0xABABECEC; + data[4] = 0xB6F1F1F1; + } + /* Lines per frame */ + data[5] = lines; + /* bytes per line */ + data[6] = itv->vbi.enc_size / lines; + } + + IVTV_DEBUG_INFO( + "Setup VBI API header 0x%08x pkts %d buffs %d ln %d sz %d\n", + data[0], data[1], data[2], data[5], data[6]); + + ivtv_api(itv, CX2341X_ENC_SET_VBI_CONFIG, 7, data); + + /* returns the VBI encoder memory area. */ + itv->vbi.enc_start = data[2]; + itv->vbi.fpi = data[0]; + if (!itv->vbi.fpi) + itv->vbi.fpi = 1; + + IVTV_DEBUG_INFO("Setup VBI start 0x%08x frames %d fpi %d lines 0x%08x\n", + itv->vbi.enc_start, data[1], itv->vbi.fpi, itv->digitizer); + + /* select VBI lines. + Note that the sliced argument seems to have no effect. */ + for (i = 2; i <= 24; i++) { + int valid; + + if (itv->is_60hz) { + valid = i >= 10 && i < 22; + } else { + valid = i >= 6 && i < 24; + } + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, i - 1, + valid, 0 , 0, 0); + ivtv_vapi(itv, CX2341X_ENC_SET_VBI_LINE, 5, (i - 1) | 0x80000000, + valid, 0, 0, 0); + } + + /* Remaining VBI questions: + - Is it possible to select particular VBI lines only for inclusion in the MPEG + stream? Currently you can only get the first X lines. + - Is mixed raw and sliced VBI possible? + - What's the meaning of the raw/sliced flag? + - What's the meaning of params 2, 3 & 4 of the Select VBI command? */ +} + +int ivtv_start_v4l2_encode_stream(struct ivtv_stream *s) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv *itv = s->itv; + int captype = 0, subtype = 0; + int enable_passthrough = 0; + + if (s->v4l2dev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("Start encoder stream %s\n", s->name); + + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_MPG: + captype = 0; + subtype = 3; + + /* Stop Passthrough */ + if (itv->output_mode == OUT_PASSTHROUGH) { + ivtv_passthrough_mode(itv, 0); + enable_passthrough = 1; + } + itv->mpg_data_received = itv->vbi_data_inserted = 0; + itv->dualwatch_jiffies = jiffies; + itv->dualwatch_stereo_mode = itv->params.audio_properties & 0x0300; + itv->search_pack_header = 0; + break; + + case IVTV_ENC_STREAM_TYPE_YUV: + if (itv->output_mode == OUT_PASSTHROUGH) { + captype = 2; + subtype = 11; /* video+audio+decoder */ + break; + } + captype = 1; + subtype = 1; + break; + case IVTV_ENC_STREAM_TYPE_PCM: + captype = 1; + subtype = 2; + break; + case IVTV_ENC_STREAM_TYPE_VBI: + captype = 1; + subtype = 4; + + itv->vbi.frame = 0; + itv->vbi.inserted_frame = 0; + memset(itv->vbi.sliced_mpeg_size, + 0, sizeof(itv->vbi.sliced_mpeg_size)); + break; + default: + return -EINVAL; + } + s->subtype = subtype; + s->buffers_stolen = 0; + + /* mute/unmute video */ + ivtv_vapi(itv, CX2341X_ENC_MUTE_VIDEO, 1, test_bit(IVTV_F_I_RADIO_USER, &itv->i_flags) ? 1 : 0); + + /* Clear Streamoff flags in case left from last capture */ + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + if (atomic_read(&itv->capturing) == 0) { + /* Always use frame based mode. Experiments have demonstrated that byte + stream based mode results in dropped frames and corruption. Not often, + but occasionally. Many thanks go to Leonard Orb who spent a lot of + effort and time trying to trace the cause of the drop outs. */ + /* 1 frame per DMA */ + /*ivtv_vapi(itv, CX2341X_ENC_SET_DMA_BLOCK_SIZE, 2, 128, 0); */ + ivtv_vapi(itv, CX2341X_ENC_SET_DMA_BLOCK_SIZE, 2, 1, 1); + + /* Stuff from Windows, we don't know what it is */ + ivtv_vapi(itv, CX2341X_ENC_SET_VERT_CROP_LINE, 1, 0); + /* According to the docs, this should be correct. However, this is + untested. I don't dare enable this without having tested it. + Only very few old cards actually have this hardware combination. + ivtv_vapi(itv, CX2341X_ENC_SET_VERT_CROP_LINE, 1, + ((itv->hw_flags & IVTV_HW_SAA7114) && itv->is_60hz) ? 10001 : 0); + */ + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 3, !itv->has_cx23415); + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 8, 0); + ivtv_vapi(itv, CX2341X_ENC_MISC, 2, 4, 1); + ivtv_vapi(itv, CX2341X_ENC_MISC, 1, 12); + + /* assign placeholder */ + ivtv_vapi(itv, CX2341X_ENC_SET_PLACEHOLDER, 12, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + ivtv_vapi(itv, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, itv->digitizer, itv->digitizer); + + /* Setup VBI */ + if (itv->v4l2_cap & V4L2_CAP_VBI_CAPTURE) { + ivtv_vbi_setup(itv); + } + + /* assign program index info. Mask 7: select I/P/B, Num_req: 400 max */ + ivtv_vapi_result(itv, data, CX2341X_ENC_SET_PGM_INDEX_INFO, 2, 7, 400); + itv->pgm_info_offset = data[0]; + itv->pgm_info_num = data[1]; + itv->pgm_info_write_idx = 0; + itv->pgm_info_read_idx = 0; + + IVTV_DEBUG_INFO("PGM Index at 0x%08x with %d elements\n", + itv->pgm_info_offset, itv->pgm_info_num); + + /* Setup API for Stream */ + cx2341x_update(itv, ivtv_api_func, NULL, &itv->params); + } + + /* Vsync Setup */ + if (itv->has_cx23415 && !test_and_set_bit(IVTV_F_I_DIG_RST, &itv->i_flags)) { + /* event notification (on) */ + ivtv_vapi(itv, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, 0, 1, IVTV_IRQ_ENC_VIM_RST, -1); + ivtv_clear_irq_mask(itv, IVTV_IRQ_ENC_VIM_RST); + } + + if (atomic_read(&itv->capturing) == 0) { + /* Clear all Pending Interrupts */ + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + clear_bit(IVTV_F_I_EOS, &itv->i_flags); + + /* Initialize Digitizer for Capture */ + ivtv_vapi(itv, CX2341X_ENC_INITIALIZE_INPUT, 0); + + ivtv_sleep_timeout(HZ / 10, 0); + } + + /* begin_capture */ + if (ivtv_vapi(itv, CX2341X_ENC_START_CAPTURE, 2, captype, subtype)) + { + IVTV_DEBUG_WARN( "Error starting capture!\n"); + return -EINVAL; + } + + /* Start Passthrough */ + if (enable_passthrough) { + ivtv_passthrough_mode(itv, 1); + } + + if (s->type == IVTV_ENC_STREAM_TYPE_VBI) + ivtv_clear_irq_mask(itv, IVTV_IRQ_ENC_VBI_CAP); + else + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&itv->capturing); + return 0; +} + +static int ivtv_setup_v4l2_decode_stream(struct ivtv_stream *s) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + struct ivtv *itv = s->itv; + int datatype; + + if (s->v4l2dev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("Setting some initial decoder settings\n"); + + /* disable VBI signals, if the MPEG stream contains VBI data, + then that data will be processed automatically for you. */ + ivtv_disable_vbi(itv); + + /* set audio mode to left/stereo for dual/stereo mode. */ + ivtv_vapi(itv, CX2341X_DEC_SET_AUDIO_MODE, 2, itv->audio_bilingual_mode, itv->audio_stereo_mode); + + /* set number of internal decoder buffers */ + ivtv_vapi(itv, CX2341X_DEC_SET_DISPLAY_BUFFERS, 1, 0); + + /* prebuffering */ + ivtv_vapi(itv, CX2341X_DEC_SET_PREBUFFERING, 1, 1); + + /* extract from user packets */ + ivtv_vapi_result(itv, data, CX2341X_DEC_EXTRACT_VBI, 1, 1); + itv->vbi.dec_start = data[0]; + + IVTV_DEBUG_INFO("Decoder VBI RE-Insert start 0x%08x size 0x%08x\n", + itv->vbi.dec_start, data[1]); + + /* set decoder source settings */ + /* Data type: 0 = mpeg from host, + 1 = yuv from encoder, + 2 = yuv_from_host */ + switch (s->type) { + case IVTV_DEC_STREAM_TYPE_YUV: + datatype = itv->output_mode == OUT_PASSTHROUGH ? 1 : 2; + IVTV_DEBUG_INFO("Setup DEC YUV Stream data[0] = %d\n", datatype); + break; + case IVTV_DEC_STREAM_TYPE_MPG: + default: + datatype = 0; + break; + } + if (ivtv_vapi(itv, CX2341X_DEC_SET_DECODER_SOURCE, 4, datatype, + itv->params.width, itv->params.height, itv->params.audio_properties)) { + IVTV_DEBUG_WARN("COULDN'T INITIALIZE DECODER SOURCE\n"); + } + return 0; +} + +int ivtv_start_v4l2_decode_stream(struct ivtv_stream *s, int gop_offset) +{ + struct ivtv *itv = s->itv; + + if (s->v4l2dev == NULL) + return -EINVAL; + + if (test_and_set_bit(IVTV_F_S_STREAMING, &s->s_flags)) + return 0; /* already started */ + + IVTV_DEBUG_INFO("Starting decode stream %s (gop_offset %d)\n", s->name, gop_offset); + + /* Clear Streamoff */ + if (s->type == IVTV_DEC_STREAM_TYPE_YUV) { + /* Initialize Decoder */ + /* Reprogram Decoder YUV Buffers for YUV */ + write_reg(yuv_offset[0] >> 4, 0x82c); + write_reg((yuv_offset[0] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x830); + write_reg(yuv_offset[0] >> 4, 0x834); + write_reg((yuv_offset[0] + IVTV_YUV_BUFFER_UV_OFFSET) >> 4, 0x838); + + write_reg_sync(0x00000000 | (0x0c << 16) | (0x0b << 8), 0x2d24); + + write_reg_sync(0x00108080, 0x2898); + /* Enable YUV decoder output */ + write_reg_sync(0x01, IVTV_REG_VDM); + } + + ivtv_setup_v4l2_decode_stream(s); + + /* set dma size to 65536 bytes */ + ivtv_vapi(itv, CX2341X_DEC_SET_DMA_BLOCK_SIZE, 1, 65536); + + clear_bit(IVTV_F_S_STREAMOFF, &s->s_flags); + + /* Zero out decoder counters */ + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_FIELD_DISPLAYED].data[0]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_FIELD_DISPLAYED].data[1]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_FIELD_DISPLAYED].data[2]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_FIELD_DISPLAYED].data[3]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[0]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[1]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[2]); + writel(0, &itv->dec_mbox.mbox[IVTV_MBOX_DMA].data[3]); + + /* turn on notification of dual/stereo mode change */ + ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 1, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); + + /* start playback */ + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, gop_offset, 0); + + /* Clear the following Interrupt mask bits for decoding */ + ivtv_clear_irq_mask(itv, IVTV_IRQ_MASK_DECODE); + IVTV_DEBUG_IRQ("IRQ Mask is now: 0x%08x\n", itv->irqmask); + + /* you're live! sit back and await interrupts :) */ + atomic_inc(&itv->decoding); + return 0; +} + +void ivtv_stop_all_captures(struct ivtv *itv) +{ + int i; + + for (i = IVTV_MAX_STREAMS - 1; i >= 0; i--) { + struct ivtv_stream *s = &itv->streams[i]; + + if (s->v4l2dev == NULL) + continue; + if (test_bit(IVTV_F_S_STREAMING, &s->s_flags)) { + ivtv_stop_v4l2_encode_stream(s, 0); + } + } +} + +int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end) +{ + struct ivtv *itv = s->itv; + DECLARE_WAITQUEUE(wait, current); + int cap_type; + unsigned long then; + int stopmode; + u32 data[CX2341X_MBOX_MAX_DATA]; + + if (s->v4l2dev == NULL) + return -EINVAL; + + /* This function assumes that you are allowed to stop the capture + and that we are actually capturing */ + + IVTV_DEBUG_INFO("Stop Capture\n"); + + if (s->type == IVTV_DEC_STREAM_TYPE_VOUT) + return 0; + if (atomic_read(&itv->capturing) == 0) + return 0; + + switch (s->type) { + case IVTV_ENC_STREAM_TYPE_YUV: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_PCM: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_VBI: + cap_type = 1; + break; + case IVTV_ENC_STREAM_TYPE_MPG: + default: + cap_type = 0; + break; + } + + /* Stop Capture Mode */ + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && gop_end) { + stopmode = 0; + } else { + stopmode = 1; + } + + /* end_capture */ + /* when: 0 = end of GOP 1 = NOW!, type: 0 = mpeg, subtype: 3 = video+audio */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, stopmode, cap_type, s->subtype); + + /* only run these if we're shutting down the last cap */ + if (atomic_read(&itv->capturing) - 1 == 0) { + /* event notification (off) */ + if (test_and_clear_bit(IVTV_F_I_DIG_RST, &itv->i_flags)) { + /* type: 0 = refresh */ + /* on/off: 0 = off, intr: 0x10000000, mbox_id: -1: none */ + ivtv_vapi(itv, CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_ENC_VIM_RST, -1); + ivtv_set_irq_mask(itv, IVTV_IRQ_ENC_VIM_RST); + } + } + + then = jiffies; + + if (!test_bit(IVTV_F_S_PASSTHROUGH, &s->s_flags)) { + if (s->type == IVTV_ENC_STREAM_TYPE_MPG && gop_end) { + /* only run these if we're shutting down the last cap */ + unsigned long duration; + + then = jiffies; + add_wait_queue(&itv->cap_w, &wait); + + set_current_state(TASK_INTERRUPTIBLE); + + /* wait 2s for EOS interrupt */ + while (!test_bit(IVTV_F_I_EOS, &itv->i_flags) && jiffies < then + 2 * HZ) { + schedule_timeout(HZ / 100); + } + + /* To convert jiffies to ms, we must multiply by 1000 + * and divide by HZ. To avoid runtime division, we + * convert this to multiplication by 1000/HZ. + * Since integer division truncates, we get the best + * accuracy if we do a rounding calculation of the constant. + * Think of the case where HZ is 1024. + */ + duration = ((1000 + HZ / 2) / HZ) * (jiffies - then); + + if (!test_bit(IVTV_F_I_EOS, &itv->i_flags)) { + IVTV_DEBUG_WARN("%s: EOS interrupt not received! stopping anyway.\n", s->name); + IVTV_DEBUG_WARN("%s: waited %lu ms.\n", s->name, duration); + } else { + IVTV_DEBUG_INFO("%s: EOS took %lu ms to occur.\n", s->name, duration); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&itv->cap_w, &wait); + } + + then = jiffies; + /* Make sure DMA is complete */ + add_wait_queue(&s->waitq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + do { + /* check if DMA is pending */ + if ((s->type == IVTV_ENC_STREAM_TYPE_MPG) && /* MPG Only */ + (read_reg(IVTV_REG_DMASTATUS) & 0x02)) { + /* Check for last DMA */ + ivtv_vapi_result(itv, data, CX2341X_ENC_GET_SEQ_END, 2, 0, 0); + + if (data[0] == 1) { + IVTV_DEBUG_DMA("%s: Last DMA of size 0x%08x\n", s->name, data[1]); + break; + } + } else if (read_reg(IVTV_REG_DMASTATUS) & 0x02) { + break; + } + + ivtv_sleep_timeout(HZ / 100, 1); + } while (then + HZ * 2 > jiffies); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&s->waitq, &wait); + } + + atomic_dec(&itv->capturing); + + /* Clear capture and no-read bits */ + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + + if (s->type == IVTV_ENC_STREAM_TYPE_VBI) + ivtv_set_irq_mask(itv, IVTV_IRQ_ENC_VBI_CAP); + + if (atomic_read(&itv->capturing) > 0) { + return 0; + } + + /* Set the following Interrupt mask bits for capture */ + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_CAPTURE); + + wake_up(&s->waitq); + + return 0; +} + +int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts) +{ + struct ivtv *itv = s->itv; + + if (s->v4l2dev == NULL) + return -EINVAL; + + if (s->type != IVTV_DEC_STREAM_TYPE_YUV && s->type != IVTV_DEC_STREAM_TYPE_MPG) + return -EINVAL; + + if (!test_bit(IVTV_F_S_STREAMING, &s->s_flags)) + return 0; + + IVTV_DEBUG_INFO("Stop Decode at %llu, flags: %x\n", pts, flags); + + /* Stop Decoder */ + if (!(flags & VIDEO_CMD_STOP_IMMEDIATELY) || pts) { + u32 tmp = 0; + + /* Wait until the decoder is no longer running */ + if (pts) { + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, + 0, (u32)(pts & 0xffffffff), (u32)(pts >> 32)); + } + while (1) { + u32 data[CX2341X_MBOX_MAX_DATA]; + ivtv_vapi_result(itv, data, CX2341X_DEC_GET_XFER_INFO, 0); + if (s->q_full.buffers + s->q_dma.buffers == 0) { + if (tmp == data[3]) + break; + tmp = data[3]; + } + if (ivtv_sleep_timeout(HZ/10, 1)) + break; + } + } + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, flags & VIDEO_CMD_STOP_TO_BLACK, 0, 0); + + /* turn off notification of dual/stereo mode change */ + ivtv_vapi(itv, CX2341X_DEC_SET_EVENT_NOTIFICATION, 4, 0, 0, IVTV_IRQ_DEC_AUD_MODE_CHG, -1); + + ivtv_set_irq_mask(itv, IVTV_IRQ_MASK_DECODE); + + clear_bit(IVTV_F_S_NEEDS_DATA, &s->s_flags); + clear_bit(IVTV_F_S_STREAMING, &s->s_flags); + ivtv_flush_queues(s); + + if (!test_bit(IVTV_F_S_PASSTHROUGH, &s->s_flags)) { + /* disable VBI on TV-out */ + ivtv_disable_vbi(itv); + } + + /* decrement decoding */ + atomic_dec(&itv->decoding); + + set_bit(IVTV_F_I_EV_DEC_STOPPED, &itv->i_flags); + wake_up(&itv->event_waitq); + + /* wake up wait queues */ + wake_up(&s->waitq); + + return 0; +} + +int ivtv_passthrough_mode(struct ivtv *itv, int enable) +{ + struct ivtv_stream *yuv_stream = &itv->streams[IVTV_ENC_STREAM_TYPE_YUV]; + struct ivtv_stream *dec_stream = &itv->streams[IVTV_DEC_STREAM_TYPE_YUV]; + + if (yuv_stream->v4l2dev == NULL || dec_stream->v4l2dev == NULL) + return -EINVAL; + + IVTV_DEBUG_INFO("ivtv ioctl: Select passthrough mode\n"); + + /* Prevent others from starting/stopping streams while we + initiate/terminate passthrough mode */ + if (enable) { + if (itv->output_mode == OUT_PASSTHROUGH) { + return 0; + } + if (ivtv_set_output_mode(itv, OUT_PASSTHROUGH) != OUT_PASSTHROUGH) + return -EBUSY; + + /* Fully initialize stream, and then unflag init */ + set_bit(IVTV_F_S_PASSTHROUGH, &dec_stream->s_flags); + set_bit(IVTV_F_S_STREAMING, &dec_stream->s_flags); + + /* Setup YUV Decoder */ + ivtv_setup_v4l2_decode_stream(dec_stream); + + /* Start Decoder */ + ivtv_vapi(itv, CX2341X_DEC_START_PLAYBACK, 2, 0, 1); + atomic_inc(&itv->decoding); + + /* Setup capture if not already done */ + if (atomic_read(&itv->capturing) == 0) { + cx2341x_update(itv, ivtv_api_func, NULL, &itv->params); + } + + /* Start Passthrough Mode */ + ivtv_vapi(itv, CX2341X_ENC_START_CAPTURE, 2, 2, 11); + atomic_inc(&itv->capturing); + return 0; + } + + if (itv->output_mode != OUT_PASSTHROUGH) + return 0; + + /* Stop Passthrough Mode */ + ivtv_vapi(itv, CX2341X_ENC_STOP_CAPTURE, 3, 1, 2, 11); + ivtv_vapi(itv, CX2341X_DEC_STOP_PLAYBACK, 3, 1, 0, 0); + + atomic_dec(&itv->capturing); + atomic_dec(&itv->decoding); + clear_bit(IVTV_F_S_PASSTHROUGH, &dec_stream->s_flags); + clear_bit(IVTV_F_S_STREAMING, &dec_stream->s_flags); + itv->output_mode = OUT_NONE; + + return 0; +} diff --git a/drivers/media/video/ivtv/ivtv-streams.h b/drivers/media/video/ivtv/ivtv-streams.h new file mode 100644 index 00000000000..8597b75384a --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-streams.h @@ -0,0 +1,31 @@ +/* + init/start/stop/exit stream functions + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +int ivtv_streams_setup(struct ivtv *itv); +void ivtv_streams_cleanup(struct ivtv *itv); + +/* Capture related */ +int ivtv_start_v4l2_encode_stream(struct ivtv_stream *s); +int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end); +int ivtv_start_v4l2_decode_stream(struct ivtv_stream *s, int gop_offset); +int ivtv_stop_v4l2_decode_stream(struct ivtv_stream *s, int flags, u64 pts); + +void ivtv_stop_all_captures(struct ivtv *itv); +int ivtv_passthrough_mode(struct ivtv *itv, int enable); diff --git a/drivers/media/video/ivtv/ivtv-udma.c b/drivers/media/video/ivtv/ivtv-udma.c new file mode 100644 index 00000000000..bd642e1aafc --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-udma.c @@ -0,0 +1,200 @@ +/* + User DMA + + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-streams.h" +#include "ivtv-udma.h" + +void ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size) +{ + dma_page->uaddr = first & PAGE_MASK; + dma_page->offset = first & ~PAGE_MASK; + dma_page->tail = 1 + ((first+size-1) & ~PAGE_MASK); + dma_page->first = (first & PAGE_MASK) >> PAGE_SHIFT; + dma_page->last = ((first+size-1) & PAGE_MASK) >> PAGE_SHIFT; + dma_page->page_count = dma_page->last - dma_page->first + 1; + if (dma_page->page_count == 1) dma_page->tail -= dma_page->offset; +} + +int ivtv_udma_fill_sg_list (struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset) +{ + int i, offset; + + offset = dma_page->offset; + + /* Fill SG Array with new values */ + for (i = 0; i < dma_page->page_count; i++) { + if (i == dma_page->page_count - 1) { + dma->SGlist[map_offset].length = dma_page->tail; + } + else { + dma->SGlist[map_offset].length = PAGE_SIZE - offset; + } + dma->SGlist[map_offset].offset = offset; + dma->SGlist[map_offset].page = dma->map[map_offset]; + offset = 0; + map_offset++; + } + return map_offset; +} + +void ivtv_udma_fill_sg_array (struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split) { + int i; + struct scatterlist *sg; + + for (i = 0, sg = dma->SGlist; i < dma->SG_length; i++, sg++) { + dma->SGarray[i].size = cpu_to_le32(sg_dma_len(sg)); + dma->SGarray[i].src = cpu_to_le32(sg_dma_address(sg)); + dma->SGarray[i].dst = cpu_to_le32(buffer_offset); + buffer_offset += sg_dma_len(sg); + + split -= sg_dma_len(sg); + if (split == 0) + buffer_offset = buffer_offset_2; + } +} + +/* User DMA Buffers */ +void ivtv_udma_alloc(struct ivtv *itv) +{ + if (itv->udma.SG_handle == 0) { + /* Map DMA Page Array Buffer */ + itv->udma.SG_handle = pci_map_single(itv->dev, itv->udma.SGarray, + sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); + ivtv_udma_sync_for_cpu(itv); + } +} + +int ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, + void __user *userbuf, int size_in_bytes) +{ + struct ivtv_dma_page_info user_dma; + struct ivtv_user_dma *dma = &itv->udma; + int err; + + IVTV_DEBUG_DMA("ivtv_udma_setup, dst: 0x%08x\n", (unsigned int)ivtv_dest_addr); + + /* Still in USE */ + if (dma->SG_length || dma->page_count) { + IVTV_DEBUG_WARN("ivtv_udma_setup: SG_length %d page_count %d still full?\n", + dma->SG_length, dma->page_count); + return -EBUSY; + } + + ivtv_udma_get_page_info(&user_dma, (unsigned long)userbuf, size_in_bytes); + + if (user_dma.page_count <= 0) { + IVTV_DEBUG_WARN("ivtv_udma_setup: Error %d page_count from %d bytes %d offset\n", + user_dma.page_count, size_in_bytes, user_dma.offset); + return -EINVAL; + } + + /* Get user pages for DMA Xfer */ + down_read(¤t->mm->mmap_sem); + err = get_user_pages(current, current->mm, + user_dma.uaddr, user_dma.page_count, 0, 1, dma->map, NULL); + up_read(¤t->mm->mmap_sem); + + if (user_dma.page_count != err) { + IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n", + err, user_dma.page_count); + return -EINVAL; + } + + dma->page_count = user_dma.page_count; + + /* Fill SG List with new values */ + ivtv_udma_fill_sg_list(dma, &user_dma, 0); + + /* Map SG List */ + dma->SG_length = pci_map_sg(itv->dev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); + + /* Fill SG Array with new values */ + ivtv_udma_fill_sg_array (dma, ivtv_dest_addr, 0, -1); + + /* Tag SG Array with Interrupt Bit */ + dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); + + ivtv_udma_sync_for_device(itv); + return dma->page_count; +} + +void ivtv_udma_unmap(struct ivtv *itv) +{ + struct ivtv_user_dma *dma = &itv->udma; + int i; + + IVTV_DEBUG_INFO("ivtv_unmap_user_dma\n"); + + /* Nothing to free */ + if (dma->page_count == 0) + return; + + /* Unmap Scatterlist */ + if (dma->SG_length) { + pci_unmap_sg(itv->dev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); + dma->SG_length = 0; + } + /* sync DMA */ + ivtv_udma_sync_for_cpu(itv); + + /* Release User Pages */ + for (i = 0; i < dma->page_count; i++) { + put_page(dma->map[i]); + } + dma->page_count = 0; +} + +void ivtv_udma_free(struct ivtv *itv) +{ + /* Unmap SG Array */ + if (itv->udma.SG_handle) { + pci_unmap_single(itv->dev, itv->udma.SG_handle, + sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); + } + + /* Unmap Scatterlist */ + if (itv->udma.SG_length) { + pci_unmap_sg(itv->dev, itv->udma.SGlist, itv->udma.page_count, PCI_DMA_TODEVICE); + } +} + +void ivtv_udma_start(struct ivtv *itv) +{ + IVTV_DEBUG_DMA("start UDMA\n"); + write_reg(itv->udma.SG_handle, IVTV_REG_DECDMAADDR); + write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); + set_bit(IVTV_F_I_DMA, &itv->i_flags); + set_bit(IVTV_F_I_UDMA, &itv->i_flags); +} + +void ivtv_udma_prepare(struct ivtv *itv) +{ + unsigned long flags; + + spin_lock_irqsave(&itv->dma_reg_lock, flags); + if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) + ivtv_udma_start(itv); + else + set_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); + spin_unlock_irqrestore(&itv->dma_reg_lock, flags); +} diff --git a/drivers/media/video/ivtv/ivtv-udma.h b/drivers/media/video/ivtv/ivtv-udma.h new file mode 100644 index 00000000000..e131bccedec --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-udma.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + Copyright (C) 2006-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* User DMA functions */ +void ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size); +int ivtv_udma_fill_sg_list(struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset); +void ivtv_udma_fill_sg_array(struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split); +int ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, + void __user *userbuf, int size_in_bytes); +void ivtv_udma_unmap(struct ivtv *itv); +void ivtv_udma_free(struct ivtv *itv); +void ivtv_udma_alloc(struct ivtv *itv); +void ivtv_udma_prepare(struct ivtv *itv); +void ivtv_udma_start(struct ivtv *itv); + +static inline void ivtv_udma_sync_for_device(struct ivtv *itv) +{ + pci_dma_sync_single_for_device((struct pci_dev *)itv->dev, itv->udma.SG_handle, + sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); +} + +static inline void ivtv_udma_sync_for_cpu(struct ivtv *itv) +{ + pci_dma_sync_single_for_cpu((struct pci_dev *)itv->dev, itv->udma.SG_handle, + sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); +} diff --git a/drivers/media/video/ivtv/ivtv-vbi.c b/drivers/media/video/ivtv/ivtv-vbi.c new file mode 100644 index 00000000000..5efa5a86781 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-vbi.c @@ -0,0 +1,538 @@ +/* + Vertical Blank Interval support functions + Copyright (C) 2004-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-video.h" +#include "ivtv-vbi.h" +#include "ivtv-ioctl.h" +#include "ivtv-queue.h" + +static int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static void passthrough_vbi_data(struct ivtv *itv, int cnt) +{ + int wss = 0; + u8 cc[4] = { 0x80, 0x80, 0x80, 0x80 }; + u8 vps[13]; + int found_cc = 0; + int found_wss = 0; + int found_vps = 0; + int cc_pos = itv->vbi.cc_pos; + int i; + + for (i = 0; i < cnt; i++) { + struct v4l2_sliced_vbi_data *d = itv->vbi.sliced_dec_data + i; + + if (d->id == V4L2_SLICED_CAPTION_525 && d->line == 21) { + found_cc = 1; + if (d->field) { + cc[2] = d->data[0]; + cc[3] = d->data[1]; + } else { + cc[0] = d->data[0]; + cc[1] = d->data[1]; + } + } + else if (d->id == V4L2_SLICED_VPS && d->line == 16 && d->field == 0) { + memcpy(vps, d->data, sizeof(vps)); + found_vps = 1; + } + else if (d->id == V4L2_SLICED_WSS_625 && d->line == 23 && d->field == 0) { + wss = d->data[0] | d->data[1] << 8; + found_wss = 1; + } + } + + if (itv->vbi.wss_found != found_wss || itv->vbi.wss != wss) { + itv->vbi.wss = wss; + itv->vbi.wss_found = found_wss; + set_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags); + } + + if (found_vps || itv->vbi.vps_found) { + itv->vbi.vps[0] = vps[2]; + itv->vbi.vps[1] = vps[8]; + itv->vbi.vps[2] = vps[9]; + itv->vbi.vps[3] = vps[10]; + itv->vbi.vps[4] = vps[11]; + itv->vbi.vps_found = found_vps; + set_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags); + } + + if (found_cc && cc_pos < sizeof(itv->vbi.cc_data_even)) { + itv->vbi.cc_data_odd[cc_pos] = cc[0]; + itv->vbi.cc_data_odd[cc_pos + 1] = cc[1]; + itv->vbi.cc_data_even[cc_pos] = cc[2]; + itv->vbi.cc_data_even[cc_pos + 1] = cc[3]; + itv->vbi.cc_pos = cc_pos + 2; + set_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + } +} + +static void copy_vbi_data(struct ivtv *itv, int lines, u32 pts_stamp) +{ + int line = 0; + int i; + u32 linemask[2] = { 0, 0 }; + unsigned short size; + static const u8 mpeg_hdr_data[] = { + 0x00, 0x00, 0x01, 0xba, 0x44, 0x00, 0x0c, 0x66, + 0x24, 0x01, 0x01, 0xd1, 0xd3, 0xfa, 0xff, 0xff, + 0x00, 0x00, 0x01, 0xbd, 0x00, 0x1a, 0x84, 0x80, + 0x07, 0x21, 0x00, 0x5d, 0x63, 0xa7, 0xff, 0xff + }; + const int sd = sizeof(mpeg_hdr_data); /* start of vbi data */ + int idx = itv->vbi.frame % IVTV_VBI_FRAMES; + u8 *dst = &itv->vbi.sliced_mpeg_data[idx][0]; + + for (i = 0; i < lines; i++) { + int f, l; + + if (itv->vbi.sliced_data[i].id == 0) + continue; + + l = itv->vbi.sliced_data[i].line - 6; + f = itv->vbi.sliced_data[i].field; + if (f) + l += 18; + if (l < 32) + linemask[0] |= (1 << l); + else + linemask[1] |= (1 << (l - 32)); + dst[sd + 12 + line * 43] = service2vbi(itv->vbi.sliced_data[i].id); + memcpy(dst + sd + 12 + line * 43 + 1, itv->vbi.sliced_data[i].data, 42); + line++; + } + memcpy(dst, mpeg_hdr_data, sizeof(mpeg_hdr_data)); + if (line == 36) { + /* All lines are used, so there is no space for the linemask + (the max size of the VBI data is 36 * 43 + 4 bytes). + So in this case we use the magic number 'ITV0'. */ + memcpy(dst + sd, "ITV0", 4); + memcpy(dst + sd + 4, dst + sd + 12, line * 43); + size = 4 + ((43 * line + 3) & ~3); + } else { + memcpy(dst + sd, "itv0", 4); + memcpy(dst + sd + 4, &linemask[0], 8); + size = 12 + ((43 * line + 3) & ~3); + } + dst[4+16] = (size + 10) >> 8; + dst[5+16] = (size + 10) & 0xff; + dst[9+16] = 0x21 | ((pts_stamp >> 29) & 0x6); + dst[10+16] = (pts_stamp >> 22) & 0xff; + dst[11+16] = 1 | ((pts_stamp >> 14) & 0xff); + dst[12+16] = (pts_stamp >> 7) & 0xff; + dst[13+16] = 1 | ((pts_stamp & 0x7f) << 1); + itv->vbi.sliced_mpeg_size[idx] = sd + size; +} + +static int ivtv_convert_ivtv_vbi(struct ivtv *itv, u8 *p) +{ + u32 linemask[2]; + int i, l, id2; + int line = 0; + + if (!memcmp(p, "itv0", 4)) { + memcpy(linemask, p + 4, 8); + p += 12; + } else if (!memcmp(p, "ITV0", 4)) { + linemask[0] = 0xffffffff; + linemask[1] = 0xf; + p += 4; + } else { + /* unknown VBI data stream */ + return 0; + } + for (i = 0; i < 36; i++) { + int err = 0; + + if (i < 32 && !(linemask[0] & (1 << i))) + continue; + if (i >= 32 && !(linemask[1] & (1 << (i - 32)))) + continue; + id2 = *p & 0xf; + switch (id2) { + case IVTV_SLICED_TYPE_TELETEXT_B: + id2 = V4L2_SLICED_TELETEXT_B; + break; + case IVTV_SLICED_TYPE_CAPTION_525: + id2 = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[1]) || !odd_parity(p[2]); + break; + case IVTV_SLICED_TYPE_VPS: + id2 = V4L2_SLICED_VPS; + break; + case IVTV_SLICED_TYPE_WSS_625: + id2 = V4L2_SLICED_WSS_625; + break; + default: + id2 = 0; + break; + } + if (err == 0) { + l = (i < 18) ? i + 6 : i - 18 + 6; + itv->vbi.sliced_dec_data[line].line = l; + itv->vbi.sliced_dec_data[line].field = i >= 18; + itv->vbi.sliced_dec_data[line].id = id2; + memcpy(itv->vbi.sliced_dec_data[line].data, p + 1, 42); + line++; + } + p += 43; + } + while (line < 36) { + itv->vbi.sliced_dec_data[line].id = 0; + itv->vbi.sliced_dec_data[line].line = 0; + itv->vbi.sliced_dec_data[line].field = 0; + line++; + } + return line * sizeof(itv->vbi.sliced_dec_data[0]); +} + +ssize_t ivtv_write_vbi(struct ivtv *itv, const char __user *ubuf, size_t count) +{ + /* Should be a __user pointer, but sparse doesn't parse this bit correctly. */ + const struct v4l2_sliced_vbi_data *p = (const struct v4l2_sliced_vbi_data *)ubuf; + u8 cc[4] = { 0x80, 0x80, 0x80, 0x80 }; + int found_cc = 0; + int cc_pos = itv->vbi.cc_pos; + + if (itv->vbi.service_set_out == 0) + return -EPERM; + + while (count >= sizeof(struct v4l2_sliced_vbi_data)) { + switch (p->id) { + case V4L2_SLICED_CAPTION_525: + if (p->id == V4L2_SLICED_CAPTION_525 && + p->line == 21 && + (itv->vbi.service_set_out & + V4L2_SLICED_CAPTION_525) == 0) { + break; + } + found_cc = 1; + if (p->field) { + cc[2] = p->data[0]; + cc[3] = p->data[1]; + } else { + cc[0] = p->data[0]; + cc[1] = p->data[1]; + } + break; + + case V4L2_SLICED_VPS: + if (p->line == 16 && p->field == 0 && + (itv->vbi.service_set_out & V4L2_SLICED_VPS)) { + itv->vbi.vps[0] = p->data[2]; + itv->vbi.vps[1] = p->data[8]; + itv->vbi.vps[2] = p->data[9]; + itv->vbi.vps[3] = p->data[10]; + itv->vbi.vps[4] = p->data[11]; + itv->vbi.vps_found = 1; + set_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags); + } + break; + + case V4L2_SLICED_WSS_625: + if (p->line == 23 && p->field == 0 && + (itv->vbi.service_set_out & V4L2_SLICED_WSS_625)) { + /* No lock needed for WSS */ + itv->vbi.wss = p->data[0] | (p->data[1] << 8); + itv->vbi.wss_found = 1; + set_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags); + } + break; + + default: + break; + } + count -= sizeof(*p); + p++; + } + + if (found_cc && cc_pos < sizeof(itv->vbi.cc_data_even)) { + itv->vbi.cc_data_odd[cc_pos] = cc[0]; + itv->vbi.cc_data_odd[cc_pos + 1] = cc[1]; + itv->vbi.cc_data_even[cc_pos] = cc[2]; + itv->vbi.cc_data_even[cc_pos + 1] = cc[3]; + itv->vbi.cc_pos = cc_pos + 2; + set_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + } + + return (const char __user *)p - ubuf; +} + +/* Compress raw VBI format, removes leading SAV codes and surplus space after the + field. + Returns new compressed size. */ +static u32 compress_raw_buf(struct ivtv *itv, u8 *buf, u32 size) +{ + u32 line_size = itv->vbi.raw_decoder_line_size; + u32 lines = itv->vbi.count; + u8 sav1 = itv->vbi.raw_decoder_sav_odd_field; + u8 sav2 = itv->vbi.raw_decoder_sav_even_field; + u8 *q = buf; + u8 *p; + int i; + + for (i = 0; i < lines; i++) { + p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || (p[3] != sav1 && p[3] != sav2)) { + break; + } + memcpy(q, p + 4, line_size - 4); + q += line_size - 4; + } + return lines * (line_size - 4); +} + + +/* Compressed VBI format, all found sliced blocks put next to one another + Returns new compressed size */ +static u32 compress_sliced_buf(struct ivtv *itv, u32 line, u8 *buf, u32 size, u8 sav) +{ + u32 line_size = itv->vbi.sliced_decoder_line_size; + struct v4l2_decode_vbi_line vbi; + int i; + + /* find the first valid line */ + for (i = 0; i < size; i++, buf++) { + if (buf[0] == 0xff && !buf[1] && !buf[2] && buf[3] == sav) + break; + } + + size -= i; + if (size < line_size) { + return line; + } + for (i = 0; i < size / line_size; i++) { + u8 *p = buf + i * line_size; + + /* Look for SAV code */ + if (p[0] != 0xff || p[1] || p[2] || p[3] != sav) { + continue; + } + vbi.p = p + 4; + itv->video_dec_func(itv, VIDIOC_INT_DECODE_VBI_LINE, &vbi); + if (vbi.type) { + itv->vbi.sliced_data[line].id = vbi.type; + itv->vbi.sliced_data[line].field = vbi.is_second_field; + itv->vbi.sliced_data[line].line = vbi.line; + memcpy(itv->vbi.sliced_data[line].data, vbi.p, 42); + line++; + } + } + return line; +} + +void ivtv_process_vbi_data(struct ivtv *itv, struct ivtv_buffer *buf, + u64 pts_stamp, int streamtype) +{ + u8 *p = (u8 *) buf->buf; + u32 size = buf->bytesused; + int y; + + /* Raw VBI data */ + if (streamtype == IVTV_ENC_STREAM_TYPE_VBI && itv->vbi.sliced_in->service_set == 0) { + u8 type; + + ivtv_buf_swap(buf); + + type = p[3]; + + size = buf->bytesused = compress_raw_buf(itv, p, size); + + /* second field of the frame? */ + if (type == itv->vbi.raw_decoder_sav_even_field) { + /* Dirty hack needed for backwards + compatibility of old VBI software. */ + p += size - 4; + memcpy(p, &itv->vbi.frame, 4); + itv->vbi.frame++; + } + return; + } + + /* Sliced VBI data with data insertion */ + if (streamtype == IVTV_ENC_STREAM_TYPE_VBI) { + int lines; + + ivtv_buf_swap(buf); + + /* first field */ + lines = compress_sliced_buf(itv, 0, p, size / 2, + itv->vbi.sliced_decoder_sav_odd_field); + /* second field */ + /* experimentation shows that the second half does not always begin + at the exact address. So start a bit earlier (hence 32). */ + lines = compress_sliced_buf(itv, lines, p + size / 2 - 32, size / 2 + 32, + itv->vbi.sliced_decoder_sav_even_field); + /* always return at least one empty line */ + if (lines == 0) { + itv->vbi.sliced_data[0].id = 0; + itv->vbi.sliced_data[0].line = 0; + itv->vbi.sliced_data[0].field = 0; + lines = 1; + } + buf->bytesused = size = lines * sizeof(itv->vbi.sliced_data[0]); + memcpy(p, &itv->vbi.sliced_data[0], size); + + if (itv->vbi.insert_mpeg) { + copy_vbi_data(itv, lines, pts_stamp); + } + itv->vbi.frame++; + return; + } + + /* Sliced VBI re-inserted from an MPEG stream */ + if (streamtype == IVTV_DEC_STREAM_TYPE_VBI) { + /* If the size is not 4-byte aligned, then the starting address + for the swapping is also shifted. After swapping the data the + real start address of the VBI data is exactly 4 bytes after the + original start. It's a bit fiddly but it works like a charm. + Non-4-byte alignment happens when an lseek is done on the input + mpeg file to a non-4-byte aligned position. So on arrival here + the VBI data is also non-4-byte aligned. */ + int offset = size & 3; + int cnt; + + if (offset) { + p += 4 - offset; + } + /* Swap Buffer */ + for (y = 0; y < size; y += 4) { + swab32s((u32 *)(p + y)); + } + + cnt = ivtv_convert_ivtv_vbi(itv, p + offset); + memcpy(buf->buf, itv->vbi.sliced_dec_data, cnt); + buf->bytesused = cnt; + + passthrough_vbi_data(itv, cnt / sizeof(itv->vbi.sliced_dec_data[0])); + return; + } +} + +void ivtv_disable_vbi(struct ivtv *itv) +{ + clear_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags); + clear_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags); + clear_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + ivtv_set_wss(itv, 0, 0); + ivtv_set_cc(itv, 0, 0, 0, 0, 0); + ivtv_set_vps(itv, 0, 0, 0, 0, 0, 0); + itv->vbi.vps_found = itv->vbi.wss_found = 0; + itv->vbi.wss = 0; + itv->vbi.cc_pos = 0; +} + + +void vbi_work_handler(struct ivtv *itv) +{ + struct v4l2_sliced_vbi_data data; + + /* Lock */ + if (itv->output_mode == OUT_PASSTHROUGH) { + /* Note: currently only the saa7115 is used in a PVR350, + so these commands are for now saa7115 specific. */ + if (itv->is_50hz) { + data.id = V4L2_SLICED_WSS_625; + data.field = 0; + + if (itv->video_dec_func(itv, VIDIOC_INT_G_VBI_DATA, &data) == 0) { + ivtv_set_wss(itv, 1, data.data[0] & 0xf); + itv->vbi.wss_no_update = 0; + } else if (itv->vbi.wss_no_update == 4) { + ivtv_set_wss(itv, 1, 0x8); /* 4x3 full format */ + } else { + itv->vbi.wss_no_update++; + } + } + else { + u8 c1 = 0, c2 = 0, c3 = 0, c4 = 0; + int mode = 0; + + data.id = V4L2_SLICED_CAPTION_525; + data.field = 0; + if (itv->video_dec_func(itv, VIDIOC_INT_G_VBI_DATA, &data) == 0) { + mode |= 1; + c1 = data.data[0]; + c2 = data.data[1]; + } + data.field = 1; + if (itv->video_dec_func(itv, VIDIOC_INT_G_VBI_DATA, &data) == 0) { + mode |= 2; + c3 = data.data[0]; + c4 = data.data[1]; + } + if (mode) { + itv->vbi.cc_no_update = 0; + ivtv_set_cc(itv, mode, c1, c2, c3, c4); + } else if (itv->vbi.cc_no_update == 4) { + ivtv_set_cc(itv, 0, 0, 0, 0, 0); + } else { + itv->vbi.cc_no_update++; + } + } + return; + } + + if (test_and_clear_bit(IVTV_F_I_UPDATE_WSS, &itv->i_flags)) { + /* Lock */ + ivtv_set_wss(itv, itv->vbi.wss_found, itv->vbi.wss & 0xf); + } + + if (test_and_clear_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags)) { + if (itv->vbi.cc_pos == 0) { + ivtv_set_cc(itv, 3, 0x80, 0x80, 0x80, 0x80); + } + while (itv->vbi.cc_pos) { + u8 cc_odd0 = itv->vbi.cc_data_odd[0]; + u8 cc_odd1 = itv->vbi.cc_data_odd[1]; + u8 cc_even0 = itv->vbi.cc_data_even[0]; + u8 cc_even1 = itv->vbi.cc_data_even[1]; + + memcpy(itv->vbi.cc_data_odd, itv->vbi.cc_data_odd + 2, sizeof(itv->vbi.cc_data_odd) - 2); + memcpy(itv->vbi.cc_data_even, itv->vbi.cc_data_even + 2, sizeof(itv->vbi.cc_data_even) - 2); + itv->vbi.cc_pos -= 2; + if (itv->vbi.cc_pos && cc_odd0 == 0x80 && cc_odd1 == 0x80) + continue; + + /* Send to Saa7127 */ + ivtv_set_cc(itv, 3, cc_odd0, cc_odd1, cc_even0, cc_even1); + if (itv->vbi.cc_pos == 0) + set_bit(IVTV_F_I_UPDATE_CC, &itv->i_flags); + break; + } + } + + if (test_and_clear_bit(IVTV_F_I_UPDATE_VPS, &itv->i_flags)) { + /* Lock */ + ivtv_set_vps(itv, itv->vbi.vps_found, + itv->vbi.vps[0], itv->vbi.vps[1], + itv->vbi.vps[2], itv->vbi.vps[3], itv->vbi.vps[4]); + } +} diff --git a/drivers/media/video/ivtv/ivtv-vbi.h b/drivers/media/video/ivtv/ivtv-vbi.h new file mode 100644 index 00000000000..cdaea697b3e --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-vbi.h @@ -0,0 +1,26 @@ +/* + Vertical Blank Interval support functions + Copyright (C) 2004-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +ssize_t ivtv_write_vbi(struct ivtv *itv, const char __user *ubuf, size_t count); +void ivtv_process_vbi_data(struct ivtv *itv, struct ivtv_buffer *buf, + u64 pts_stamp, int streamtype); +int ivtv_used_line(struct ivtv *itv, int line, int field); +void ivtv_disable_vbi(struct ivtv *itv); +void ivtv_set_vbi(unsigned long arg); +void vbi_work_handler(struct ivtv *itv); diff --git a/drivers/media/video/ivtv/ivtv-version.h b/drivers/media/video/ivtv/ivtv-version.h new file mode 100644 index 00000000000..85530a3cd36 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-version.h @@ -0,0 +1,26 @@ +/* + ivtv driver version information + Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define IVTV_DRIVER_NAME "ivtv" +#define IVTV_DRIVER_VERSION_MAJOR 1 +#define IVTV_DRIVER_VERSION_MINOR 0 +#define IVTV_DRIVER_VERSION_PATCHLEVEL 0 + +#define IVTV_VERSION __stringify(IVTV_DRIVER_VERSION_MAJOR) "." __stringify(IVTV_DRIVER_VERSION_MINOR) "." __stringify(IVTV_DRIVER_VERSION_PATCHLEVEL) +#define IVTV_DRIVER_VERSION KERNEL_VERSION(IVTV_DRIVER_VERSION_MAJOR,IVTV_DRIVER_VERSION_MINOR,IVTV_DRIVER_VERSION_PATCHLEVEL) diff --git a/drivers/media/video/ivtv/ivtv-video.c b/drivers/media/video/ivtv/ivtv-video.c new file mode 100644 index 00000000000..5858b197d51 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-video.c @@ -0,0 +1,142 @@ +/* + saa7127 interface functions + Copyright (C) 2004-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-video.h" +#include "ivtv-i2c.h" +#include "ivtv-gpio.h" +#include "ivtv-cards.h" +#include <media/upd64031a.h> +#include <media/upd64083.h> + +void ivtv_set_vps(struct ivtv *itv, int enabled, u8 vps1, u8 vps2, u8 vps3, + u8 vps4, u8 vps5) +{ + struct v4l2_sliced_vbi_data data; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return; + data.id = V4L2_SLICED_VPS; + data.field = 0; + data.line = enabled ? 16 : 0; + data.data[4] = vps1; + data.data[10] = vps2; + data.data[11] = vps3; + data.data[12] = vps4; + data.data[13] = vps5; + ivtv_saa7127(itv, VIDIOC_INT_S_VBI_DATA, &data); +} + +void ivtv_set_cc(struct ivtv *itv, int mode, u8 cc1, u8 cc2, u8 cc3, u8 cc4) +{ + struct v4l2_sliced_vbi_data data; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return; + data.id = V4L2_SLICED_CAPTION_525; + data.field = 0; + data.line = (mode & 1) ? 21 : 0; + data.data[0] = cc1; + data.data[1] = cc2; + ivtv_saa7127(itv, VIDIOC_INT_S_VBI_DATA, &data); + data.field = 1; + data.line = (mode & 2) ? 21 : 0; + data.data[0] = cc3; + data.data[1] = cc4; + ivtv_saa7127(itv, VIDIOC_INT_S_VBI_DATA, &data); +} + +void ivtv_set_wss(struct ivtv *itv, int enabled, int mode) +{ + struct v4l2_sliced_vbi_data data; + + if (!(itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) + return; + /* When using a 50 Hz system, always turn on the + wide screen signal with 4x3 ratio as the default. + Turning this signal on and off can confuse certain + TVs. As far as I can tell there is no reason not to + transmit this signal. */ + if ((itv->std & V4L2_STD_625_50) && !enabled) { + enabled = 1; + mode = 0x08; /* 4x3 full format */ + } + data.id = V4L2_SLICED_WSS_625; + data.field = 0; + data.line = enabled ? 23 : 0; + data.data[0] = mode & 0xff; + data.data[1] = (mode >> 8) & 0xff; + ivtv_saa7127(itv, VIDIOC_INT_S_VBI_DATA, &data); +} + +void ivtv_video_set_io(struct ivtv *itv) +{ + struct v4l2_routing route; + int inp = itv->active_input; + u32 type; + + route.input = itv->card->video_inputs[inp].video_input; + route.output = 0; + itv->video_dec_func(itv, VIDIOC_INT_S_VIDEO_ROUTING, &route); + + type = itv->card->video_inputs[inp].video_type; + + if (type == IVTV_CARD_INPUT_VID_TUNER) { + route.input = 0; /* Tuner */ + } else if (type < IVTV_CARD_INPUT_COMPOSITE1) { + route.input = 2; /* S-Video */ + } else { + route.input = 1; /* Composite */ + } + + if (itv->card->hw_video & IVTV_HW_GPIO) + ivtv_gpio(itv, VIDIOC_INT_S_VIDEO_ROUTING, &route); + + if (itv->card->hw_video & IVTV_HW_UPD64031A) { + if (type == IVTV_CARD_INPUT_VID_TUNER || + type >= IVTV_CARD_INPUT_COMPOSITE1) { + /* Composite: GR on, connect to 3DYCS */ + route.input = UPD64031A_GR_ON | UPD64031A_3DYCS_COMPOSITE; + } else { + /* S-Video: GR bypassed, turn it off */ + route.input = UPD64031A_GR_OFF | UPD64031A_3DYCS_DISABLE; + } + route.input |= itv->card->gr_config; + + ivtv_upd64031a(itv, VIDIOC_INT_S_VIDEO_ROUTING, &route); + } + + if (itv->card->hw_video & IVTV_HW_UPD6408X) { + route.input = UPD64083_YCS_MODE; + if (type > IVTV_CARD_INPUT_VID_TUNER && + type < IVTV_CARD_INPUT_COMPOSITE1) { + /* S-Video uses YCNR mode and internal Y-ADC, the upd64031a + is not used. */ + route.input |= UPD64083_YCNR_MODE; + } + else if (itv->card->hw_video & IVTV_HW_UPD64031A) { + /* Use upd64031a output for tuner and composite(CX23416GYC only) inputs */ + if ((type == IVTV_CARD_INPUT_VID_TUNER)|| + (itv->card->type == IVTV_CARD_CX23416GYC)) { + route.input |= UPD64083_EXT_Y_ADC; + } + } + ivtv_upd64083(itv, VIDIOC_INT_S_VIDEO_ROUTING, &route); + } +} diff --git a/drivers/media/video/ivtv/ivtv-video.h b/drivers/media/video/ivtv/ivtv-video.h new file mode 100644 index 00000000000..c8ade5d3c41 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-video.h @@ -0,0 +1,24 @@ +/* + saa7127 interface functions + Copyright (C) 2004-2007 Hans Verkuil <hverkuil@xs4all.nl> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +void ivtv_set_wss(struct ivtv *itv, int enabled, int mode); +void ivtv_set_cc(struct ivtv *itv, int mode, u8 cc1, u8 cc2, u8 cc3, u8 cc4); +void ivtv_set_vps(struct ivtv *itv, int enabled, u8 vps1, u8 vps2, u8 vps3, + u8 vps4, u8 vps5); +void ivtv_video_set_io(struct ivtv *itv); diff --git a/drivers/media/video/ivtv/ivtv-yuv.c b/drivers/media/video/ivtv/ivtv-yuv.c new file mode 100644 index 00000000000..bcea09542e5 --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-yuv.c @@ -0,0 +1,1129 @@ +/* + yuv support + + Copyright (C) 2007 Ian Armstrong <ian@iarmst.demon.co.uk> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-yuv.h" + +static int ivtv_yuv_prep_user_dma(struct ivtv *itv, struct ivtv_user_dma *dma, + struct ivtv_dma_frame *args) +{ + struct ivtv_dma_page_info y_dma; + struct ivtv_dma_page_info uv_dma; + + int i; + int y_pages, uv_pages; + + unsigned long y_buffer_offset, uv_buffer_offset; + int y_decode_height, uv_decode_height, y_size; + int frame = atomic_read(&itv->yuv_info.next_fill_frame); + + y_buffer_offset = IVTV_DEC_MEM_START + yuv_offset[frame]; + uv_buffer_offset = y_buffer_offset + IVTV_YUV_BUFFER_UV_OFFSET; + + y_decode_height = uv_decode_height = args->src.height + args->src.top; + + if (y_decode_height < 512-16) + y_buffer_offset += 720 * 16; + + if (y_decode_height & 15) + y_decode_height = (y_decode_height + 16) & ~15; + + if (uv_decode_height & 31) + uv_decode_height = (uv_decode_height + 32) & ~31; + + y_size = 720 * y_decode_height; + + /* Still in USE */ + if (dma->SG_length || dma->page_count) { + IVTV_DEBUG_WARN("prep_user_dma: SG_length %d page_count %d still full?\n", + dma->SG_length, dma->page_count); + return -EBUSY; + } + + ivtv_udma_get_page_info (&y_dma, (unsigned long)args->y_source, 720 * y_decode_height); + ivtv_udma_get_page_info (&uv_dma, (unsigned long)args->uv_source, 360 * uv_decode_height); + + /* Get user pages for DMA Xfer */ + down_read(¤t->mm->mmap_sem); + y_pages = get_user_pages(current, current->mm, y_dma.uaddr, y_dma.page_count, 0, 1, &dma->map[0], NULL); + uv_pages = get_user_pages(current, current->mm, uv_dma.uaddr, uv_dma.page_count, 0, 1, &dma->map[y_pages], NULL); + up_read(¤t->mm->mmap_sem); + + dma->page_count = y_dma.page_count + uv_dma.page_count; + + if (y_pages + uv_pages != dma->page_count) { + IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n", + y_pages + uv_pages, dma->page_count); + + for (i = 0; i < dma->page_count; i++) { + put_page(dma->map[i]); + } + dma->page_count = 0; + return -EINVAL; + } + + /* Fill & map SG List */ + ivtv_udma_fill_sg_list (dma, &uv_dma, ivtv_udma_fill_sg_list (dma, &y_dma, 0)); + dma->SG_length = pci_map_sg(itv->dev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); + + /* Fill SG Array with new values */ + ivtv_udma_fill_sg_array (dma, y_buffer_offset, uv_buffer_offset, y_size); + + /* If we've offset the y plane, ensure top area is blanked */ + if (args->src.height + args->src.top < 512-16) { + if (itv->yuv_info.blanking_dmaptr) { + dma->SGarray[dma->SG_length].size = cpu_to_le32(720*16); + dma->SGarray[dma->SG_length].src = cpu_to_le32(itv->yuv_info.blanking_dmaptr); + dma->SGarray[dma->SG_length].dst = cpu_to_le32(IVTV_DEC_MEM_START + yuv_offset[frame]); + dma->SG_length++; + } + } + + /* Tag SG Array with Interrupt Bit */ + dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); + + ivtv_udma_sync_for_device(itv); + return 0; +} + +/* We rely on a table held in the firmware - Quick check. */ +int ivtv_yuv_filter_check(struct ivtv *itv) +{ + int i, offset_y, offset_uv; + + for (i=0, offset_y = 16, offset_uv = 4; i<16; i++, offset_y += 24, offset_uv += 12) { + if ((read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + offset_y) != i << 16) || + (read_dec(IVTV_YUV_VERTICAL_FILTER_OFFSET + offset_uv) != i << 16)) { + IVTV_WARN ("YUV filter table not found in firmware.\n"); + return -1; + } + } + return 0; +} + +static void ivtv_yuv_filter(struct ivtv *itv, int h_filter, int v_filter_1, int v_filter_2) +{ + int filter_index, filter_line; + + /* If any filter is -1, then don't update it */ + if (h_filter > -1) { + if (h_filter > 4) h_filter = 4; + filter_index = h_filter * 384; + filter_line = 0; + while (filter_line < 16) { + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02804); + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x0281c); + filter_index += 4; + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02808); + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02820); + filter_index += 4; + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x0280c); + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02824); + filter_index += 4; + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02810); + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02828); + filter_index += 4; + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x02814); + write_reg(read_dec(IVTV_YUV_HORIZONTAL_FILTER_OFFSET + filter_index), 0x0282c); + filter_index += 8; + write_reg(0, 0x02818); + write_reg(0, 0x02830); + filter_line ++; + } + IVTV_DEBUG_YUV("h_filter -> %d\n",h_filter); + } + + if (v_filter_1 > -1) { + if (v_filter_1 > 4) v_filter_1 = 4; + filter_index = v_filter_1 * 192; + filter_line = 0; + while (filter_line < 16) { + write_reg(read_dec(IVTV_YUV_VERTICAL_FILTER_OFFSET + filter_index), 0x02900); + filter_index += 4; + write_reg(read_dec(IVTV_YUV_VERTICAL_FILTER_OFFSET + filter_index), 0x02904); + filter_index += 8; + write_reg(0, 0x02908); + filter_line ++; + } + IVTV_DEBUG_YUV("v_filter_1 -> %d\n",v_filter_1); + } + + if (v_filter_2 > -1) { + if (v_filter_2 > 4) v_filter_2 = 4; + filter_index = v_filter_2 * 192; + filter_line = 0; + while (filter_line < 16) { + write_reg(read_dec(IVTV_YUV_VERTICAL_FILTER_OFFSET + filter_index), 0x0290c); + filter_index += 4; + write_reg(read_dec(IVTV_YUV_VERTICAL_FILTER_OFFSET + filter_index), 0x02910); + filter_index += 8; + write_reg(0, 0x02914); + filter_line ++; + } + IVTV_DEBUG_YUV("v_filter_2 -> %d\n",v_filter_2); + } +} + +static void ivtv_yuv_handle_horizontal(struct ivtv *itv, struct yuv_frame_info *window) +{ + u32 reg_2834, reg_2838, reg_283c; + u32 reg_2844, reg_2854, reg_285c; + u32 reg_2864, reg_2874, reg_2890; + u32 reg_2870, reg_2870_base, reg_2870_offset; + int x_cutoff; + int h_filter; + u32 master_width; + + IVTV_DEBUG_WARN( "Need to adjust to width %d src_w %d dst_w %d src_x %d dst_x %d\n", + window->tru_w, window->src_w, window->dst_w,window->src_x, window->dst_x); + + /* How wide is the src image */ + x_cutoff = window->src_w + window->src_x; + + /* Set the display width */ + reg_2834 = window->dst_w; + reg_2838 = reg_2834; + + /* Set the display position */ + reg_2890 = window->dst_x; + + /* Index into the image horizontally */ + reg_2870 = 0; + + /* 2870 is normally fudged to align video coords with osd coords. + If running full screen, it causes an unwanted left shift + Remove the fudge if we almost fill the screen. + Gradually adjust the offset to avoid the video 'snapping' + left/right if it gets dragged through this region. + Only do this if osd is full width. */ + if (window->vis_w == 720) { + if ((window->tru_x - window->pan_x > -1) && (window->tru_x - window->pan_x <= 40) && (window->dst_w >= 680)){ + reg_2870 = 10 - (window->tru_x - window->pan_x) / 4; + } + else if ((window->tru_x - window->pan_x < 0) && (window->tru_x - window->pan_x >= -20) && (window->dst_w >= 660)) { + reg_2870 = (10 + (window->tru_x - window->pan_x) / 2); + } + + if (window->dst_w >= window->src_w) + reg_2870 = reg_2870 << 16 | reg_2870; + else + reg_2870 = ((reg_2870 & ~1) << 15) | (reg_2870 & ~1); + } + + if (window->dst_w < window->src_w) + reg_2870 = 0x000d000e - reg_2870; + else + reg_2870 = 0x0012000e - reg_2870; + + /* We're also using 2870 to shift the image left (src_x & negative dst_x) */ + reg_2870_offset = (window->src_x*((window->dst_w << 21)/window->src_w))>>19; + + if (window->dst_w >= window->src_w) { + x_cutoff &= ~1; + master_width = (window->src_w * 0x00200000) / (window->dst_w); + if (master_width * window->dst_w != window->src_w * 0x00200000) master_width ++; + reg_2834 = (reg_2834 << 16) | x_cutoff; + reg_2838 = (reg_2838 << 16) | x_cutoff; + reg_283c = master_width >> 2; + reg_2844 = master_width >> 2; + reg_2854 = master_width; + reg_285c = master_width >> 1; + reg_2864 = master_width >> 1; + + /* We also need to factor in the scaling + (src_w - dst_w) / (src_w / 4) */ + if (window->dst_w > window->src_w) + reg_2870_base = ((window->dst_w - window->src_w)<<16) / (window->src_w <<14); + else + reg_2870_base = 0; + + reg_2870 += (((reg_2870_offset << 14) & 0xFFFF0000) | reg_2870_offset >> 2) + (reg_2870_base << 17 | reg_2870_base); + reg_2874 = 0; + } + else if (window->dst_w < window->src_w / 2) { + master_width = (window->src_w * 0x00080000) / window->dst_w; + if (master_width * window->dst_w != window->src_w * 0x00080000) master_width ++; + reg_2834 = (reg_2834 << 16) | x_cutoff; + reg_2838 = (reg_2838 << 16) | x_cutoff; + reg_283c = master_width >> 2; + reg_2844 = master_width >> 1; + reg_2854 = master_width; + reg_285c = master_width >> 1; + reg_2864 = master_width >> 1; + reg_2870 += (((reg_2870_offset << 15) & 0xFFFF0000) | reg_2870_offset); + reg_2870 += (5 - (((window->src_w + window->src_w / 2) - 1) / window->dst_w)) << 16; + reg_2874 = 0x00000012; + } + else { + master_width = (window->src_w * 0x00100000) / window->dst_w; + if (master_width * window->dst_w != window->src_w * 0x00100000) master_width ++; + reg_2834 = (reg_2834 << 16) | x_cutoff; + reg_2838 = (reg_2838 << 16) | x_cutoff; + reg_283c = master_width >> 2; + reg_2844 = master_width >> 1; + reg_2854 = master_width; + reg_285c = master_width >> 1; + reg_2864 = master_width >> 1; + reg_2870 += (((reg_2870_offset << 14) & 0xFFFF0000) | reg_2870_offset >> 1); + reg_2870 += (5 - (((window->src_w * 3) - 1) / window->dst_w)) << 16; + reg_2874 = 0x00000001; + } + + /* Select the horizontal filter */ + if (window->src_w == window->dst_w) { + /* An exact size match uses filter 0 */ + h_filter = 0; + } + else { + /* Figure out which filter to use */ + h_filter = ((window->src_w << 16) / window->dst_w) >> 15; + h_filter = (h_filter >> 1) + (h_filter & 1); + /* Only an exact size match can use filter 0 */ + if (h_filter == 0) h_filter = 1; + } + + write_reg(reg_2834, 0x02834); + write_reg(reg_2838, 0x02838); + IVTV_DEBUG_YUV("Update reg 0x2834 %08x->%08x 0x2838 %08x->%08x\n",itv->yuv_info.reg_2834, reg_2834, itv->yuv_info.reg_2838, reg_2838); + + write_reg(reg_283c, 0x0283c); + write_reg(reg_2844, 0x02844); + + IVTV_DEBUG_YUV("Update reg 0x283c %08x->%08x 0x2844 %08x->%08x\n",itv->yuv_info.reg_283c, reg_283c, itv->yuv_info.reg_2844, reg_2844); + + write_reg(0x00080514, 0x02840); + write_reg(0x00100514, 0x02848); + IVTV_DEBUG_YUV("Update reg 0x2840 %08x->%08x 0x2848 %08x->%08x\n",itv->yuv_info.reg_2840, 0x00080514, itv->yuv_info.reg_2848, 0x00100514); + + write_reg(reg_2854, 0x02854); + IVTV_DEBUG_YUV("Update reg 0x2854 %08x->%08x \n",itv->yuv_info.reg_2854, reg_2854); + + write_reg(reg_285c, 0x0285c); + write_reg(reg_2864, 0x02864); + IVTV_DEBUG_YUV("Update reg 0x285c %08x->%08x 0x2864 %08x->%08x\n",itv->yuv_info.reg_285c, reg_285c, itv->yuv_info.reg_2864, reg_2864); + + write_reg(reg_2874, 0x02874); + IVTV_DEBUG_YUV("Update reg 0x2874 %08x->%08x\n",itv->yuv_info.reg_2874, reg_2874); + + write_reg(reg_2870, 0x02870); + IVTV_DEBUG_YUV("Update reg 0x2870 %08x->%08x\n",itv->yuv_info.reg_2870, reg_2870); + + write_reg( reg_2890,0x02890); + IVTV_DEBUG_YUV("Update reg 0x2890 %08x->%08x\n",itv->yuv_info.reg_2890, reg_2890); + + /* Only update the filter if we really need to */ + if (h_filter != itv->yuv_info.h_filter) { + ivtv_yuv_filter (itv,h_filter,-1,-1); + itv->yuv_info.h_filter = h_filter; + } +} + +static void ivtv_yuv_handle_vertical(struct ivtv *itv, struct yuv_frame_info *window) +{ + u32 master_height; + u32 reg_2918, reg_291c, reg_2920, reg_2928; + u32 reg_2930, reg_2934, reg_293c; + u32 reg_2940, reg_2944, reg_294c; + u32 reg_2950, reg_2954, reg_2958, reg_295c; + u32 reg_2960, reg_2964, reg_2968, reg_296c; + u32 reg_289c; + u32 src_y_major_y, src_y_minor_y; + u32 src_y_major_uv, src_y_minor_uv; + u32 reg_2964_base, reg_2968_base; + int v_filter_1, v_filter_2; + + IVTV_DEBUG_WARN("Need to adjust to height %d src_h %d dst_h %d src_y %d dst_y %d\n", + window->tru_h, window->src_h, window->dst_h,window->src_y, window->dst_y); + + /* What scaling mode is being used... */ + if (window->interlaced_y) { + IVTV_DEBUG_YUV("Scaling mode Y: Interlaced\n"); + } + else { + IVTV_DEBUG_YUV("Scaling mode Y: Progressive\n"); + } + + if (window->interlaced_uv) { + IVTV_DEBUG_YUV("Scaling mode UV: Interlaced\n"); + } + else { + IVTV_DEBUG_YUV("Scaling mode UV: Progressive\n"); + } + + /* What is the source video being treated as... */ + if (itv->yuv_info.frame_interlaced) { + IVTV_DEBUG_WARN("Source video: Interlaced\n"); + } + else { + IVTV_DEBUG_WARN("Source video: Non-interlaced\n"); + } + + /* We offset into the image using two different index methods, so split + the y source coord into two parts. */ + if (window->src_y < 8) { + src_y_minor_uv = window->src_y; + src_y_major_uv = 0; + } + else { + src_y_minor_uv = 8; + src_y_major_uv = window->src_y - 8; + } + + src_y_minor_y = src_y_minor_uv; + src_y_major_y = src_y_major_uv; + + if (window->offset_y) src_y_minor_y += 16; + + if (window->interlaced_y) + reg_2918 = (window->dst_h << 16) | (window->src_h + src_y_minor_y); + else + reg_2918 = (window->dst_h << 16) | ((window->src_h + src_y_minor_y) << 1); + + if (window->interlaced_uv) + reg_291c = (window->dst_h << 16) | ((window->src_h + src_y_minor_uv) >> 1); + else + reg_291c = (window->dst_h << 16) | (window->src_h + src_y_minor_uv); + + reg_2964_base = (src_y_minor_y * ((window->dst_h << 16)/window->src_h)) >> 14; + reg_2968_base = (src_y_minor_uv * ((window->dst_h << 16)/window->src_h)) >> 14; + + if (window->dst_h / 2 >= window->src_h && !window->interlaced_y) { + master_height = (window->src_h * 0x00400000) / window->dst_h; + if ((window->src_h * 0x00400000) - (master_height * window->dst_h) >= window->dst_h / 2) master_height ++; + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 3; + reg_2930 = master_height; + reg_2940 = master_height >> 1; + reg_2964_base >>= 3; + reg_2968_base >>= 3; + reg_296c = 0x00000000; + } + else if (window->dst_h >= window->src_h) { + master_height = (window->src_h * 0x00400000) / window->dst_h; + master_height = (master_height >> 1) + (master_height & 1); + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 2; + reg_2930 = master_height; + reg_2940 = master_height >> 1; + reg_296c = 0x00000000; + if (window->interlaced_y) { + reg_2964_base >>= 3; + } + else { + reg_296c ++; + reg_2964_base >>= 2; + } + if (window->interlaced_uv) reg_2928 >>= 1; + reg_2968_base >>= 3; + } + else if (window->dst_h >= window->src_h / 2) { + master_height = (window->src_h * 0x00200000) / window->dst_h; + master_height = (master_height >> 1) + (master_height & 1); + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 2; + reg_2930 = master_height; + reg_2940 = master_height; + reg_296c = 0x00000101; + if (window->interlaced_y) { + reg_2964_base >>= 2; + } + else { + reg_296c ++; + reg_2964_base >>= 1; + } + if (window->interlaced_uv) reg_2928 >>= 1; + reg_2968_base >>= 2; + } + else { + master_height = (window->src_h * 0x00100000) / window->dst_h; + master_height = (master_height >> 1) + (master_height & 1); + reg_2920 = master_height >> 2; + reg_2928 = master_height >> 2; + reg_2930 = master_height; + reg_2940 = master_height; + reg_2964_base >>= 1; + reg_2968_base >>= 2; + reg_296c = 0x00000102; + } + + /* FIXME These registers change depending on scaled / unscaled output + We really need to work out what they should be */ + if (window->src_h == window->dst_h){ + reg_2934 = 0x00020000; + reg_293c = 0x00100000; + reg_2944 = 0x00040000; + reg_294c = 0x000b0000; + } + else { + reg_2934 = 0x00000FF0; + reg_293c = 0x00000FF0; + reg_2944 = 0x00000FF0; + reg_294c = 0x00000FF0; + } + + /* The first line to be displayed */ + reg_2950 = 0x00010000 + src_y_major_y; + if (window->interlaced_y) reg_2950 += 0x00010000; + reg_2954 = reg_2950 + 1; + + reg_2958 = 0x00010000 + (src_y_major_y >> 1); + if (window->interlaced_uv) reg_2958 += 0x00010000; + reg_295c = reg_2958 + 1; + + if (itv->yuv_info.decode_height == 480) + reg_289c = 0x011e0017; + else + reg_289c = 0x01500017; + + if (window->dst_y < 0) + reg_289c = (reg_289c - ((window->dst_y & ~1)<<15))-(window->dst_y >>1); + else + reg_289c = (reg_289c + ((window->dst_y & ~1)<<15))+(window->dst_y >>1); + + /* How much of the source to decode. + Take into account the source offset */ + reg_2960 = ((src_y_minor_y + window->src_h + src_y_major_y) - 1 ) | + ((((src_y_minor_uv + window->src_h + src_y_major_uv) - 1) & ~1) << 15); + + /* Calculate correct value for register 2964 */ + if (window->src_h == window->dst_h) + reg_2964 = 1; + else { + reg_2964 = 2 + ((window->dst_h << 1) / window->src_h); + reg_2964 = (reg_2964 >> 1) + (reg_2964 & 1); + } + reg_2968 = (reg_2964 << 16) + reg_2964 + (reg_2964 >> 1); + reg_2964 = (reg_2964 << 16) + reg_2964 + (reg_2964 * 46 / 94); + + /* Okay, we've wasted time working out the correct value, + but if we use it, it fouls the the window alignment. + Fudge it to what we want... */ + reg_2964 = 0x00010001 + ((reg_2964 & 0x0000FFFF) - (reg_2964 >> 16)); + reg_2968 = 0x00010001 + ((reg_2968 & 0x0000FFFF) - (reg_2968 >> 16)); + + /* Deviate further from what it should be. I find the flicker headache + inducing so try to reduce it slightly. Leave 2968 as-is otherwise + colours foul. */ + if ((reg_2964 != 0x00010001) && (window->dst_h / 2 <= window->src_h)) + reg_2964 = (reg_2964 & 0xFFFF0000) + ((reg_2964 & 0x0000FFFF)/2); + + if (!window->interlaced_y) reg_2964 -= 0x00010001; + if (!window->interlaced_uv) reg_2968 -= 0x00010001; + + reg_2964 += ((reg_2964_base << 16) | reg_2964_base); + reg_2968 += ((reg_2968_base << 16) | reg_2968_base); + + /* Select the vertical filter */ + if (window->src_h == window->dst_h) { + /* An exact size match uses filter 0/1 */ + v_filter_1 = 0; + v_filter_2 = 1; + } + else { + /* Figure out which filter to use */ + v_filter_1 = ((window->src_h << 16) / window->dst_h) >> 15; + v_filter_1 = (v_filter_1 >> 1) + (v_filter_1 & 1); + /* Only an exact size match can use filter 0 */ + if (v_filter_1 == 0) v_filter_1 = 1; + v_filter_2 = v_filter_1; + } + + write_reg(reg_2934, 0x02934); + write_reg(reg_293c, 0x0293c); + IVTV_DEBUG_YUV("Update reg 0x2934 %08x->%08x 0x293c %08x->%08x\n",itv->yuv_info.reg_2934, reg_2934, itv->yuv_info.reg_293c, reg_293c); + write_reg(reg_2944, 0x02944); + write_reg(reg_294c, 0x0294c); + IVTV_DEBUG_YUV("Update reg 0x2944 %08x->%08x 0x294c %08x->%08x\n",itv->yuv_info.reg_2944, reg_2944, itv->yuv_info.reg_294c, reg_294c); + + /* Ensure 2970 is 0 (does it ever change ?) */ +/* write_reg(0,0x02970); */ +/* IVTV_DEBUG_YUV("Update reg 0x2970 %08x->%08x\n",itv->yuv_info.reg_2970, 0); */ + + write_reg(reg_2930, 0x02938); + write_reg(reg_2930, 0x02930); + IVTV_DEBUG_YUV("Update reg 0x2930 %08x->%08x 0x2938 %08x->%08x\n",itv->yuv_info.reg_2930, reg_2930, itv->yuv_info.reg_2938, reg_2930); + + write_reg(reg_2928, 0x02928); + write_reg(reg_2928+0x514, 0x0292C); + IVTV_DEBUG_YUV("Update reg 0x2928 %08x->%08x 0x292c %08x->%08x\n",itv->yuv_info.reg_2928, reg_2928, itv->yuv_info.reg_292c, reg_2928+0x514); + + write_reg(reg_2920, 0x02920); + write_reg(reg_2920+0x514, 0x02924); + IVTV_DEBUG_YUV("Update reg 0x2920 %08x->%08x 0x2924 %08x->%08x\n",itv->yuv_info.reg_2920, reg_2920, itv->yuv_info.reg_2924, 0x514+reg_2920); + + write_reg (reg_2918,0x02918); + write_reg (reg_291c,0x0291C); + IVTV_DEBUG_YUV("Update reg 0x2918 %08x->%08x 0x291C %08x->%08x\n",itv->yuv_info.reg_2918,reg_2918,itv->yuv_info.reg_291c,reg_291c); + + write_reg(reg_296c, 0x0296c); + IVTV_DEBUG_YUV("Update reg 0x296c %08x->%08x\n",itv->yuv_info.reg_296c, reg_296c); + + write_reg(reg_2940, 0x02948); + write_reg(reg_2940, 0x02940); + IVTV_DEBUG_YUV("Update reg 0x2940 %08x->%08x 0x2948 %08x->%08x\n",itv->yuv_info.reg_2940, reg_2940, itv->yuv_info.reg_2948, reg_2940); + + write_reg(reg_2950, 0x02950); + write_reg(reg_2954, 0x02954); + IVTV_DEBUG_YUV("Update reg 0x2950 %08x->%08x 0x2954 %08x->%08x\n",itv->yuv_info.reg_2950, reg_2950, itv->yuv_info.reg_2954, reg_2954); + + write_reg(reg_2958, 0x02958); + write_reg(reg_295c, 0x0295C); + IVTV_DEBUG_YUV("Update reg 0x2958 %08x->%08x 0x295C %08x->%08x\n",itv->yuv_info.reg_2958, reg_2958, itv->yuv_info.reg_295c, reg_295c); + + write_reg(reg_2960, 0x02960); + IVTV_DEBUG_YUV("Update reg 0x2960 %08x->%08x \n",itv->yuv_info.reg_2960, reg_2960); + + write_reg(reg_2964, 0x02964); + write_reg(reg_2968, 0x02968); + IVTV_DEBUG_YUV("Update reg 0x2964 %08x->%08x 0x2968 %08x->%08x\n",itv->yuv_info.reg_2964, reg_2964, itv->yuv_info.reg_2968, reg_2968); + + write_reg( reg_289c,0x0289c); + IVTV_DEBUG_YUV("Update reg 0x289c %08x->%08x\n",itv->yuv_info.reg_289c, reg_289c); + + /* Only update filter 1 if we really need to */ + if (v_filter_1 != itv->yuv_info.v_filter_1) { + ivtv_yuv_filter (itv,-1,v_filter_1,-1); + itv->yuv_info.v_filter_1 = v_filter_1; + } + + /* Only update filter 2 if we really need to */ + if (v_filter_2 != itv->yuv_info.v_filter_2) { + ivtv_yuv_filter (itv,-1,-1,v_filter_2); + itv->yuv_info.v_filter_2 = v_filter_2; + } + + itv->yuv_info.frame_interlaced_last = itv->yuv_info.frame_interlaced; +} + +/* Modify the supplied coordinate information to fit the visible osd area */ +static u32 ivtv_yuv_window_setup (struct ivtv *itv, struct yuv_frame_info *window) +{ + int osd_crop, lace_threshold; + u32 osd_scale; + u32 yuv_update = 0; + + lace_threshold = itv->yuv_info.lace_threshold; + if (lace_threshold < 0) + lace_threshold = itv->yuv_info.decode_height - 1; + + /* Work out the lace settings */ + switch (itv->yuv_info.lace_mode) { + case IVTV_YUV_MODE_PROGRESSIVE: /* Progressive mode */ + itv->yuv_info.frame_interlaced = 0; + if (window->tru_h < 512 || (window->tru_h > 576 && window->tru_h < 1021)) + window->interlaced_y = 0; + else + window->interlaced_y = 1; + + if (window->tru_h < 1021 && (window->dst_h >= window->src_h /2)) + window->interlaced_uv = 0; + else + window->interlaced_uv = 1; + break; + + case IVTV_YUV_MODE_AUTO: + if (window->tru_h <= lace_threshold || window->tru_h > 576 || window->tru_w > 720){ + itv->yuv_info.frame_interlaced = 0; + if ((window->tru_h < 512) || + (window->tru_h > 576 && window->tru_h < 1021) || + (window->tru_w > 720 && window->tru_h < 1021)) + window->interlaced_y = 0; + else + window->interlaced_y = 1; + + if (window->tru_h < 1021 && (window->dst_h >= window->src_h /2)) + window->interlaced_uv = 0; + else + window->interlaced_uv = 1; + } + else { + itv->yuv_info.frame_interlaced = 1; + window->interlaced_y = 1; + window->interlaced_uv = 1; + } + break; + + case IVTV_YUV_MODE_INTERLACED: /* Interlace mode */ + default: + itv->yuv_info.frame_interlaced = 1; + window->interlaced_y = 1; + window->interlaced_uv = 1; + break; + } + + /* Sorry, but no negative coords for src */ + if (window->src_x < 0) window->src_x = 0; + if (window->src_y < 0) window->src_y = 0; + + /* Can only reduce width down to 1/4 original size */ + if ((osd_crop = window->src_w - ( 4 * window->dst_w )) > 0) { + window->src_x += osd_crop / 2; + window->src_w = (window->src_w - osd_crop) & ~3; + window->dst_w = window->src_w / 4; + window->dst_w += window->dst_w & 1; + } + + /* Can only reduce height down to 1/4 original size */ + if (window->src_h / window->dst_h >= 2) { + /* Overflow may be because we're running progressive, so force mode switch */ + window->interlaced_y = 1; + /* Make sure we're still within limits for interlace */ + if ((osd_crop = window->src_h - ( 4 * window->dst_h )) > 0) { + /* If we reach here we'll have to force the height. */ + window->src_y += osd_crop / 2; + window->src_h = (window->src_h - osd_crop) & ~3; + window->dst_h = window->src_h / 4; + window->dst_h += window->dst_h & 1; + } + } + + /* If there's nothing to safe to display, we may as well stop now */ + if ((int)window->dst_w <= 2 || (int)window->dst_h <= 2 || (int)window->src_w <= 2 || (int)window->src_h <= 2) { + return 0; + } + + /* Ensure video remains inside OSD area */ + osd_scale = (window->src_h << 16) / window->dst_h; + + if ((osd_crop = window->pan_y - window->dst_y) > 0) { + /* Falls off the upper edge - crop */ + window->src_y += (osd_scale * osd_crop) >> 16; + window->src_h -= (osd_scale * osd_crop) >> 16; + window->dst_h -= osd_crop; + window->dst_y = 0; + } + else { + window->dst_y -= window->pan_y; + } + + if ((osd_crop = window->dst_h + window->dst_y - window->vis_h) > 0) { + /* Falls off the lower edge - crop */ + window->dst_h -= osd_crop; + window->src_h -= (osd_scale * osd_crop) >> 16; + } + + osd_scale = (window->src_w << 16) / window->dst_w; + + if ((osd_crop = window->pan_x - window->dst_x) > 0) { + /* Fall off the left edge - crop */ + window->src_x += (osd_scale * osd_crop) >> 16; + window->src_w -= (osd_scale * osd_crop) >> 16; + window->dst_w -= osd_crop; + window->dst_x = 0; + } + else { + window->dst_x -= window->pan_x; + } + + if ((osd_crop = window->dst_w + window->dst_x - window->vis_w) > 0) { + /* Falls off the right edge - crop */ + window->dst_w -= osd_crop; + window->src_w -= (osd_scale * osd_crop) >> 16; + } + + /* The OSD can be moved. Track to it */ + window->dst_x += itv->yuv_info.osd_x_offset; + window->dst_y += itv->yuv_info.osd_y_offset; + + /* Width & height for both src & dst must be even. + Same for coordinates. */ + window->dst_w &= ~1; + window->dst_x &= ~1; + + window->src_w += window->src_x & 1; + window->src_x &= ~1; + + window->src_w &= ~1; + window->dst_w &= ~1; + + window->dst_h &= ~1; + window->dst_y &= ~1; + + window->src_h += window->src_y & 1; + window->src_y &= ~1; + + window->src_h &= ~1; + window->dst_h &= ~1; + + /* Due to rounding, we may have reduced the output size to <1/4 of the source + Check again, but this time just resize. Don't change source coordinates */ + if (window->dst_w < window->src_w / 4) { + window->src_w &= ~3; + window->dst_w = window->src_w / 4; + window->dst_w += window->dst_w & 1; + } + if (window->dst_h < window->src_h / 4) { + window->src_h &= ~3; + window->dst_h = window->src_h / 4; + window->dst_h += window->dst_h & 1; + } + + /* Check again. If there's nothing to safe to display, stop now */ + if ((int)window->dst_w <= 2 || (int)window->dst_h <= 2 || (int)window->src_w <= 2 || (int)window->src_h <= 2) { + return 0; + } + + /* Both x offset & width are linked, so they have to be done together */ + if ((itv->yuv_info.old_frame_info.dst_w != window->dst_w) || + (itv->yuv_info.old_frame_info.src_w != window->src_w) || + (itv->yuv_info.old_frame_info.dst_x != window->dst_x) || + (itv->yuv_info.old_frame_info.src_x != window->src_x) || + (itv->yuv_info.old_frame_info.pan_x != window->pan_x) || + (itv->yuv_info.old_frame_info.vis_w != window->vis_w)) { + yuv_update |= IVTV_YUV_UPDATE_HORIZONTAL; + } + + if ((itv->yuv_info.old_frame_info.src_h != window->src_h) || + (itv->yuv_info.old_frame_info.dst_h != window->dst_h) || + (itv->yuv_info.old_frame_info.dst_y != window->dst_y) || + (itv->yuv_info.old_frame_info.src_y != window->src_y) || + (itv->yuv_info.old_frame_info.pan_y != window->pan_y) || + (itv->yuv_info.old_frame_info.vis_h != window->vis_h) || + (itv->yuv_info.old_frame_info.interlaced_y != window->interlaced_y) || + (itv->yuv_info.old_frame_info.interlaced_uv != window->interlaced_uv)) { + yuv_update |= IVTV_YUV_UPDATE_VERTICAL; + } + + return yuv_update; +} + +/* Update the scaling register to the requested value */ +void ivtv_yuv_work_handler (struct ivtv *itv) +{ + struct yuv_frame_info window; + u32 yuv_update; + + int frame = itv->yuv_info.update_frame; + +/* IVTV_DEBUG_YUV("Update yuv registers for frame %d\n",frame); */ + memcpy(&window, &itv->yuv_info.new_frame_info[frame], sizeof (window)); + + /* Update the osd pan info */ + window.pan_x = itv->yuv_info.osd_x_pan; + window.pan_y = itv->yuv_info.osd_y_pan; + window.vis_w = itv->yuv_info.osd_vis_w; + window.vis_h = itv->yuv_info.osd_vis_h; + + /* Calculate the display window coordinates. Exit if nothing left */ + if (!(yuv_update = ivtv_yuv_window_setup (itv, &window))) + return; + + /* Update horizontal settings */ + if (yuv_update & IVTV_YUV_UPDATE_HORIZONTAL) + ivtv_yuv_handle_horizontal(itv, &window); + + if (yuv_update & IVTV_YUV_UPDATE_VERTICAL) + ivtv_yuv_handle_vertical(itv, &window); + + memcpy(&itv->yuv_info.old_frame_info, &window, sizeof (itv->yuv_info.old_frame_info)); +} + +static void ivtv_yuv_init (struct ivtv *itv) +{ + IVTV_DEBUG_YUV("ivtv_yuv_init\n"); + + /* Take a snapshot of the current register settings */ + itv->yuv_info.reg_2834 = read_reg(0x02834); + itv->yuv_info.reg_2838 = read_reg(0x02838); + itv->yuv_info.reg_283c = read_reg(0x0283c); + itv->yuv_info.reg_2840 = read_reg(0x02840); + itv->yuv_info.reg_2844 = read_reg(0x02844); + itv->yuv_info.reg_2848 = read_reg(0x02848); + itv->yuv_info.reg_2854 = read_reg(0x02854); + itv->yuv_info.reg_285c = read_reg(0x0285c); + itv->yuv_info.reg_2864 = read_reg(0x02864); + itv->yuv_info.reg_2870 = read_reg(0x02870); + itv->yuv_info.reg_2874 = read_reg(0x02874); + itv->yuv_info.reg_2898 = read_reg(0x02898); + itv->yuv_info.reg_2890 = read_reg(0x02890); + + itv->yuv_info.reg_289c = read_reg(0x0289c); + itv->yuv_info.reg_2918 = read_reg(0x02918); + itv->yuv_info.reg_291c = read_reg(0x0291c); + itv->yuv_info.reg_2920 = read_reg(0x02920); + itv->yuv_info.reg_2924 = read_reg(0x02924); + itv->yuv_info.reg_2928 = read_reg(0x02928); + itv->yuv_info.reg_292c = read_reg(0x0292c); + itv->yuv_info.reg_2930 = read_reg(0x02930); + itv->yuv_info.reg_2934 = read_reg(0x02934); + itv->yuv_info.reg_2938 = read_reg(0x02938); + itv->yuv_info.reg_293c = read_reg(0x0293c); + itv->yuv_info.reg_2940 = read_reg(0x02940); + itv->yuv_info.reg_2944 = read_reg(0x02944); + itv->yuv_info.reg_2948 = read_reg(0x02948); + itv->yuv_info.reg_294c = read_reg(0x0294c); + itv->yuv_info.reg_2950 = read_reg(0x02950); + itv->yuv_info.reg_2954 = read_reg(0x02954); + itv->yuv_info.reg_2958 = read_reg(0x02958); + itv->yuv_info.reg_295c = read_reg(0x0295c); + itv->yuv_info.reg_2960 = read_reg(0x02960); + itv->yuv_info.reg_2964 = read_reg(0x02964); + itv->yuv_info.reg_2968 = read_reg(0x02968); + itv->yuv_info.reg_296c = read_reg(0x0296c); + itv->yuv_info.reg_2970 = read_reg(0x02970); + + itv->yuv_info.v_filter_1 = -1; + itv->yuv_info.v_filter_2 = -1; + itv->yuv_info.h_filter = -1; + + /* Set some valid size info */ + itv->yuv_info.osd_x_offset = read_reg(0x02a04) & 0x00000FFF; + itv->yuv_info.osd_y_offset = (read_reg(0x02a04) >> 16) & 0x00000FFF; + + /* Bit 2 of reg 2878 indicates current decoder output format + 0 : NTSC 1 : PAL */ + if (read_reg(0x2878) & 4) + itv->yuv_info.decode_height = 576; + else + itv->yuv_info.decode_height = 480; + + /* If no visible size set, assume full size */ + if (!itv->yuv_info.osd_vis_w) itv->yuv_info.osd_vis_w = 720 - itv->yuv_info.osd_x_offset; + if (!itv->yuv_info.osd_vis_h) itv->yuv_info.osd_vis_h = itv->yuv_info.decode_height - itv->yuv_info.osd_y_offset; + + /* We need a buffer for blanking when Y plane is offset - non-fatal if we can't get one */ + itv->yuv_info.blanking_ptr = kzalloc(720*16,GFP_KERNEL); + if (itv->yuv_info.blanking_ptr) { + itv->yuv_info.blanking_dmaptr = pci_map_single(itv->dev, itv->yuv_info.blanking_ptr, 720*16, PCI_DMA_TODEVICE); + } + else { + itv->yuv_info.blanking_dmaptr = 0; + IVTV_DEBUG_WARN ("Failed to allocate yuv blanking buffer\n"); + } + + IVTV_DEBUG_WARN("Enable video output\n"); + write_reg_sync(0x00108080, 0x2898); + + /* Enable YUV decoder output */ + write_reg_sync(0x01, IVTV_REG_VDM); + + set_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags); + atomic_set(&itv->yuv_info.next_dma_frame,0); +} + +int ivtv_yuv_prep_frame(struct ivtv *itv, struct ivtv_dma_frame *args) +{ + DEFINE_WAIT(wait); + int rc = 0; + int got_sig = 0; + int frame, next_fill_frame, last_fill_frame; + + IVTV_DEBUG_INFO("yuv_prep_frame\n"); + + if (atomic_read(&itv->yuv_info.next_dma_frame) == -1) ivtv_yuv_init(itv); + + frame = atomic_read(&itv->yuv_info.next_fill_frame); + next_fill_frame = (frame + 1) & 0x3; + last_fill_frame = (atomic_read(&itv->yuv_info.next_dma_frame)+1) & 0x3; + + if (next_fill_frame != last_fill_frame && last_fill_frame != frame) { + /* Buffers are full - Overwrite the last frame */ + next_fill_frame = frame; + frame = (frame - 1) & 3; + } + + /* Take a snapshot of the yuv coordinate information */ + itv->yuv_info.new_frame_info[frame].src_x = args->src.left; + itv->yuv_info.new_frame_info[frame].src_y = args->src.top; + itv->yuv_info.new_frame_info[frame].src_w = args->src.width; + itv->yuv_info.new_frame_info[frame].src_h = args->src.height; + itv->yuv_info.new_frame_info[frame].dst_x = args->dst.left; + itv->yuv_info.new_frame_info[frame].dst_y = args->dst.top; + itv->yuv_info.new_frame_info[frame].dst_w = args->dst.width; + itv->yuv_info.new_frame_info[frame].dst_h = args->dst.height; + itv->yuv_info.new_frame_info[frame].tru_x = args->dst.left; + itv->yuv_info.new_frame_info[frame].tru_w = args->src_width; + itv->yuv_info.new_frame_info[frame].tru_h = args->src_height; + + /* Are we going to offset the Y plane */ + if (args->src.height + args->src.top < 512-16) + itv->yuv_info.new_frame_info[frame].offset_y = 1; + else + itv->yuv_info.new_frame_info[frame].offset_y = 0; + + /* Snapshot the osd pan info */ + itv->yuv_info.new_frame_info[frame].pan_x = itv->yuv_info.osd_x_pan; + itv->yuv_info.new_frame_info[frame].pan_y = itv->yuv_info.osd_y_pan; + itv->yuv_info.new_frame_info[frame].vis_w = itv->yuv_info.osd_vis_w; + itv->yuv_info.new_frame_info[frame].vis_h = itv->yuv_info.osd_vis_h; + + itv->yuv_info.new_frame_info[frame].update = 0; + itv->yuv_info.new_frame_info[frame].interlaced_y = 0; + itv->yuv_info.new_frame_info[frame].interlaced_uv = 0; + + if (memcmp (&itv->yuv_info.old_frame_info_args, &itv->yuv_info.new_frame_info[frame], + sizeof (itv->yuv_info.new_frame_info[frame]))) { + memcpy(&itv->yuv_info.old_frame_info_args, &itv->yuv_info.new_frame_info[frame], sizeof (itv->yuv_info.old_frame_info_args)); + itv->yuv_info.new_frame_info[frame].update = 1; +/* IVTV_DEBUG_YUV ("Requesting register update for frame %d\n",frame); */ + } + + /* DMA the frame */ + mutex_lock(&itv->udma.lock); + + if ((rc = ivtv_yuv_prep_user_dma(itv, &itv->udma, args)) != 0) { + mutex_unlock(&itv->udma.lock); + return rc; + } + + ivtv_udma_prepare(itv); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + /* if no UDMA is pending and no UDMA is in progress, then the DMA + is finished */ + while (itv->i_flags & (IVTV_F_I_UDMA_PENDING | IVTV_F_I_UDMA)) { + /* don't interrupt if the DMA is in progress but break off + a still pending DMA. */ + got_sig = signal_pending(current); + if (got_sig && test_and_clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + + /* Unmap Last DMA Xfer */ + ivtv_udma_unmap(itv); + + if (got_sig) { + IVTV_DEBUG_INFO("User stopped YUV UDMA\n"); + mutex_unlock(&itv->udma.lock); + return -EINTR; + } + + atomic_set(&itv->yuv_info.next_fill_frame, next_fill_frame); + + mutex_unlock(&itv->udma.lock); + return rc; +} + +void ivtv_yuv_close(struct ivtv *itv) +{ + int h_filter, v_filter_1, v_filter_2; + + IVTV_DEBUG_YUV("ivtv_yuv_close\n"); + ivtv_waitq(&itv->vsync_waitq); + + atomic_set(&itv->yuv_info.next_dma_frame, -1); + atomic_set(&itv->yuv_info.next_fill_frame, 0); + + /* Reset registers we have changed so mpeg playback works */ + + /* If we fully restore this register, the display may remain active. + Restore, but set one bit to blank the video. Firmware will always + clear this bit when needed, so not a problem. */ + write_reg(itv->yuv_info.reg_2898 | 0x01000000, 0x2898); + + write_reg(itv->yuv_info.reg_2834, 0x02834); + write_reg(itv->yuv_info.reg_2838, 0x02838); + write_reg(itv->yuv_info.reg_283c, 0x0283c); + write_reg(itv->yuv_info.reg_2840, 0x02840); + write_reg(itv->yuv_info.reg_2844, 0x02844); + write_reg(itv->yuv_info.reg_2848, 0x02848); + write_reg(itv->yuv_info.reg_2854, 0x02854); + write_reg(itv->yuv_info.reg_285c, 0x0285c); + write_reg(itv->yuv_info.reg_2864, 0x02864); + write_reg(itv->yuv_info.reg_2870, 0x02870); + write_reg(itv->yuv_info.reg_2874, 0x02874); + write_reg(itv->yuv_info.reg_2890, 0x02890); + write_reg(itv->yuv_info.reg_289c, 0x0289c); + + write_reg(itv->yuv_info.reg_2918, 0x02918); + write_reg(itv->yuv_info.reg_291c, 0x0291c); + write_reg(itv->yuv_info.reg_2920, 0x02920); + write_reg(itv->yuv_info.reg_2924, 0x02924); + write_reg(itv->yuv_info.reg_2928, 0x02928); + write_reg(itv->yuv_info.reg_292c, 0x0292c); + write_reg(itv->yuv_info.reg_2930, 0x02930); + write_reg(itv->yuv_info.reg_2934, 0x02934); + write_reg(itv->yuv_info.reg_2938, 0x02938); + write_reg(itv->yuv_info.reg_293c, 0x0293c); + write_reg(itv->yuv_info.reg_2940, 0x02940); + write_reg(itv->yuv_info.reg_2944, 0x02944); + write_reg(itv->yuv_info.reg_2948, 0x02948); + write_reg(itv->yuv_info.reg_294c, 0x0294c); + write_reg(itv->yuv_info.reg_2950, 0x02950); + write_reg(itv->yuv_info.reg_2954, 0x02954); + write_reg(itv->yuv_info.reg_2958, 0x02958); + write_reg(itv->yuv_info.reg_295c, 0x0295c); + write_reg(itv->yuv_info.reg_2960, 0x02960); + write_reg(itv->yuv_info.reg_2964, 0x02964); + write_reg(itv->yuv_info.reg_2968, 0x02968); + write_reg(itv->yuv_info.reg_296c, 0x0296c); + write_reg(itv->yuv_info.reg_2970, 0x02970); + + /* Prepare to restore filters */ + + /* First the horizontal filter */ + if ((itv->yuv_info.reg_2834 & 0x0000FFFF) == (itv->yuv_info.reg_2834 >> 16)) { + /* An exact size match uses filter 0 */ + h_filter = 0; + } + else { + /* Figure out which filter to use */ + h_filter = ((itv->yuv_info.reg_2834 << 16) / (itv->yuv_info.reg_2834 >> 16)) >> 15; + h_filter = (h_filter >> 1) + (h_filter & 1); + /* Only an exact size match can use filter 0. */ + if (h_filter < 1) h_filter = 1; + } + + /* Now the vertical filter */ + if ((itv->yuv_info.reg_2918 & 0x0000FFFF) == (itv->yuv_info.reg_2918 >> 16)) { + /* An exact size match uses filter 0/1 */ + v_filter_1 = 0; + v_filter_2 = 1; + } + else { + /* Figure out which filter to use */ + v_filter_1 = ((itv->yuv_info.reg_2918 << 16) / (itv->yuv_info.reg_2918 >> 16)) >> 15; + v_filter_1 = (v_filter_1 >> 1) + (v_filter_1 & 1); + /* Only an exact size match can use filter 0 */ + if (v_filter_1 == 0) v_filter_1 = 1; + v_filter_2 = v_filter_1; + } + + /* Now restore the filters */ + ivtv_yuv_filter (itv,h_filter,v_filter_1,v_filter_2); + + /* and clear a few registers */ + write_reg(0, 0x02814); + write_reg(0, 0x0282c); + write_reg(0, 0x02904); + write_reg(0, 0x02910); + + /* Release the blanking buffer */ + if (itv->yuv_info.blanking_ptr) { + kfree (itv->yuv_info.blanking_ptr); + itv->yuv_info.blanking_ptr = NULL; + pci_unmap_single(itv->dev, itv->yuv_info.blanking_dmaptr, 720*16, PCI_DMA_TODEVICE); + } + + /* Invalidate the old dimension information */ + itv->yuv_info.old_frame_info.src_w = 0; + itv->yuv_info.old_frame_info.src_h = 0; + itv->yuv_info.old_frame_info_args.src_w = 0; + itv->yuv_info.old_frame_info_args.src_h = 0; + + /* All done. */ + clear_bit(IVTV_F_I_DECODING_YUV, &itv->i_flags); +} + diff --git a/drivers/media/video/ivtv/ivtv-yuv.h b/drivers/media/video/ivtv/ivtv-yuv.h new file mode 100644 index 00000000000..88972d3f77c --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-yuv.h @@ -0,0 +1,24 @@ +/* + yuv support + + Copyright (C) 2007 Ian Armstrong <ian@iarmst.demon.co.uk> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +int ivtv_yuv_filter_check(struct ivtv *itv); +int ivtv_yuv_prep_frame(struct ivtv *itv, struct ivtv_dma_frame *args); +void ivtv_yuv_close(struct ivtv *itv); +void ivtv_yuv_work_handler (struct ivtv *itv); diff --git a/drivers/media/video/meye.c b/drivers/media/video/meye.c index 98681da5e3b..664aba8b4d8 100644 --- a/drivers/media/video/meye.c +++ b/drivers/media/video/meye.c @@ -925,13 +925,13 @@ static int meye_do_ioctl(struct inode *inode, struct file *file, if (p->palette != VIDEO_PALETTE_YUV422 && p->palette != VIDEO_PALETTE_YUYV) return -EINVAL; mutex_lock(&meye.lock); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, p->brightness >> 10); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAHUE, p->hue >> 10); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACOLOR, p->colour >> 10); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACONTRAST, p->contrast >> 10); meye.picture = *p; mutex_unlock(&meye.lock); @@ -1043,11 +1043,11 @@ static int meye_do_ioctl(struct inode *inode, struct file *file, meye.params.quality != jp->quality) mchip_hic_stop(); /* need restart */ meye.params = *jp; - sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERASHARPNESS, meye.params.sharpness); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAAGC, meye.params.agc); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAPICTURE, meye.params.picture); mutex_unlock(&meye.lock); break; @@ -1287,38 +1287,38 @@ static int meye_do_ioctl(struct inode *inode, struct file *file, mutex_lock(&meye.lock); switch (c->id) { case V4L2_CID_BRIGHTNESS: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERABRIGHTNESS, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, c->value); meye.picture.brightness = c->value << 10; break; case V4L2_CID_HUE: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERAHUE, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERAHUE, c->value); meye.picture.hue = c->value << 10; break; case V4L2_CID_CONTRAST: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERACONTRAST, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERACONTRAST, c->value); meye.picture.contrast = c->value << 10; break; case V4L2_CID_SATURATION: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERACOLOR, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERACOLOR, c->value); meye.picture.colour = c->value << 10; break; case V4L2_CID_AGC: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERAAGC, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERAAGC, c->value); meye.params.agc = c->value; break; case V4L2_CID_SHARPNESS: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERASHARPNESS, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERASHARPNESS, c->value); meye.params.sharpness = c->value; break; case V4L2_CID_PICTURE: - sonypi_camera_command( - SONYPI_COMMAND_SETCAMERAPICTURE, c->value); + sony_pic_camera_command( + SONY_PIC_COMMAND_SETCAMERAPICTURE, c->value); meye.params.picture = c->value; break; case V4L2_CID_JPEGQUAL: @@ -1848,7 +1848,7 @@ static int __devinit meye_probe(struct pci_dev *pcidev, memcpy(meye.video_dev, &meye_template, sizeof(meye_template)); meye.video_dev->dev = &meye.mchip_dev->dev; - if ((ret = sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 1))) { + if ((ret = sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 1))) { printk(KERN_ERR "meye: unable to power on the camera\n"); printk(KERN_ERR "meye: did you enable the camera in " "sonypi using the module options ?\n"); @@ -1928,13 +1928,13 @@ static int __devinit meye_probe(struct pci_dev *pcidev, meye.params.picture = 0; meye.params.framerate = 0; - sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, 32); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, 0); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, 48); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERABRIGHTNESS, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAHUE, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACOLOR, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERACONTRAST, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERASHARPNESS, 32); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAPICTURE, 0); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERAAGC, 48); printk(KERN_INFO "meye: Motion Eye Camera Driver v%s.\n", MEYE_DRIVER_VERSION); @@ -1953,7 +1953,7 @@ outremap: outregions: pci_disable_device(meye.mchip_dev); outenabledev: - sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 0); outsonypienable: kfifo_free(meye.doneq); outkfifoalloc2: @@ -1986,7 +1986,7 @@ static void __devexit meye_remove(struct pci_dev *pcidev) pci_disable_device(meye.mchip_dev); - sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); + sony_pic_camera_command(SONY_PIC_COMMAND_SETCAMERA, 0); kfifo_free(meye.doneq); kfifo_free(meye.grabq); diff --git a/drivers/media/video/meye.h b/drivers/media/video/meye.h index ea107cb5c84..323d0074120 100644 --- a/drivers/media/video/meye.h +++ b/drivers/media/video/meye.h @@ -255,7 +255,7 @@ /****************************************************************************/ /* Sony Programmable I/O Controller for accessing the camera commands */ -#include <linux/sonypi.h> +#include <linux/sony-laptop.h> /* private API definitions */ #include <linux/meye.h> diff --git a/drivers/media/video/msp3400-driver.c b/drivers/media/video/msp3400-driver.c index 2fb9fe6a1ae..3bb7d663486 100644 --- a/drivers/media/video/msp3400-driver.c +++ b/drivers/media/video/msp3400-driver.c @@ -773,6 +773,9 @@ static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg) break; } + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, state->ident, (state->rev1 << 16) | state->rev2); + default: /* unknown */ return -EINVAL; @@ -780,18 +783,16 @@ static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg) return 0; } -static int msp_suspend(struct device * dev, pm_message_t state) +static int msp_suspend(struct i2c_client *client, pm_message_t state) { - struct i2c_client *client = container_of(dev, struct i2c_client, dev); v4l_dbg(1, msp_debug, client, "suspend\n"); msp_reset(client); return 0; } -static int msp_resume(struct device * dev) +static int msp_resume(struct i2c_client *client) { - struct i2c_client *client = container_of(dev, struct i2c_client, dev); v4l_dbg(1, msp_debug, client, "resume\n"); msp_wake_thread(client); @@ -825,7 +826,7 @@ static int msp_attach(struct i2c_adapter *adapter, int address, int kind) if (msp_reset(client) == -1) { v4l_dbg(1, msp_debug, client, "msp3400 not found\n"); kfree(client); - return -1; + return 0; } state = kmalloc(sizeof(*state), GFP_KERNEL); @@ -859,7 +860,7 @@ static int msp_attach(struct i2c_adapter *adapter, int address, int kind) v4l_dbg(1, msp_debug, client, "not an msp3400 (cannot read chip version)\n"); kfree(state); kfree(client); - return -1; + return 0; } msp_set_audio(client); @@ -874,6 +875,8 @@ static int msp_attach(struct i2c_adapter *adapter, int address, int kind) snprintf(client->name, sizeof(client->name), "MSP%d4%02d%c-%c%d", msp_family, msp_product, msp_revision, msp_hard, msp_rom); + /* Rev B=2, C=3, D=4, G=7 */ + state->ident = msp_family * 10000 + 4000 + msp_product * 10 + msp_revision - '@'; /* Has NICAM support: all mspx41x and mspx45x products have NICAM */ state->has_nicam = msp_prod_hi == 1 || msp_prod_hi == 5; @@ -996,11 +999,11 @@ static struct i2c_driver i2c_driver = { .id = I2C_DRIVERID_MSP3400, .attach_adapter = msp_probe, .detach_client = msp_detach, + .suspend = msp_suspend, + .resume = msp_resume, .command = msp_command, .driver = { .name = "msp3400", - .suspend = msp_suspend, - .resume = msp_resume, }, }; diff --git a/drivers/media/video/msp3400-driver.h b/drivers/media/video/msp3400-driver.h index 7531efa1615..ab69a290e5d 100644 --- a/drivers/media/video/msp3400-driver.h +++ b/drivers/media/video/msp3400-driver.h @@ -50,6 +50,7 @@ extern int msp_stereo_thresh; struct msp_state { int rev1, rev2; + int ident; u8 has_nicam; u8 has_radio; u8 has_headphones; diff --git a/drivers/media/video/ov7670.c b/drivers/media/video/ov7670.c index 5ed0adc4ca2..03bc369a9e4 100644 --- a/drivers/media/video/ov7670.c +++ b/drivers/media/video/ov7670.c @@ -5,6 +5,8 @@ * by Jonathan Corbet with substantial inspiration from Mark * McClelland's ovcamchip code. * + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * * This file may be distributed under the terms of the GNU General * Public License, version 2. */ @@ -15,6 +17,7 @@ #include <linux/delay.h> #include <linux/videodev.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <linux/i2c.h> @@ -162,6 +165,10 @@ MODULE_LICENSE("GPL"); #define REG_GFIX 0x69 /* Fix gain control */ +#define REG_REG76 0x76 /* OV's name */ +#define R76_BLKPCOR 0x80 /* Black pixel correction enable */ +#define R76_WHTPCOR 0x40 /* White pixel correction enable */ + #define REG_RGB444 0x8c /* RGB 444 control */ #define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */ #define R444_RGBX 0x01 /* Empty nibble at end */ @@ -255,7 +262,7 @@ static struct regval_list ov7670_default_regs[] = { /* Almost all of these are magic "reserved" values. */ { REG_COM5, 0x61 }, { REG_COM6, 0x4b }, - { 0x16, 0x02 }, { REG_MVFP, 0x07|MVFP_MIRROR }, + { 0x16, 0x02 }, { REG_MVFP, 0x07 }, { 0x21, 0x02 }, { 0x22, 0x91 }, { 0x29, 0x07 }, { 0x33, 0x0b }, { 0x35, 0x0b }, { 0x37, 0x1d }, @@ -380,6 +387,13 @@ static struct regval_list ov7670_fmt_rgb444[] = { { 0xff, 0xff }, }; +static struct regval_list ov7670_fmt_raw[] = { + { REG_COM7, COM7_BAYER }, + { REG_COM13, 0x08 }, /* No gamma, magic rsvd bit */ + { REG_COM16, 0x3d }, /* Edge enhancement, denoise */ + { REG_REG76, 0xe1 }, /* Pix correction, magic rsvd */ + { 0xff, 0xff }, +}; @@ -483,32 +497,39 @@ static struct ov7670_format_struct { __u32 pixelformat; struct regval_list *regs; int cmatrix[CMATRIX_LEN]; + int bpp; /* Bytes per pixel */ } ov7670_formats[] = { { .desc = "YUYV 4:2:2", .pixelformat = V4L2_PIX_FMT_YUYV, .regs = ov7670_fmt_yuv422, .cmatrix = { 128, -128, 0, -34, -94, 128 }, + .bpp = 2, }, { .desc = "RGB 444", .pixelformat = V4L2_PIX_FMT_RGB444, .regs = ov7670_fmt_rgb444, .cmatrix = { 179, -179, 0, -61, -176, 228 }, + .bpp = 2, }, { .desc = "RGB 565", .pixelformat = V4L2_PIX_FMT_RGB565, .regs = ov7670_fmt_rgb565, .cmatrix = { 179, -179, 0, -61, -176, 228 }, + .bpp = 2, + }, + { + .desc = "Raw RGB Bayer", + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .regs = ov7670_fmt_raw, + .cmatrix = { 0, 0, 0, 0, 0, 0 }, + .bpp = 1 }, }; -#define N_OV7670_FMTS (sizeof(ov7670_formats)/sizeof(ov7670_formats[0])) +#define N_OV7670_FMTS ARRAY_SIZE(ov7670_formats) -/* - * All formats we support are 2 bytes/pixel. - */ -#define BYTES_PER_PIXEL 2 /* * Then there is the issue of window sizes. Try to capture the info here. @@ -685,7 +706,7 @@ static int ov7670_try_fmt(struct i2c_client *c, struct v4l2_format *fmt, */ pix->width = wsize->width; pix->height = wsize->height; - pix->bytesperline = pix->width*BYTES_PER_PIXEL; + pix->bytesperline = pix->width*ov7670_formats[index].bpp; pix->sizeimage = pix->height*pix->bytesperline; return 0; } @@ -1270,9 +1291,8 @@ static int ov7670_command(struct i2c_client *client, unsigned int cmd, void *arg) { switch (cmd) { - case VIDIOC_INT_G_CHIP_IDENT: - * (enum v4l2_chip_ident *) arg = V4L2_IDENT_OV7670; - return 0; + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_OV7670, 0); case VIDIOC_INT_RESET: ov7670_reset(client); diff --git a/drivers/media/video/planb.c b/drivers/media/video/planb.c index 86d2884e16c..1455a8f4e93 100644 --- a/drivers/media/video/planb.c +++ b/drivers/media/video/planb.c @@ -2160,7 +2160,7 @@ static int find_planb(void) if (!machine_is(powermac)) return 0; - planb_devices = find_devices("planb"); + planb_devices = of_find_node_by_name(NULL, "planb"); if (planb_devices == 0) { planb_num=0; printk(KERN_WARNING "PlanB: no device found!\n"); @@ -2175,12 +2175,14 @@ static int find_planb(void) if (planb_devices->n_addrs != 1) { printk (KERN_WARNING "PlanB: expecting 1 address for planb " "(got %d)", planb_devices->n_addrs); + of_node_put(planb_devices); return 0; } if (planb_devices->n_intrs == 0) { printk(KERN_WARNING "PlanB: no intrs for device %s\n", planb_devices->full_name); + of_node_put(planb_devices); return 0; } else { irq = planb_devices->intrs[0].line; @@ -2202,12 +2204,13 @@ static int find_planb(void) confreg = planb_devices->addrs[0].space & 0xff; old_base = planb_devices->addrs[0].address; new_base = 0xf1000000; + of_node_put(planb_devices); DEBUG("PlanB: Found on bus %d, dev %d, func %d, " "membase 0x%x (base reg. 0x%x)\n", bus, PCI_SLOT(dev_fn), PCI_FUNC(dev_fn), old_base, confreg); - pdev = pci_find_slot (bus, dev_fn); + pdev = pci_get_bus_and_slot(bus, dev_fn); if (!pdev) { printk(KERN_ERR "planb: cannot find slot\n"); goto err_out; @@ -2237,6 +2240,7 @@ static int find_planb(void) pb->planb_base = planb_regs; pb->planb_base_phys = (struct planb_registers *)new_base; pb->irq = irq; + pb->dev = pdev; return planb_num; @@ -2244,6 +2248,7 @@ err_out_disable: pci_disable_device(pdev); err_out: /* FIXME handle error */ /* comment moved from pci_find_slot, above */ + pci_dev_put(pdev); return 0; } @@ -2271,6 +2276,8 @@ static void release_planb(void) printk(KERN_INFO "PlanB: unregistering with v4l\n"); video_unregister_device(&pb->video_dev); + pci_dev_put(pb->dev); + /* note that iounmap() does nothing on the PPC right now */ iounmap ((void *)pb->planb_base); } diff --git a/drivers/media/video/planb.h b/drivers/media/video/planb.h index 92823211d0c..e21b5735c10 100644 --- a/drivers/media/video/planb.h +++ b/drivers/media/video/planb.h @@ -177,6 +177,7 @@ struct planb { struct mutex lock; unsigned int irq; /* interrupt number */ volatile unsigned int intr_mask; + struct pci_dev *dev; /* Our PCI device */ int overlay; /* overlay running? */ struct planb_window win; diff --git a/drivers/media/video/pvrusb2/pvrusb2-encoder.c b/drivers/media/video/pvrusb2/pvrusb2-encoder.c index 5786faf9b3b..5669c8ca9ca 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-encoder.c +++ b/drivers/media/video/pvrusb2/pvrusb2-encoder.c @@ -324,7 +324,7 @@ static int pvr2_encoder_vcmd(struct pvr2_hdw *hdw, int cmd, /* This implements some extra setup for the encoder that seems to be specific to the PVR USB2 hardware. */ -int pvr2_encoder_prep_config(struct pvr2_hdw *hdw) +static int pvr2_encoder_prep_config(struct pvr2_hdw *hdw) { int ret = 0; int encMisc3Arg = 0; diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h index 16bd7419960..ce66ab8ff2d 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h @@ -283,6 +283,8 @@ struct pvr2_hdw { int unit_number; /* ID for driver instance */ unsigned long serial_number; /* ID for hardware itself */ + char bus_info[32]; /* Bus location info */ + /* Minor numbers used by v4l logic (yes, this is a hack, as there should be no v4l junk here). Probably a better way to do this. */ int v4l_minor_number_video; diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/drivers/media/video/pvrusb2/pvrusb2-hdw.c index 9916cf32494..acf651e01f9 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.c @@ -1008,6 +1008,13 @@ unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw) return hdw->serial_number; } + +const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *hdw) +{ + return hdw->bus_info; +} + + unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *hdw) { return hdw->freqSelector ? hdw->freqValTelevision : hdw->freqValRadio; @@ -2105,6 +2112,11 @@ struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf, hdw->usb_intf = intf; hdw->usb_dev = interface_to_usbdev(intf); + scnprintf(hdw->bus_info,sizeof(hdw->bus_info), + "usb %s address %d", + hdw->usb_dev->dev.bus_id, + hdw->usb_dev->devnum); + ifnum = hdw->usb_intf->cur_altsetting->desc.bInterfaceNumber; usb_set_interface(hdw->usb_dev,ifnum,0); @@ -3275,7 +3287,9 @@ int pvr2_hdw_register_access(struct pvr2_hdw *hdw, mutex_lock(&hdw->i2c_list_lock); do { list_for_each(item,&hdw->i2c_clients) { cp = list_entry(item,struct pvr2_i2c_client,list); - if (!v4l2_chip_match_i2c_client(cp->client, req.match_type, req.match_chip)) { + if (!v4l2_chip_match_i2c_client( + cp->client, + req.match_type, req.match_chip)) { continue; } stat = pvr2_i2c_client_cmd( diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.h b/drivers/media/video/pvrusb2/pvrusb2-hdw.h index 0c9cca43ff8..4dba8d00632 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-hdw.h +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.h @@ -124,6 +124,9 @@ struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *); /* Retrieve serial number of device */ unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *); +/* Retrieve bus location info of device */ +const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *); + /* Called when hardware has been unplugged */ void pvr2_hdw_disconnect(struct pvr2_hdw *); diff --git a/drivers/media/video/pvrusb2/pvrusb2-sysfs.c b/drivers/media/video/pvrusb2/pvrusb2-sysfs.c index 91396fd573e..a741c556a39 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-sysfs.c +++ b/drivers/media/video/pvrusb2/pvrusb2-sysfs.c @@ -42,9 +42,11 @@ struct pvr2_sysfs { struct class_device_attribute attr_v4l_minor_number; struct class_device_attribute attr_v4l_radio_minor_number; struct class_device_attribute attr_unit_number; + struct class_device_attribute attr_bus_info; int v4l_minor_number_created_ok; int v4l_radio_minor_number_created_ok; int unit_number_created_ok; + int bus_info_created_ok; }; #ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC @@ -705,6 +707,10 @@ static void class_dev_destroy(struct pvr2_sysfs *sfp) pvr2_sysfs_tear_down_debugifc(sfp); #endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ pvr2_sysfs_tear_down_controls(sfp); + if (sfp->bus_info_created_ok) { + class_device_remove_file(sfp->class_dev, + &sfp->attr_bus_info); + } if (sfp->v4l_minor_number_created_ok) { class_device_remove_file(sfp->class_dev, &sfp->attr_v4l_minor_number); @@ -735,6 +741,16 @@ static ssize_t v4l_minor_number_show(struct class_device *class_dev,char *buf) } +static ssize_t bus_info_show(struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + return scnprintf(buf,PAGE_SIZE,"%s\n", + pvr2_hdw_get_bus_info(sfp->channel.hdw)); +} + + static ssize_t v4l_radio_minor_number_show(struct class_device *class_dev, char *buf) { @@ -836,6 +852,20 @@ static void class_dev_create(struct pvr2_sysfs *sfp, sfp->unit_number_created_ok = !0; } + sfp->attr_bus_info.attr.owner = THIS_MODULE; + sfp->attr_bus_info.attr.name = "bus_info_str"; + sfp->attr_bus_info.attr.mode = S_IRUGO; + sfp->attr_bus_info.show = bus_info_show; + sfp->attr_bus_info.store = NULL; + ret = class_device_create_file(sfp->class_dev, + &sfp->attr_bus_info); + if (ret < 0) { + printk(KERN_WARNING "%s: class_device_create_file error: %d\n", + __FUNCTION__, ret); + } else { + sfp->bus_info_created_ok = !0; + } + pvr2_sysfs_add_controls(sfp); #ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC pvr2_sysfs_add_debugifc(sfp); diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c index 25d3830b482..4563b3df8a0 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c +++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c @@ -203,6 +203,8 @@ static int pvr2_v4l2_do_ioctl(struct inode *inode, struct file *file, struct v4l2_capability *cap = arg; memcpy(cap, &pvr_capability, sizeof(struct v4l2_capability)); + strlcpy(cap->bus_info,pvr2_hdw_get_bus_info(hdw), + sizeof(cap->bus_info)); ret = 0; break; diff --git a/drivers/media/video/pwc/pwc-ctrl.c b/drivers/media/video/pwc/pwc-ctrl.c index 0bd115588f3..338ced7188f 100644 --- a/drivers/media/video/pwc/pwc-ctrl.c +++ b/drivers/media/video/pwc/pwc-ctrl.c @@ -140,6 +140,8 @@ static const char *size2name[PSZ_MAX] = An alternate value of 0 means this mode is not available at all. */ +#define PWC_FPS_MAX_NALA 8 + struct Nala_table_entry { char alternate; /* USB alternate setting */ int compressed; /* Compressed yes/no */ @@ -147,7 +149,9 @@ struct Nala_table_entry { unsigned char mode[3]; /* precomputed mode table */ }; -static struct Nala_table_entry Nala_table[PSZ_MAX][8] = +static unsigned int Nala_fps_vector[PWC_FPS_MAX_NALA] = { 4, 5, 7, 10, 12, 15, 20, 24 }; + +static struct Nala_table_entry Nala_table[PSZ_MAX][PWC_FPS_MAX_NALA] = { #include "pwc-nala.h" }; @@ -423,6 +427,59 @@ int pwc_set_video_mode(struct pwc_device *pdev, int width, int height, int frame return 0; } +static unsigned int pwc_get_fps_Nala(struct pwc_device *pdev, unsigned int index, unsigned int size) +{ + unsigned int i; + + for (i = 0; i < PWC_FPS_MAX_NALA; i++) { + if (Nala_table[size][i].alternate) { + if (index--==0) return Nala_fps_vector[i]; + } + } + return 0; +} + +static unsigned int pwc_get_fps_Kiara(struct pwc_device *pdev, unsigned int index, unsigned int size) +{ + unsigned int i; + + for (i = 0; i < PWC_FPS_MAX_KIARA; i++) { + if (Kiara_table[size][i][3].alternate) { + if (index--==0) return Kiara_fps_vector[i]; + } + } + return 0; +} + +static unsigned int pwc_get_fps_Timon(struct pwc_device *pdev, unsigned int index, unsigned int size) +{ + unsigned int i; + + for (i=0; i < PWC_FPS_MAX_TIMON; i++) { + if (Timon_table[size][i][3].alternate) { + if (index--==0) return Timon_fps_vector[i]; + } + } + return 0; +} + +unsigned int pwc_get_fps(struct pwc_device *pdev, unsigned int index, unsigned int size) +{ + unsigned int ret; + + if (DEVICE_USE_CODEC1(pdev->type)) { + ret = pwc_get_fps_Nala(pdev, index, size); + + } else if (DEVICE_USE_CODEC3(pdev->type)) { + ret = pwc_get_fps_Kiara(pdev, index, size); + + } else { + ret = pwc_get_fps_Timon(pdev, index, size); + } + + return ret; +} + #define BLACK_Y 0 #define BLACK_U 128 #define BLACK_V 128 @@ -1343,7 +1400,7 @@ int pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg) ret = pwc_read_red_gain(pdev, &ARGR(wb).read_red); if (ret < 0) break; - ret =pwc_read_blue_gain(pdev, &ARGR(wb).read_blue); + ret = pwc_read_blue_gain(pdev, &ARGR(wb).read_blue); if (ret < 0) break; } diff --git a/drivers/media/video/pwc/pwc-if.c b/drivers/media/video/pwc/pwc-if.c index 27ed76986ca..085332a503d 100644 --- a/drivers/media/video/pwc/pwc-if.c +++ b/drivers/media/video/pwc/pwc-if.c @@ -95,8 +95,8 @@ static const struct usb_device_id pwc_device_table [] = { { USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom (old model) */ { USB_DEVICE(0x046D, 0x08B4) }, /* Logitech QuickCam Zoom (new model) */ { USB_DEVICE(0x046D, 0x08B5) }, /* Logitech QuickCam Orbit/Sphere */ - { USB_DEVICE(0x046D, 0x08B6) }, /* Logitech (reserved) */ - { USB_DEVICE(0x046D, 0x08B7) }, /* Logitech (reserved) */ + { USB_DEVICE(0x046D, 0x08B6) }, /* Cisco VT Camera */ + { USB_DEVICE(0x046D, 0x08B7) }, /* Logitech ViewPort AV 100 */ { USB_DEVICE(0x046D, 0x08B8) }, /* Logitech (reserved) */ { USB_DEVICE(0x055D, 0x9000) }, /* Samsung MPC-C10 */ { USB_DEVICE(0x055D, 0x9001) }, /* Samsung MPC-C30 */ @@ -1493,7 +1493,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id case 0x0329: PWC_INFO("Philips SPC 900NC USB webcam detected.\n"); name = "Philips SPC 900NC webcam"; - type_id = 720; + type_id = 740; break; default: return -ENODEV; @@ -1547,8 +1547,16 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id features |= FEATURE_MOTOR_PANTILT; break; case 0x08b6: + PWC_INFO("Logitech/Cisco VT Camera webcam detected.\n"); + name = "Cisco VT Camera"; + type_id = 740; /* CCD sensor */ + break; case 0x08b7: - case 0x08b8: + PWC_INFO("Logitech ViewPort AV 100 webcam detected.\n"); + name = "Logitech ViewPort AV 100"; + type_id = 740; /* CCD sensor */ + break; + case 0x08b8: /* Where this released? */ PWC_INFO("Logitech QuickCam detected (reserved ID).\n"); name = "Logitech QuickCam (res.)"; type_id = 730; /* Assuming CMOS */ diff --git a/drivers/media/video/pwc/pwc-ioctl.h b/drivers/media/video/pwc/pwc-ioctl.h index 784bc72521f..cec66029976 100644 --- a/drivers/media/video/pwc/pwc-ioctl.h +++ b/drivers/media/video/pwc/pwc-ioctl.h @@ -2,7 +2,7 @@ #define PWC_IOCTL_H /* (C) 2001-2004 Nemosoft Unv. - (C) 2004 Luc Saillard (luc@saillard.org) + (C) 2004-2006 Luc Saillard (luc@saillard.org) NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx driver and thus may have bugs that are not present in the original version. @@ -25,7 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* This is pwc-ioctl.h belonging to PWC 8.12.1 +/* This is pwc-ioctl.h belonging to PWC 10.0.10 It contains structures and defines to communicate from user space directly to the driver. */ @@ -51,6 +51,9 @@ ... the function */ +#include <linux/types.h> +#include <linux/version.h> + /* Enumeration of image sizes */ #define PSZ_SQCIF 0x00 @@ -65,6 +68,8 @@ /* The frame rate is encoded in the video_window.flags parameter using the upper 16 bits, since some flags are defined nowadays. The following defines provide a mask and shift to filter out this value. + This value can also be passing using the private flag when using v4l2 and + VIDIOC_S_FMT ioctl. In 'Snapshot' mode the camera freezes its automatic exposure and colour balance controls. @@ -73,6 +78,8 @@ #define PWC_FPS_MASK 0x00FF0000 #define PWC_FPS_FRMASK 0x003F0000 #define PWC_FPS_SNAPSHOT 0x00400000 +#define PWC_QLT_MASK 0x03000000 +#define PWC_QLT_SHIFT 24 /* structure for transferring x & y coordinates */ @@ -289,4 +296,29 @@ struct pwc_table_init_buffer { }; #define VIDIOCPWCGVIDTABLE _IOR('v', 216, struct pwc_table_init_buffer) +/* + * This is private command used when communicating with v4l2. + * In the future all private ioctl will be remove/replace to + * use interface offer by v4l2. + */ + +#define V4L2_CID_PRIVATE_SAVE_USER (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_RESTORE_USER (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_RESTORE_FACTORY (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_COLOUR_MODE (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_PRIVATE_AUTOCONTOUR (V4L2_CID_PRIVATE_BASE + 4) +#define V4L2_CID_PRIVATE_CONTOUR (V4L2_CID_PRIVATE_BASE + 5) +#define V4L2_CID_PRIVATE_BACKLIGHT (V4L2_CID_PRIVATE_BASE + 6) +#define V4L2_CID_PRIVATE_FLICKERLESS (V4L2_CID_PRIVATE_BASE + 7) +#define V4L2_CID_PRIVATE_NOISE_REDUCTION (V4L2_CID_PRIVATE_BASE + 8) + +struct pwc_raw_frame { + __le16 type; /* type of the webcam */ + __le16 vbandlength; /* Size of 4lines compressed (used by the decompressor) */ + __u8 cmd[4]; /* the four byte of the command (in case of nala, + only the first 3 bytes is filled) */ + __u8 rawframe[0]; /* frame_size = H/4*vbandlength */ +} __attribute__ ((packed)); + + #endif diff --git a/drivers/media/video/pwc/pwc-kiara.c b/drivers/media/video/pwc/pwc-kiara.c index fec39cc5a9f..f4ae83c0cf2 100644 --- a/drivers/media/video/pwc/pwc-kiara.c +++ b/drivers/media/video/pwc/pwc-kiara.c @@ -42,6 +42,8 @@ #include "pwc-kiara.h" #include "pwc-uncompress.h" +const unsigned int Kiara_fps_vector[PWC_FPS_MAX_KIARA] = { 5, 10, 15, 20, 25, 30 }; + const struct Kiara_table_entry Kiara_table[PSZ_MAX][6][4] = { /* SQCIF */ diff --git a/drivers/media/video/pwc/pwc-kiara.h b/drivers/media/video/pwc/pwc-kiara.h index 0bdb22547d8..047dad8c15f 100644 --- a/drivers/media/video/pwc/pwc-kiara.h +++ b/drivers/media/video/pwc/pwc-kiara.h @@ -29,6 +29,8 @@ #include <media/pwc-ioctl.h> +#define PWC_FPS_MAX_KIARA 6 + struct Kiara_table_entry { char alternate; /* USB alternate interface */ @@ -37,8 +39,9 @@ struct Kiara_table_entry unsigned char mode[12]; /* precomputed mode settings for cam */ }; -extern const struct Kiara_table_entry Kiara_table[PSZ_MAX][6][4]; +extern const struct Kiara_table_entry Kiara_table[PSZ_MAX][PWC_FPS_MAX_KIARA][4]; extern const unsigned int KiaraRomTable[8][2][16][8]; +extern const unsigned int Kiara_fps_vector[PWC_FPS_MAX_KIARA]; #endif diff --git a/drivers/media/video/pwc/pwc-timon.c b/drivers/media/video/pwc/pwc-timon.c index be65bdcd195..c56c174b161 100644 --- a/drivers/media/video/pwc/pwc-timon.c +++ b/drivers/media/video/pwc/pwc-timon.c @@ -40,7 +40,9 @@ #include "pwc-timon.h" -const struct Timon_table_entry Timon_table[PSZ_MAX][6][4] = +const unsigned int Timon_fps_vector[PWC_FPS_MAX_TIMON] = { 5, 10, 15, 20, 25, 30 }; + +const struct Timon_table_entry Timon_table[PSZ_MAX][PWC_FPS_MAX_TIMON][4] = { /* SQCIF */ { diff --git a/drivers/media/video/pwc/pwc-timon.h b/drivers/media/video/pwc/pwc-timon.h index eef9e2cd432..a6e22224c95 100644 --- a/drivers/media/video/pwc/pwc-timon.h +++ b/drivers/media/video/pwc/pwc-timon.h @@ -44,6 +44,8 @@ #include <media/pwc-ioctl.h> +#define PWC_FPS_MAX_TIMON 6 + struct Timon_table_entry { char alternate; /* USB alternate interface */ @@ -52,9 +54,9 @@ struct Timon_table_entry unsigned char mode[13]; /* precomputed mode settings for cam */ }; -extern const struct Timon_table_entry Timon_table[PSZ_MAX][6][4]; +extern const struct Timon_table_entry Timon_table[PSZ_MAX][PWC_FPS_MAX_TIMON][4]; extern const unsigned int TimonRomTable [16][2][16][8]; - +extern const unsigned int Timon_fps_vector[PWC_FPS_MAX_TIMON]; #endif diff --git a/drivers/media/video/pwc/pwc-v4l.c b/drivers/media/video/pwc/pwc-v4l.c index d5e6bc85064..32fbe1ae625 100644 --- a/drivers/media/video/pwc/pwc-v4l.c +++ b/drivers/media/video/pwc/pwc-v4l.c @@ -1168,7 +1168,7 @@ int pwc_video_do_ioctl(struct inode *inode, struct file *file, buf->sequence = 0; buf->memory = V4L2_MEMORY_MMAP; buf->m.offset = pdev->fill_image * pdev->len_per_image; - buf->length = buf->bytesused; + buf->length = pdev->len_per_image; pwc_next_image(pdev); PWC_DEBUG_IOCTL("VIDIOC_DQBUF: buf->index=%d\n",buf->index); @@ -1193,6 +1193,64 @@ int pwc_video_do_ioctl(struct inode *inode, struct file *file, return 0; } + case VIDIOC_ENUM_FRAMESIZES: + { + struct v4l2_frmsizeenum *fsize = arg; + unsigned int i = 0, index = fsize->index; + + if (fsize->pixel_format == V4L2_PIX_FMT_YUV420) { + for (i = 0; i < PSZ_MAX; i++) { + if (pdev->image_mask & (1UL << i)) { + if (!index--) { + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = pwc_image_sizes[i].x; + fsize->discrete.height = pwc_image_sizes[i].y; + return 0; + } + } + } + } else if (fsize->index == 0 && + ((fsize->pixel_format == V4L2_PIX_FMT_PWC1 && DEVICE_USE_CODEC1(pdev->type)) || + (fsize->pixel_format == V4L2_PIX_FMT_PWC2 && DEVICE_USE_CODEC23(pdev->type)))) { + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = pdev->abs_max.x; + fsize->discrete.height = pdev->abs_max.y; + return 0; + } + return -EINVAL; + } + + case VIDIOC_ENUM_FRAMEINTERVALS: + { + struct v4l2_frmivalenum *fival = arg; + int size = -1; + unsigned int i; + + for (i = 0; i < PSZ_MAX; i++) { + if (pwc_image_sizes[i].x == fival->width && + pwc_image_sizes[i].y == fival->height) { + size = i; + break; + } + } + + /* TODO: Support raw format */ + if (size < 0 || fival->pixel_format != V4L2_PIX_FMT_YUV420) { + return -EINVAL; + } + + i = pwc_get_fps(pdev, fival->index, size); + if (!i) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = i; + + return 0; + } + default: return pwc_ioctl(pdev, cmd, arg); } /* ..switch */ diff --git a/drivers/media/video/pwc/pwc.h b/drivers/media/video/pwc/pwc.h index e778a2b8c28..acbb9312960 100644 --- a/drivers/media/video/pwc/pwc.h +++ b/drivers/media/video/pwc/pwc.h @@ -44,7 +44,7 @@ #define PWC_MINOR 0 #define PWC_EXTRAMINOR 12 #define PWC_VERSION_CODE KERNEL_VERSION(PWC_MAJOR,PWC_MINOR,PWC_EXTRAMINOR) -#define PWC_VERSION "10.0.12" +#define PWC_VERSION "10.0.13" #define PWC_NAME "pwc" #define PFX PWC_NAME ": " @@ -85,7 +85,7 @@ #define PWC_INFO(fmt, args...) printk(KERN_INFO PFX fmt, ##args) #define PWC_TRACE(fmt, args...) PWC_DEBUG(TRACE, fmt, ##args) -#else /* if ! CONFIG_PWC_DEBUG */ +#else /* if ! CONFIG_USB_PWC_DEBUG */ #define PWC_ERROR(fmt, args...) printk(KERN_ERR PFX fmt, ##args) #define PWC_WARNING(fmt, args...) printk(KERN_WARNING PFX fmt, ##args) @@ -287,6 +287,7 @@ void pwc_construct(struct pwc_device *pdev); /** Functions in pwc-ctrl.c */ /* Request a certain video mode. Returns < 0 if not possible */ extern int pwc_set_video_mode(struct pwc_device *pdev, int width, int height, int frames, int compression, int snapshot); +extern unsigned int pwc_get_fps(struct pwc_device *pdev, unsigned int index, unsigned int size); /* Calculate the number of bytes per image (not frame) */ extern int pwc_mpt_reset(struct pwc_device *pdev, int flags); extern int pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt); diff --git a/drivers/media/video/saa7115.c b/drivers/media/video/saa7115.c index 7735b675892..2d18f006982 100644 --- a/drivers/media/video/saa7115.c +++ b/drivers/media/video/saa7115.c @@ -45,6 +45,7 @@ #include <linux/i2c.h> #include <linux/videodev2.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <media/saa7115.h> #include <asm/div64.h> @@ -80,7 +81,7 @@ struct saa711x_state { int sat; int width; int height; - enum v4l2_chip_ident ident; + u32 ident; u32 audclk_freq; u32 crystal_freq; u8 ucgc; @@ -961,7 +962,7 @@ static void saa711x_set_v4lstd(struct i2c_client *client, v4l2_std_id std) reg |= 0x10; } else if (std == V4L2_STD_NTSC_M_JP) { reg |= 0x40; - } else if (std == V4L2_STD_SECAM) { + } else if (std & V4L2_STD_SECAM) { reg |= 0x50; } saa711x_write(client, R_0E_CHROMA_CNTL_1, reg); @@ -1232,7 +1233,6 @@ static void saa711x_decode_vbi_line(struct i2c_client *client, static int saa711x_command(struct i2c_client *client, unsigned int cmd, void *arg) { struct saa711x_state *state = i2c_get_clientdata(client); - int *iarg = arg; /* ioctls to allow direct access to the saa7115 registers for testing */ switch (cmd) { @@ -1437,9 +1437,8 @@ static int saa711x_command(struct i2c_client *client, unsigned int cmd, void *ar } #endif - case VIDIOC_INT_G_CHIP_IDENT: - *iarg = state->ident; - break; + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, state->ident, 0); default: return -EINVAL; @@ -1487,6 +1486,7 @@ static int saa711x_attach(struct i2c_adapter *adapter, int address, int kind) if (memcmp(name, "1f711", 5)) { v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n", address << 1, name); + kfree(client); return 0; } diff --git a/drivers/media/video/saa7127.c b/drivers/media/video/saa7127.c index 654863db159..9f986930490 100644 --- a/drivers/media/video/saa7127.c +++ b/drivers/media/video/saa7127.c @@ -54,6 +54,7 @@ #include <linux/i2c.h> #include <linux/videodev2.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <media/saa7127.h> static int debug = 0; @@ -234,7 +235,7 @@ static struct i2c_reg_value saa7127_init_config_50hz[] = { struct saa7127_state { v4l2_std_id std; - enum v4l2_chip_ident ident; + u32 ident; enum saa7127_input_type input_type; enum saa7127_output_type output_type; int video_enable; @@ -550,12 +551,12 @@ static int saa7127_command(struct i2c_client *client, struct v4l2_routing *route = arg; switch (cmd) { - case VIDIOC_S_STD: + case VIDIOC_INT_S_STD_OUTPUT: if (state->std == *(v4l2_std_id *)arg) break; return saa7127_set_std(client, *(v4l2_std_id *)arg); - case VIDIOC_G_STD: + case VIDIOC_INT_G_STD_OUTPUT: *(v4l2_std_id *)arg = state->std; break; @@ -650,9 +651,8 @@ static int saa7127_command(struct i2c_client *client, break; } - case VIDIOC_INT_G_CHIP_IDENT: - *(enum v4l2_chip_ident *)arg = state->ident; - break; + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, state->ident, 0); default: return -EINVAL; diff --git a/drivers/media/video/saa7134/Kconfig b/drivers/media/video/saa7134/Kconfig index 59da79ce2ef..309dca368f4 100644 --- a/drivers/media/video/saa7134/Kconfig +++ b/drivers/media/video/saa7134/Kconfig @@ -46,6 +46,7 @@ config VIDEO_SAA7134_DVB select DVB_NXT200X if !DVB_FE_CUSTOMISE select DVB_TDA10086 if !DVB_FE_CUSTOMISE select DVB_TDA826X if !DVB_FE_CUSTOMISE + select DVB_TDA827X if !DVB_FE_CUSTOMISE select DVB_ISL6421 if !DVB_FE_CUSTOMISE ---help--- This adds support for DVB cards based on the diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/video/saa7134/saa7134-cards.c index 89f32107f46..4ea479baee7 100644 --- a/drivers/media/video/saa7134/saa7134-cards.c +++ b/drivers/media/video/saa7134/saa7134-cards.c @@ -1543,12 +1543,12 @@ struct saa7134_board saa7134_boards[] = { },{ .name = name_comp1, .vmux = 0, - .amux = LINE2, + .amux = LINE1, .gpio = 0x02, },{ .name = name_svideo, .vmux = 6, - .amux = LINE2, + .amux = LINE1, .gpio = 0x02, }}, .radio = { @@ -1778,17 +1778,19 @@ struct saa7134_board saa7134_boards[] = { [SAA7134_BOARD_FLYDVBTDUO] = { /* LifeView FlyDVB-T DUO */ /* "Nico Sabbi <nsabbi@tiscali.it> Hartmut Hackmann hartmut.hackmann@t-online.de*/ - .name = "LifeView FlyDVB-T DUO", + .name = "LifeView FlyDVB-T DUO / MSI TV@nywhere Duo", .audio_clock = 0x00200000, .tuner_type = TUNER_PHILIPS_TDA8290, .radio_type = UNSET, .tuner_addr = ADDR_UNSET, .radio_addr = ADDR_UNSET, + .gpiomask = 0x00200000, .mpeg = SAA7134_MPEG_DVB, .inputs = {{ .name = name_tv, .vmux = 1, .amux = TV, + .gpio = 0x200000, /* GPIO21=High for TV input */ .tv = 1, },{ .name = name_comp1, /* Composite signal on S-Video input */ @@ -1803,6 +1805,11 @@ struct saa7134_board saa7134_boards[] = { .vmux = 8, .amux = LINE2, }}, + .radio = { + .name = name_radio, + .amux = TV, + .gpio = 0x000000, /* GPIO21=Low for FM radio antenna */ + }, }, [SAA7134_BOARD_PHILIPS_TOUGH] = { .name = "Philips TOUGH DVB-T reference design", @@ -2546,8 +2553,9 @@ struct saa7134_board saa7134_boards[] = { .radio_type = UNSET, .tuner_addr = ADDR_UNSET, .radio_addr = ADDR_UNSET, + .tuner_config = 0, .mpeg = SAA7134_MPEG_DVB, - .gpiomask = 1 << 21, + .gpiomask = 0x0200000, .inputs = {{ .name = name_tv, .vmux = 1, @@ -2624,7 +2632,7 @@ struct saa7134_board saa7134_boards[] = { }}, .radio = { .name = name_radio, - .amux = LINE1, + .amux = TV, .gpio = 0x0200000, }, }, @@ -3043,6 +3051,7 @@ struct saa7134_board saa7134_boards[] = { .radio_type = UNSET, .tuner_addr = ADDR_UNSET, .radio_addr = ADDR_UNSET, + .tuner_config = 1, .mpeg = SAA7134_MPEG_DVB, .gpiomask = 0x000200000, .inputs = {{ @@ -3289,6 +3298,115 @@ struct saa7134_board saa7134_boards[] = { .amux = LINE1, }}, }, + [SAA7134_BOARD_PHILIPS_TIGER_S] = { + .name = "Philips Tiger - S Reference design", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tuner_config = 2, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = TV, + .gpio = 0x0200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_M102] = { + .name = "Avermedia M102", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1<<21, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 6, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ASUS_P7131_4871] = { + .name = "ASUS P7131 4871", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tuner_config = 2, + .mpeg = SAA7134_MPEG_DVB, + .gpiomask = 0x0200000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + .gpio = 0x0200000, + }}, + }, + [SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA] = { + .name = "ASUSTeK P7131 Hybrid", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .tuner_config = 2, + .gpiomask = 1 << 21, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + .gpio = 0x0000000, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .name = name_comp2, + .vmux = 0, + .amux = LINE2, + .gpio = 0x0200000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x0200000, + }}, + .radio = { + .name = name_radio, + .amux = TV, + .gpio = 0x0200000, + }, + }, }; const unsigned int saa7134_bcount = ARRAY_SIZE(saa7134_boards); @@ -3914,7 +4032,7 @@ struct pci_device_id saa7134_pci_tbl[] = { .device = PCI_DEVICE_ID_PHILIPS_SAA7133, .subvendor = 0x1043, .subdevice = 0x4876, - .driver_data = SAA7134_BOARD_ASUSTeK_P7131_DUAL, + .driver_data = SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA, },{ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7133, @@ -3958,6 +4076,30 @@ struct pci_device_id saa7134_pci_tbl[] = { .subdevice = 0x1175, .driver_data = SAA7134_BOARD_CINERGY_HT_PCI, },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0xf31e, + .driver_data = SAA7134_BOARD_AVERMEDIA_M102, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x4E42, /* MSI */ + .subdevice = 0x0306, /* TV@nywhere DUO */ + .driver_data = SAA7134_BOARD_FLYDVBTDUO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x1043, + .subdevice = 0x4871, + .driver_data = SAA7134_BOARD_ASUS_P7131_4871, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x1043, + .subdevice = 0x4857, + .driver_data = SAA7134_BOARD_ASUSTeK_P7131_DUAL, + },{ /* --- boards without eeprom + subsystem ID --- */ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7134, @@ -3971,7 +4113,6 @@ struct pci_device_id saa7134_pci_tbl[] = { .subdevice = 0, .driver_data = SAA7134_BOARD_NOAUTO, },{ - /* --- default catch --- */ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7130, @@ -4063,6 +4204,7 @@ int saa7134_board_init1(struct saa7134_dev *dev) case SAA7134_BOARD_SEDNA_PC_TV_CARDBUS: case SAA7134_BOARD_FLYDVBT_LR301: case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: case SAA7134_BOARD_FLYDVBTDUO: case SAA7134_BOARD_PROTEUS_2309: case SAA7134_BOARD_AVERMEDIA_A16AR: @@ -4103,8 +4245,8 @@ int saa7134_board_init1(struct saa7134_dev *dev) break; case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: - saa_writeb(SAA7134_GPIO_GPMODE3, 0x08); - saa_writeb(SAA7134_GPIO_GPSTATUS3, 0x00); + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x08000000, 0x08000000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x08000000, 0x00000000); break; case SAA7134_BOARD_AVERMEDIA_CARDBUS: /* power-up tuner chip */ @@ -4137,6 +4279,11 @@ int saa7134_board_init1(struct saa7134_dev *dev) "%s: Dual decoder functionality is disabled for now, use the other chip.\n", dev->name,card(dev).name,dev->name,dev->name); break; + case SAA7134_BOARD_AVERMEDIA_M102: + /* enable tuner */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x8c040007, 0x8c040007); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x0c0007cd, 0x0c0007cd); + break; } return 0; } @@ -4146,6 +4293,9 @@ int saa7134_board_init2(struct saa7134_dev *dev) { unsigned char buf; int board; + struct tuner_setup tun_setup; + tun_setup.config = 0; + tun_setup.tuner_callback = saa7134_tuner_callback; switch (dev->board) { case SAA7134_BOARD_BMK_MPEX_NOTUNER: @@ -4162,8 +4312,6 @@ int saa7134_board_init2(struct saa7134_dev *dev) dev->tuner_type = saa7134_boards[dev->board].tuner_type; if (TUNER_ABSENT != dev->tuner_type) { - struct tuner_setup tun_setup; - tun_setup.mode_mask = T_RADIO | T_ANALOG_TV | T_DIGITAL_TV; tun_setup.type = dev->tuner_type; tun_setup.addr = ADDR_UNSET; @@ -4173,7 +4321,6 @@ int saa7134_board_init2(struct saa7134_dev *dev) break; case SAA7134_BOARD_MD7134: { - struct tuner_setup tun_setup; u8 subaddr; u8 data[3]; int ret, tuner_t; @@ -4245,7 +4392,6 @@ int saa7134_board_init2(struct saa7134_dev *dev) * the channel decoder. We have to make it transparent to find it */ { - struct tuner_setup tun_setup; u8 data[] = { 0x07, 0x02}; struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; i2c_transfer(&dev->i2c_adap, &msg, 1); @@ -4258,16 +4404,38 @@ int saa7134_board_init2(struct saa7134_dev *dev) } break; case SAA7134_BOARD_PHILIPS_TIGER: + case SAA7134_BOARD_PHILIPS_TIGER_S: + { + u8 data[] = { 0x3c, 0x33, 0x60}; + struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + if(dev->autodetected && (dev->eedata[0x49] == 0x50)) { + dev->board = SAA7134_BOARD_PHILIPS_TIGER_S; + printk(KERN_INFO "%s: Reconfigured board as %s\n", + dev->name, saa7134_boards[dev->board].name); + } + if(dev->board == SAA7134_BOARD_PHILIPS_TIGER_S) { + tun_setup.mode_mask = T_ANALOG_TV | T_DIGITAL_TV; + tun_setup.type = TUNER_PHILIPS_TDA8290; + tun_setup.addr = 0x4b; + tun_setup.config = 2; + + saa7134_i2c_call_clients (dev, TUNER_SET_TYPE_ADDR,&tun_setup); + data[2] = 0x68; + } + i2c_transfer(&dev->i2c_adap, &msg, 1); + } + break; case SAA7134_BOARD_PINNACLE_PCTV_310i: case SAA7134_BOARD_TEVION_DVBT_220RF: case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: case SAA7134_BOARD_MEDION_MD8800_QUADRO: case SAA7134_BOARD_HAUPPAUGE_HVR1110: /* this is a hybrid board, initialize to analog mode * and configure firmware eeprom address */ { - u8 data[] = { 0x3c, 0x33, 0x68}; + u8 data[] = { 0x3c, 0x33, 0x60}; struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; i2c_transfer(&dev->i2c_adap, &msg, 1); } @@ -4281,18 +4449,18 @@ int saa7134_board_init2(struct saa7134_dev *dev) break; case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: - /* make the tda10046 find its eeprom */ + /* initialize analog mode */ { - u8 data[] = { 0x3c, 0x33, 0x62}; + u8 data[] = { 0x3c, 0x33, 0x6a}; struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; i2c_transfer(&dev->i2c_adap, &msg, 1); } break; case SAA7134_BOARD_CINERGY_HT_PCMCIA: case SAA7134_BOARD_CINERGY_HT_PCI: - /* make the tda10046 find its eeprom */ + /* initialize analog mode */ { - u8 data[] = { 0x3c, 0x33, 0x60}; + u8 data[] = { 0x3c, 0x33, 0x68}; struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; i2c_transfer(&dev->i2c_adap, &msg, 1); } diff --git a/drivers/media/video/saa7134/saa7134-core.c b/drivers/media/video/saa7134/saa7134-core.c index ed038fff3b4..25f84701a8e 100644 --- a/drivers/media/video/saa7134/saa7134-core.c +++ b/drivers/media/video/saa7134/saa7134-core.c @@ -117,6 +117,64 @@ void saa7134_track_gpio(struct saa7134_dev *dev, char *msg) dev->name, mode, (~mode) & status, mode & status, msg); } +void saa7134_set_gpio(struct saa7134_dev *dev, int bit_no, int value) +{ + u32 index, bitval; + + index = 1 << bit_no; + switch (value) { + case 0: /* static value */ + case 1: dprintk("setting GPIO%d to static %d\n", bit_no, value); + /* turn sync mode off if necessary */ + if (index & 0x00c00000) + saa_andorb(SAA7134_VIDEO_PORT_CTRL6, 0x0f, 0x00); + if (value) + bitval = index; + else + bitval = 0; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, index, index); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, index, bitval); + break; + case 3: /* tristate */ + dprintk("setting GPIO%d to tristate\n", bit_no); + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, index, 0); + break; + } +} + +int saa7134_tuner_callback(void *ptr, int command, int arg) +{ + u8 sync_control; + struct saa7134_dev *dev = ptr; + + switch (dev->tuner_type) { + case TUNER_PHILIPS_TDA8290: + switch (command) { + case 0: /* switch LNA gain through GPIO 22*/ + saa7134_set_gpio(dev, 22, arg) ; + break; + case 1: /* vsync output at GPIO22. 50 / 60Hz */ + dprintk("setting GPIO22 to vsync %d\n", arg); + saa_andorb(SAA7134_VIDEO_PORT_CTRL3, 0x80, 0x80); + saa_andorb(SAA7134_VIDEO_PORT_CTRL6, 0x0f, 0x03); + if (arg == 1) + sync_control = 11; + else + sync_control = 17; + saa_writeb(SAA7134_VGATE_START, sync_control); + saa_writeb(SAA7134_VGATE_STOP, sync_control + 1); + saa_andorb(SAA7134_MISC_VGATE_MSB, 0x03, 0x00); + break; + default: + return -EINVAL; + } + break; + default: + return -ENODEV; + } + return 0; +} + /* ------------------------------------------------------------------ */ @@ -124,55 +182,28 @@ void saa7134_track_gpio(struct saa7134_dev *dev, char *msg) /* delayed request_module */ #if defined(CONFIG_MODULES) && defined(MODULE) -static int need_empress; -static int need_dvb; -static int need_alsa; -static int need_oss; -static int pending_call(struct notifier_block *self, unsigned long state, - void *module) -{ - if (module != THIS_MODULE || state != MODULE_STATE_LIVE) - return NOTIFY_DONE; - if (need_empress) +static void request_module_async(struct work_struct *work){ + struct saa7134_dev* dev = container_of(work, struct saa7134_dev, request_module_wk); + if (card_is_empress(dev)) request_module("saa7134-empress"); - if (need_dvb) + if (card_is_dvb(dev)) request_module("saa7134-dvb"); - if (need_alsa) + if (alsa) request_module("saa7134-alsa"); - if (need_oss) + if (oss) request_module("saa7134-oss"); - return NOTIFY_DONE; } -static int pending_registered; -static struct notifier_block pending_notifier = { - .notifier_call = pending_call, -}; - -static void request_module_depend(char *name, int *flag) +static void request_submodules(struct saa7134_dev *dev) { - int err; - switch (THIS_MODULE->state) { - case MODULE_STATE_COMING: - if (!pending_registered) { - err = register_module_notifier(&pending_notifier); - pending_registered = 1; - } - *flag = 1; - break; - case MODULE_STATE_LIVE: - request_module(name); - break; - default: - /* nothing */; - break; - } + INIT_WORK(&dev->request_module_wk, request_module_async); + schedule_work(&dev->request_module_wk); } #else -#define request_module_depend(name,flag) +#define request_submodules(dev) #endif /* CONFIG_MODULES */ /* ------------------------------------------------------------------ */ @@ -703,7 +734,6 @@ static int saa7134_hwfini(struct saa7134_dev *dev) saa7134_ts_fini(dev); saa7134_input_fini(dev); saa7134_vbi_fini(dev); - saa7134_video_fini(dev); saa7134_tvaudio_fini(dev); return 0; } @@ -944,18 +974,9 @@ static int __devinit saa7134_initdev(struct pci_dev *pci_dev, request_module("tuner"); if (card_is_empress(dev)) { request_module("saa6752hs"); - request_module_depend("saa7134-empress",&need_empress); } - if (card_is_dvb(dev)) - request_module_depend("saa7134-dvb",&need_dvb); - - - if (alsa) - request_module_depend("saa7134-alsa",&need_alsa); - - if (oss) - request_module_depend("saa7134-oss",&need_oss); + request_submodules(dev); v4l2_prio_init(&dev->prio); @@ -1013,6 +1034,9 @@ static int __devinit saa7134_initdev(struct pci_dev *pci_dev, saa7134_dmasound_init(dev); } + if (TUNER_ABSENT != dev->tuner_type) + saa7134_i2c_call_clients(dev, TUNER_SET_STANDBY, NULL); + return 0; fail4: @@ -1152,10 +1176,6 @@ static int saa7134_init(void) static void saa7134_fini(void) { -#if defined(CONFIG_MODULES) && defined(MODULE) - if (pending_registered) - unregister_module_notifier(&pending_notifier); -#endif /* CONFIG_MODULES */ pci_unregister_driver(&saa7134_pci_driver); } @@ -1164,6 +1184,7 @@ module_exit(saa7134_fini); /* ----------------------------------------------------------- */ +EXPORT_SYMBOL(saa7134_set_gpio); EXPORT_SYMBOL(saa7134_i2c_call_clients); EXPORT_SYMBOL(saa7134_devlist); EXPORT_SYMBOL(saa7134_boards); diff --git a/drivers/media/video/saa7134/saa7134-dvb.c b/drivers/media/video/saa7134/saa7134-dvb.c index e3059fd3395..65aec881bbd 100644 --- a/drivers/media/video/saa7134/saa7134-dvb.c +++ b/drivers/media/video/saa7134/saa7134-dvb.c @@ -41,7 +41,9 @@ #include "tda10086.h" #include "tda826x.h" +#include "tda827x.h" #include "isl6421.h" + MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); MODULE_LICENSE("GPL"); @@ -54,7 +56,21 @@ static int use_frontend = 0; module_param(use_frontend, int, 0644); MODULE_PARM_DESC(use_frontend,"for cards with multiple frontends (0: terrestrial, 1: satellite)"); -/* ------------------------------------------------------------------ */ +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off module debugging (default:off)."); + +#define dprintk(fmt, arg...) do { if (debug) \ + printk(KERN_DEBUG "%s/dvb: " fmt, dev->name , ## arg); } while(0) + +/* Print a warning */ +#define wprintk(fmt, arg...) \ + printk(KERN_WARNING "%s/dvb: " fmt, dev->name, ## arg) + +/* ------------------------------------------------------------------ + * mt352 based DVB-T cards + */ + static int pinnacle_antenna_pwr(struct saa7134_dev *dev, int on) { u32 ok; @@ -75,8 +91,7 @@ static int pinnacle_antenna_pwr(struct saa7134_dev *dev, int on) saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 28)); udelay(10); ok = saa_readl(SAA7134_GPIO_GPSTATUS0) & (1 << 27); - printk("%s: %s %s\n", dev->name, __FUNCTION__, - ok ? "on" : "off"); + dprintk("%s %s\n", __FUNCTION__, ok ? "on" : "off"); if (!ok) saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); @@ -96,7 +111,7 @@ static int mt352_pinnacle_init(struct dvb_frontend* fe) static u8 irq_cfg [] = { INTERRUPT_EN_0, 0x00, 0x00, 0x00, 0x00 }; struct saa7134_dev *dev= fe->dvb->priv; - printk("%s: %s called\n",dev->name,__FUNCTION__); + dprintk("%s called\n", __FUNCTION__); mt352_write(fe, clock_config, sizeof(clock_config)); udelay(200); @@ -185,10 +200,26 @@ static struct mt352_config avermedia_777 = { .demod_init = mt352_aver777_init, }; -/* ------------------------------------------------------------------ */ -static int philips_tda6651_pll_set(u8 addr, struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +/* ================================================================== + * tda1004x based DVB-T cards, helper functions + */ + +static int philips_tda1004x_request_firmware(struct dvb_frontend *fe, + const struct firmware **fw, char *name) +{ + struct saa7134_dev *dev = fe->dvb->priv; + return request_firmware(fw, name, &dev->pci->dev); +} + +/* ------------------------------------------------------------------ + * these tuners are tu1216, td1316(a) + */ + +static int philips_tda6651_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) { struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; u8 tuner_buf[4]; struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tuner_buf,.len = sizeof(tuner_buf) }; @@ -263,15 +294,20 @@ static int philips_tda6651_pll_set(u8 addr, struct dvb_frontend *fe, struct dvb_ if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) + if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) { + wprintk("could not write to tuner at addr: 0x%02x\n", + addr << 1); return -EIO; + } msleep(1); return 0; } -static int philips_tda6651_pll_init(u8 addr, struct dvb_frontend *fe) +static int philips_tu1216_init(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; static u8 tu1216_init[] = { 0x0b, 0xf5, 0x85, 0xab }; struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tu1216_init,.len = sizeof(tu1216_init) }; @@ -287,46 +323,17 @@ static int philips_tda6651_pll_init(u8 addr, struct dvb_frontend *fe) /* ------------------------------------------------------------------ */ -static int philips_tu1216_tuner_60_init(struct dvb_frontend *fe) -{ - return philips_tda6651_pll_init(0x60, fe); -} - -static int philips_tu1216_tuner_60_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) -{ - return philips_tda6651_pll_set(0x60, fe, params); -} - -static int philips_tda1004x_request_firmware(struct dvb_frontend *fe, - const struct firmware **fw, char *name) -{ - struct saa7134_dev *dev = fe->dvb->priv; - return request_firmware(fw, name, &dev->pci->dev); -} - static struct tda1004x_config philips_tu1216_60_config = { - .demod_address = 0x8, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_4M, .agc_config = TDA10046_AGC_DEFAULT, .if_freq = TDA10046_FREQ_3617, - .request_firmware = philips_tda1004x_request_firmware, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ - -static int philips_tu1216_tuner_61_init(struct dvb_frontend *fe) -{ - return philips_tda6651_pll_init(0x61, fe); -} - -static int philips_tu1216_tuner_61_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) -{ - return philips_tda6651_pll_set(0x61, fe, params); -} - static struct tda1004x_config philips_tu1216_61_config = { .demod_address = 0x8, @@ -335,7 +342,8 @@ static struct tda1004x_config philips_tu1216_61_config = { .xtal_freq = TDA10046_XTAL_4M, .agc_config = TDA10046_AGC_DEFAULT, .if_freq = TDA10046_FREQ_3617, - .request_firmware = philips_tda1004x_request_firmware, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware }; /* ------------------------------------------------------------------ */ @@ -343,24 +351,42 @@ static struct tda1004x_config philips_tu1216_61_config = { static int philips_td1316_tuner_init(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; static u8 msg[] = { 0x0b, 0xf5, 0x86, 0xab }; - struct i2c_msg init_msg = {.addr = 0x61,.flags = 0,.buf = msg,.len = sizeof(msg) }; + struct i2c_msg init_msg = {.addr = addr,.flags = 0,.buf = msg,.len = sizeof(msg) }; /* setup PLL configuration */ if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); if (i2c_transfer(&dev->i2c_adap, &init_msg, 1) != 1) return -EIO; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); return 0; } static int philips_td1316_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) { - return philips_tda6651_pll_set(0x61, fe, params); + return philips_tda6651_pll_set(fe, params); } +static int philips_td1316_tuner_sleep(struct dvb_frontend *fe) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; + static u8 msg[] = { 0x0b, 0xdc, 0x86, 0xa4 }; + struct i2c_msg analog_msg = {.addr = addr,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + /* switch the tuner to analog mode */ + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 1); + if (i2c_transfer(&dev->i2c_adap, &analog_msg, 1) != 1) + return -EIO; + return 0; +} + +/* ------------------------------------------------------------------ */ + static int philips_europa_tuner_init(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; @@ -380,18 +406,14 @@ static int philips_europa_tuner_init(struct dvb_frontend *fe) static int philips_europa_tuner_sleep(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; - /* this message actually turns the tuner back to analog mode */ - static u8 msg[] = { 0x0b, 0xdc, 0x86, 0xa4 }; - struct i2c_msg analog_msg = {.addr = 0x61,.flags = 0,.buf = msg,.len = sizeof(msg) }; - i2c_transfer(&dev->i2c_adap, &analog_msg, 1); - msleep(1); + static u8 msg[] = { 0x00, 0x14 }; + struct i2c_msg analog_msg = {.addr = 0x43,.flags = 0,.buf = msg,.len = sizeof(msg) }; + + if (philips_td1316_tuner_sleep(fe)) + return -EIO; /* switch the board to analog mode */ - analog_msg.addr = 0x43; - analog_msg.len = 0x02; - msg[0] = 0x00; - msg[1] = 0x14; if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); i2c_transfer(&dev->i2c_adap, &analog_msg, 1); @@ -416,7 +438,8 @@ static struct tda1004x_config philips_europa_config = { .xtal_freq = TDA10046_XTAL_4M, .agc_config = TDA10046_AGC_IFO_AUTO_POS, .if_freq = TDA10046_FREQ_052, - .request_firmware = NULL, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware }; /* ------------------------------------------------------------------ */ @@ -424,9 +447,11 @@ static struct tda1004x_config philips_europa_config = { static int philips_fmd1216_tuner_init(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; /* this message is to set up ATC and ALC */ static u8 fmd1216_init[] = { 0x0b, 0xdc, 0x9c, 0xa0 }; - struct i2c_msg tuner_msg = {.addr = 0x61,.flags = 0,.buf = fmd1216_init,.len = sizeof(fmd1216_init) }; + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = fmd1216_init,.len = sizeof(fmd1216_init) }; if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); @@ -440,9 +465,11 @@ static int philips_fmd1216_tuner_init(struct dvb_frontend *fe) static int philips_fmd1216_tuner_sleep(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; /* this message actually turns the tuner back to analog mode */ - static u8 fmd1216_init[] = { 0x0b, 0xdc, 0x9c, 0x60 }; - struct i2c_msg tuner_msg = {.addr = 0x61,.flags = 0,.buf = fmd1216_init,.len = sizeof(fmd1216_init) }; + u8 fmd1216_init[] = { 0x0b, 0xdc, 0x9c, 0x60 }; + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = fmd1216_init,.len = sizeof(fmd1216_init) }; if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); @@ -460,8 +487,10 @@ static int philips_fmd1216_tuner_sleep(struct dvb_frontend *fe) static int philips_fmd1216_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) { struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->tuner_address; u8 tuner_buf[4]; - struct i2c_msg tuner_msg = {.addr = 0x61,.flags = 0,.buf = tuner_buf,.len = + struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tuner_buf,.len = sizeof(tuner_buf) }; int tuner_frequency = 0; int divider = 0; @@ -536,8 +565,11 @@ static int philips_fmd1216_tuner_set_params(struct dvb_frontend *fe, struct dvb_ if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) + if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) { + wprintk("could not write to tuner at addr: 0x%02x\n", + addr << 1); return -EIO; + } return 0; } @@ -548,582 +580,365 @@ static struct tda1004x_config medion_cardbus = { .xtal_freq = TDA10046_XTAL_16M, .agc_config = TDA10046_AGC_IFO_AUTO_NEG, .if_freq = TDA10046_FREQ_3613, - .request_firmware = NULL, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ - -struct tda827x_data { - u32 lomax; - u8 spd; - u8 bs; - u8 bp; - u8 cp; - u8 gc3; - u8 div1p5; -}; - -static struct tda827x_data tda827x_dvbt[] = { - { .lomax = 62000000, .spd = 3, .bs = 2, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 1}, - { .lomax = 66000000, .spd = 3, .bs = 3, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 1}, - { .lomax = 76000000, .spd = 3, .bs = 1, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 0}, - { .lomax = 84000000, .spd = 3, .bs = 2, .bp = 0, .cp = 0, .gc3 = 3, .div1p5 = 0}, - { .lomax = 93000000, .spd = 3, .bs = 2, .bp = 0, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 98000000, .spd = 3, .bs = 3, .bp = 0, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 109000000, .spd = 3, .bs = 3, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 123000000, .spd = 2, .bs = 2, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 1}, - { .lomax = 133000000, .spd = 2, .bs = 3, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 1}, - { .lomax = 151000000, .spd = 2, .bs = 1, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 154000000, .spd = 2, .bs = 2, .bp = 1, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 181000000, .spd = 2, .bs = 2, .bp = 1, .cp = 0, .gc3 = 0, .div1p5 = 0}, - { .lomax = 185000000, .spd = 2, .bs = 2, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 217000000, .spd = 2, .bs = 3, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 244000000, .spd = 1, .bs = 2, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 1}, - { .lomax = 265000000, .spd = 1, .bs = 3, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 1}, - { .lomax = 302000000, .spd = 1, .bs = 1, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 324000000, .spd = 1, .bs = 2, .bp = 2, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 370000000, .spd = 1, .bs = 2, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 454000000, .spd = 1, .bs = 3, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 493000000, .spd = 0, .bs = 2, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 1}, - { .lomax = 530000000, .spd = 0, .bs = 3, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 1}, - { .lomax = 554000000, .spd = 0, .bs = 1, .bp = 3, .cp = 0, .gc3 = 1, .div1p5 = 0}, - { .lomax = 604000000, .spd = 0, .bs = 1, .bp = 4, .cp = 0, .gc3 = 0, .div1p5 = 0}, - { .lomax = 696000000, .spd = 0, .bs = 2, .bp = 4, .cp = 0, .gc3 = 0, .div1p5 = 0}, - { .lomax = 740000000, .spd = 0, .bs = 2, .bp = 4, .cp = 1, .gc3 = 0, .div1p5 = 0}, - { .lomax = 820000000, .spd = 0, .bs = 3, .bp = 4, .cp = 0, .gc3 = 0, .div1p5 = 0}, - { .lomax = 865000000, .spd = 0, .bs = 3, .bp = 4, .cp = 1, .gc3 = 0, .div1p5 = 0}, - { .lomax = 0, .spd = 0, .bs = 0, .bp = 0, .cp = 0, .gc3 = 0, .div1p5 = 0} -}; - -static int philips_tda827x_tuner_init(struct dvb_frontend *fe) -{ - return 0; -} +/* ------------------------------------------------------------------ + * tda 1004x based cards with philips silicon tuner + */ -static int philips_tda827x_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +static void philips_tda827x_lna_gain(struct dvb_frontend *fe, int high) { struct saa7134_dev *dev = fe->dvb->priv; - u8 tuner_buf[14]; - - struct i2c_msg tuner_msg = {.addr = 0x60,.flags = 0,.buf = tuner_buf, - .len = sizeof(tuner_buf) }; - int i, tuner_freq, if_freq; - u32 N; - switch (params->u.ofdm.bandwidth) { - case BANDWIDTH_6_MHZ: - if_freq = 4000000; - break; - case BANDWIDTH_7_MHZ: - if_freq = 4500000; - break; - default: /* 8 MHz or Auto */ - if_freq = 5000000; - break; - } - tuner_freq = params->frequency + if_freq; - - i = 0; - while (tda827x_dvbt[i].lomax < tuner_freq) { - if(tda827x_dvbt[i + 1].lomax == 0) - break; - i++; + struct tda1004x_state *state = fe->demodulator_priv; + u8 addr = state->config->i2c_gate; + u8 config = state->config->tuner_config; + u8 GP00_CF[] = {0x20, 0x01}; + u8 GP00_LEV[] = {0x22, 0x00}; + + struct i2c_msg msg = {.addr = addr,.flags = 0,.buf = GP00_CF, .len = 2}; + if (config) { + if (high) { + dprintk("setting LNA to high gain\n"); + } else { + dprintk("setting LNA to low gain\n"); + } } - - N = ((tuner_freq + 125000) / 250000) << (tda827x_dvbt[i].spd + 2); - tuner_buf[0] = 0; - tuner_buf[1] = (N>>8) | 0x40; - tuner_buf[2] = N & 0xff; - tuner_buf[3] = 0; - tuner_buf[4] = 0x52; - tuner_buf[5] = (tda827x_dvbt[i].spd << 6) + (tda827x_dvbt[i].div1p5 << 5) + - (tda827x_dvbt[i].bs << 3) + tda827x_dvbt[i].bp; - tuner_buf[6] = (tda827x_dvbt[i].gc3 << 4) + 0x8f; - tuner_buf[7] = 0xbf; - tuner_buf[8] = 0x2a; - tuner_buf[9] = 0x05; - tuner_buf[10] = 0xff; - tuner_buf[11] = 0x00; - tuner_buf[12] = 0x00; - tuner_buf[13] = 0x40; - - tuner_msg.len = 14; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer(&dev->i2c_adap, &tuner_msg, 1) != 1) - return -EIO; - - msleep(500); - /* correct CP value */ - tuner_buf[0] = 0x30; - tuner_buf[1] = 0x50 + tda827x_dvbt[i].cp; - tuner_msg.len = 2; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &tuner_msg, 1); - - return 0; -} - -static int philips_tda827x_tuner_sleep(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - static u8 tda827x_sleep[] = { 0x30, 0xd0}; - struct i2c_msg tuner_msg = {.addr = 0x60,.flags = 0,.buf = tda827x_sleep, - .len = sizeof(tda827x_sleep) }; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &tuner_msg, 1); - return 0; -} - -static struct tda1004x_config tda827x_lifeview_config = { - .demod_address = 0x08, - .invert = 1, - .invert_oclk = 0, - .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, - .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, -}; - -/* ------------------------------------------------------------------ */ - -struct tda827xa_data { - u32 lomax; - u8 svco; - u8 spd; - u8 scr; - u8 sbs; - u8 gc3; -}; - -static struct tda827xa_data tda827xa_dvbt[] = { - { .lomax = 56875000, .svco = 3, .spd = 4, .scr = 0, .sbs = 0, .gc3 = 1}, - { .lomax = 67250000, .svco = 0, .spd = 3, .scr = 0, .sbs = 0, .gc3 = 1}, - { .lomax = 81250000, .svco = 1, .spd = 3, .scr = 0, .sbs = 0, .gc3 = 1}, - { .lomax = 97500000, .svco = 2, .spd = 3, .scr = 0, .sbs = 0, .gc3 = 1}, - { .lomax = 113750000, .svco = 3, .spd = 3, .scr = 0, .sbs = 1, .gc3 = 1}, - { .lomax = 134500000, .svco = 0, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1}, - { .lomax = 154000000, .svco = 1, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1}, - { .lomax = 162500000, .svco = 1, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1}, - { .lomax = 183000000, .svco = 2, .spd = 2, .scr = 0, .sbs = 1, .gc3 = 1}, - { .lomax = 195000000, .svco = 2, .spd = 2, .scr = 0, .sbs = 2, .gc3 = 1}, - { .lomax = 227500000, .svco = 3, .spd = 2, .scr = 0, .sbs = 2, .gc3 = 1}, - { .lomax = 269000000, .svco = 0, .spd = 1, .scr = 0, .sbs = 2, .gc3 = 1}, - { .lomax = 290000000, .svco = 1, .spd = 1, .scr = 0, .sbs = 2, .gc3 = 1}, - { .lomax = 325000000, .svco = 1, .spd = 1, .scr = 0, .sbs = 3, .gc3 = 1}, - { .lomax = 390000000, .svco = 2, .spd = 1, .scr = 0, .sbs = 3, .gc3 = 1}, - { .lomax = 455000000, .svco = 3, .spd = 1, .scr = 0, .sbs = 3, .gc3 = 1}, - { .lomax = 520000000, .svco = 0, .spd = 0, .scr = 0, .sbs = 3, .gc3 = 1}, - { .lomax = 538000000, .svco = 0, .spd = 0, .scr = 1, .sbs = 3, .gc3 = 1}, - { .lomax = 550000000, .svco = 1, .spd = 0, .scr = 0, .sbs = 3, .gc3 = 1}, - { .lomax = 620000000, .svco = 1, .spd = 0, .scr = 0, .sbs = 4, .gc3 = 0}, - { .lomax = 650000000, .svco = 1, .spd = 0, .scr = 1, .sbs = 4, .gc3 = 0}, - { .lomax = 700000000, .svco = 2, .spd = 0, .scr = 0, .sbs = 4, .gc3 = 0}, - { .lomax = 780000000, .svco = 2, .spd = 0, .scr = 1, .sbs = 4, .gc3 = 0}, - { .lomax = 820000000, .svco = 3, .spd = 0, .scr = 0, .sbs = 4, .gc3 = 0}, - { .lomax = 870000000, .svco = 3, .spd = 0, .scr = 1, .sbs = 4, .gc3 = 0}, - { .lomax = 911000000, .svco = 3, .spd = 0, .scr = 2, .sbs = 4, .gc3 = 0}, - { .lomax = 0, .svco = 0, .spd = 0, .scr = 0, .sbs = 0, .gc3 = 0}}; - - -static int philips_tda827xa_pll_set(u8 addr, struct dvb_frontend *fe, struct dvb_frontend_parameters *params) -{ - struct saa7134_dev *dev = fe->dvb->priv; - u8 tuner_buf[14]; - unsigned char reg2[2]; - - struct i2c_msg msg = {.addr = addr,.flags = 0,.buf = tuner_buf}; - int i, tuner_freq, if_freq; - u32 N; - - switch (params->u.ofdm.bandwidth) { - case BANDWIDTH_6_MHZ: - if_freq = 4000000; + switch (config) { + case 0: /* no LNA */ break; - case BANDWIDTH_7_MHZ: - if_freq = 4500000; + case 1: /* switch is GPIO 0 of tda8290 */ + case 2: + /* turn Vsync off */ + saa7134_set_gpio(dev, 22, 0); + GP00_LEV[1] = high ? 0 : 1; + if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) { + wprintk("could not access tda8290 at addr: 0x%02x\n", + addr << 1); + return; + } + msg.buf = GP00_LEV; + if (config == 2) + GP00_LEV[1] = high ? 1 : 0; + i2c_transfer(&dev->i2c_adap, &msg, 1); break; - default: /* 8 MHz or Auto */ - if_freq = 5000000; + case 3: /* switch with GPIO of saa713x */ + saa7134_set_gpio(dev, 22, high); break; } - tuner_freq = params->frequency + if_freq; - - i = 0; - while (tda827xa_dvbt[i].lomax < tuner_freq) { - if(tda827xa_dvbt[i + 1].lomax == 0) - break; - i++; - } - - N = ((tuner_freq + 31250) / 62500) << tda827xa_dvbt[i].spd; - tuner_buf[0] = 0; // subaddress - tuner_buf[1] = N >> 8; - tuner_buf[2] = N & 0xff; - tuner_buf[3] = 0; - tuner_buf[4] = 0x16; - tuner_buf[5] = (tda827xa_dvbt[i].spd << 5) + (tda827xa_dvbt[i].svco << 3) + - tda827xa_dvbt[i].sbs; - tuner_buf[6] = 0x4b + (tda827xa_dvbt[i].gc3 << 4); - tuner_buf[7] = 0x0c; - tuner_buf[8] = 0x06; - tuner_buf[9] = 0x24; - tuner_buf[10] = 0xff; - tuner_buf[11] = 0x60; - tuner_buf[12] = 0x00; - tuner_buf[13] = 0x39; // lpsel - msg.len = 14; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) - return -EIO; - - msg.buf= reg2; - msg.len = 2; - reg2[0] = 0x60; - reg2[1] = 0x3c; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &msg, 1); - - reg2[0] = 0xa0; - reg2[1] = 0x40; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &msg, 1); - - msleep(2); - /* correct CP value */ - reg2[0] = 0x30; - reg2[1] = 0x10 + tda827xa_dvbt[i].scr; - msg.len = 2; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &msg, 1); - - msleep(550); - reg2[0] = 0x50; - reg2[1] = 0x4f + (tda827xa_dvbt[i].gc3 << 4); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &msg, 1); - - return 0; - } -static int philips_tda827xa_tuner_sleep(u8 addr, struct dvb_frontend *fe) +static int tda8290_i2c_gate_ctrl( struct dvb_frontend* fe, int enable) { - struct saa7134_dev *dev = fe->dvb->priv; - static u8 tda827xa_sleep[] = { 0x30, 0x90}; - struct i2c_msg tuner_msg = {.addr = addr,.flags = 0,.buf = tda827xa_sleep, - .len = sizeof(tda827xa_sleep) }; - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 1); - i2c_transfer(&dev->i2c_adap, &tuner_msg, 1); - if (fe->ops.i2c_gate_ctrl) - fe->ops.i2c_gate_ctrl(fe, 0); - return 0; -} + struct tda1004x_state *state = fe->demodulator_priv; -/* ------------------------------------------------------------------ */ - -static int tda8290_i2c_gate_ctrl(struct dvb_frontend* fe, int enable) -{ - struct saa7134_dev *dev = fe->dvb->priv; + u8 addr = state->config->i2c_gate; static u8 tda8290_close[] = { 0x21, 0xc0}; static u8 tda8290_open[] = { 0x21, 0x80}; - struct i2c_msg tda8290_msg = {.addr = 0x4b,.flags = 0, .len = 2}; + struct i2c_msg tda8290_msg = {.addr = addr,.flags = 0, .len = 2}; if (enable) { tda8290_msg.buf = tda8290_close; } else { tda8290_msg.buf = tda8290_open; } - if (i2c_transfer(&dev->i2c_adap, &tda8290_msg, 1) != 1) + if (i2c_transfer(state->i2c, &tda8290_msg, 1) != 1) { + struct saa7134_dev *dev = fe->dvb->priv; + wprintk("could not access tda8290 I2C gate\n"); return -EIO; + } msleep(20); return 0; } /* ------------------------------------------------------------------ */ -static int philips_tiger_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +static int philips_tda827x_tuner_init(struct dvb_frontend *fe) { - int ret; + struct saa7134_dev *dev = fe->dvb->priv; + struct tda1004x_state *state = fe->demodulator_priv; - ret = philips_tda827xa_pll_set(0x61, fe, params); - if (ret != 0) - return ret; + switch (state->config->antenna_switch) { + case 0: break; + case 1: dprintk("setting GPIO21 to 0 (TV antenna?)\n"); + saa7134_set_gpio(dev, 21, 0); + break; + case 2: dprintk("setting GPIO21 to 1 (Radio antenna?)\n"); + saa7134_set_gpio(dev, 21, 1); + break; + } return 0; } -static int philips_tiger_tuner_init(struct dvb_frontend *fe) +static int philips_tda827x_tuner_sleep(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x6a}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; + struct tda1004x_state *state = fe->demodulator_priv; - if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) - return -EIO; + switch (state->config->antenna_switch) { + case 0: break; + case 1: dprintk("setting GPIO21 to 1 (Radio antenna?)\n"); + saa7134_set_gpio(dev, 21, 1); + break; + case 2: dprintk("setting GPIO21 to 0 (TV antenna?)\n"); + saa7134_set_gpio(dev, 21, 0); + break; + } return 0; } -static int philips_tiger_tuner_sleep(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x68}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; +static struct tda827x_config tda827x_cfg = { + .lna_gain = philips_tda827x_lna_gain, + .init = philips_tda827x_tuner_init, + .sleep = philips_tda827x_tuner_sleep +}; - i2c_transfer(&dev->i2c_adap, &msg, 1); - philips_tda827xa_tuner_sleep( 0x61, fe); - return 0; +static void configure_tda827x_fe(struct saa7134_dev *dev, struct tda1004x_config *tda_conf) +{ + dev->dvb.frontend = dvb_attach(tda10046_attach, tda_conf, &dev->i2c_adap); + if (dev->dvb.frontend) { + if (tda_conf->i2c_gate) + dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; + if (dvb_attach(tda827x_attach, dev->dvb.frontend, tda_conf->tuner_address, + &dev->i2c_adap,&tda827x_cfg) == NULL) { + wprintk("no tda827x tuner found at addr: %02x\n", + tda_conf->tuner_address); + } + } } -static struct tda1004x_config philips_tiger_config = { +/* ------------------------------------------------------------------ */ +static struct tda1004x_config tda827x_lifeview_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ - -static int cinergy_ht_tuner_init(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x62}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; - if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) - return -EIO; - return 0; -} - -static int cinergy_ht_tuner_sleep(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x60}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; - - i2c_transfer(&dev->i2c_adap, &msg, 1); - philips_tda827xa_tuner_sleep( 0x61, fe); - return 0; -} +static struct tda1004x_config philips_tiger_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 0, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware +}; static struct tda1004x_config cinergy_ht_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP01, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 0, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ +static struct tda1004x_config cinergy_ht_pci_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .tuner_config = 0, + .request_firmware = philips_tda1004x_request_firmware +}; -static struct tda1004x_config pinnacle_pctv_310i_config = { +static struct tda1004x_config philips_tiger_s_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = philips_tda1004x_request_firmware, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 2, + .antenna_switch= 1, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ +static struct tda1004x_config pinnacle_pctv_310i_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 1, + .request_firmware = philips_tda1004x_request_firmware +}; static struct tda1004x_config hauppauge_hvr_1110_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = philips_tda1004x_request_firmware, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ - static struct tda1004x_config asus_p7131_dual_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = philips_tda1004x_request_firmware, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 0, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware }; -static int asus_p7131_dual_tuner_init(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x6a}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; - - if (i2c_transfer(&dev->i2c_adap, &msg, 1) != 1) - return -EIO; - /* make sure the DVB-T antenna input is set */ - saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x0200000); - return 0; -} - -static int asus_p7131_dual_tuner_sleep(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x68}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; - - i2c_transfer(&dev->i2c_adap, &msg, 1); - philips_tda827xa_tuner_sleep( 0x61, fe); - /* reset antenna inputs for analog usage */ - saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x0200000); - return 0; -} - -/* ------------------------------------------------------------------ */ - -static int lifeview_trio_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) -{ - int ret; - - ret = philips_tda827xa_pll_set(0x60, fe, params); - return ret; -} - -static int lifeview_trio_tuner_sleep(struct dvb_frontend *fe) -{ - philips_tda827xa_tuner_sleep(0x60, fe); - return 0; -} - static struct tda1004x_config lifeview_trio_config = { .demod_address = 0x09, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP00, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP00_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ - -static int ads_duo_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) -{ - int ret; - - ret = philips_tda827xa_pll_set(0x61, fe, params); - return ret; -} - -static int ads_duo_tuner_init(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - /* route TDA8275a AGC input to the channel decoder */ - saa_writeb(SAA7134_GPIO_GPSTATUS2, 0x60); - return 0; -} - -static int ads_duo_tuner_sleep(struct dvb_frontend *fe) -{ - struct saa7134_dev *dev = fe->dvb->priv; - /* route TDA8275a AGC input to the analog IF chip*/ - saa_writeb(SAA7134_GPIO_GPSTATUS2, 0x20); - philips_tda827xa_tuner_sleep( 0x61, fe); - return 0; -} - -static struct tda1004x_config ads_tech_duo_config = { +static struct tda1004x_config tevion_dvbt220rf_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP00, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, + .tuner_address = 0x60, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ - -static int tevion_dvb220rf_tuner_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) -{ - int ret; - ret = philips_tda827xa_pll_set(0x60, fe, params); - return ret; -} - -static int tevion_dvb220rf_tuner_sleep(struct dvb_frontend *fe) -{ - philips_tda827xa_tuner_sleep( 0x61, fe); - return 0; -} +static struct tda1004x_config md8800_dvbt_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x60, + .tuner_config = 0, + .request_firmware = philips_tda1004x_request_firmware +}; -static struct tda1004x_config tevion_dvbt220rf_config = { +static struct tda1004x_config asus_p7131_4871_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP01_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 2, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware }; -/* ------------------------------------------------------------------ */ +static struct tda1004x_config asus_p7131_hybrid_lna_config = { + .demod_address = 0x08, + .invert = 1, + .invert_oclk = 0, + .xtal_freq = TDA10046_XTAL_16M, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP11_I, + .if_freq = TDA10046_FREQ_045, + .i2c_gate = 0x4b, + .tuner_address = 0x61, + .tuner_config = 2, + .antenna_switch= 2, + .request_firmware = philips_tda1004x_request_firmware +}; +/* ------------------------------------------------------------------ + * special case: this card uses saa713x GPIO22 for the mode switch + */ -static int md8800_dvbt_analog_mode(struct dvb_frontend *fe) +static int ads_duo_tuner_init(struct dvb_frontend *fe) { struct saa7134_dev *dev = fe->dvb->priv; - static u8 data[] = { 0x3c, 0x33, 0x68}; - struct i2c_msg msg = {.addr=0x08, .flags=0, .buf=data, .len = sizeof(data)}; - - i2c_transfer(&dev->i2c_adap, &msg, 1); - philips_tda827xa_tuner_sleep( 0x61, fe); + philips_tda827x_tuner_init(fe); + /* route TDA8275a AGC input to the channel decoder */ + saa7134_set_gpio(dev, 22, 1); return 0; } -static int md8800_dvbt_pll_set(struct dvb_frontend *fe, struct dvb_frontend_parameters *params) +static int ads_duo_tuner_sleep(struct dvb_frontend *fe) { - int ret; struct saa7134_dev *dev = fe->dvb->priv; - static u8 tda8290_close[] = { 0x21, 0xc0}; - static u8 tda8290_open[] = { 0x21, 0x80}; - struct i2c_msg tda8290_msg = {.addr = 0x4b,.flags = 0, .len = 2}; - /* close tda8290 i2c bridge */ - tda8290_msg.buf = tda8290_close; - ret = i2c_transfer(&dev->i2c_adap, &tda8290_msg, 1); - if (ret != 1) - return -EIO; - msleep(20); - ret = philips_tda827xa_pll_set(0x60, fe, params); - if (ret != 0) - return ret; - /* open tda8290 i2c bridge */ - tda8290_msg.buf = tda8290_open; - i2c_transfer(&dev->i2c_adap, &tda8290_msg, 1); - return ret; + /* route TDA8275a AGC input to the analog IF chip*/ + saa7134_set_gpio(dev, 22, 0); + philips_tda827x_tuner_sleep(fe); + return 0; } -static struct tda1004x_config md8800_dvbt_config = { +static struct tda827x_config ads_duo_cfg = { + .lna_gain = philips_tda827x_lna_gain, + .init = ads_duo_tuner_init, + .sleep = ads_duo_tuner_sleep +}; + +static struct tda1004x_config ads_tech_duo_config = { .demod_address = 0x08, .invert = 1, .invert_oclk = 0, .xtal_freq = TDA10046_XTAL_16M, - .agc_config = TDA10046_AGC_TDA827X_GP11, + .agc_config = TDA10046_AGC_TDA827X, + .gpio_config = TDA10046_GP00_I, .if_freq = TDA10046_FREQ_045, - .request_firmware = NULL, + .tuner_address = 0x61, + .request_firmware = philips_tda1004x_request_firmware }; +/* ================================================================== + * tda10086 based DVB-S cards, helper functions + */ + static struct tda10086_config flydvbs = { .demod_address = 0x0e, .invert = 0, }; -/* ------------------------------------------------------------------ */ +/* ================================================================== + * nxt200x based ATSC cards, helper functions + */ static struct nxt200x_config avertvhda180 = { .demod_address = 0x0a, @@ -1143,10 +958,13 @@ static struct nxt200x_config kworldatsc110 = { .set_pll_input = nxt200x_set_pll_input, }; -/* ------------------------------------------------------------------ */ +/* ================================================================== + * Core code + */ static int dvb_init(struct saa7134_dev *dev) { + int ret; /* init struct videobuf_dvb */ dev->ts.nr_bufs = 32; dev->ts.nr_packets = 32*4; @@ -1160,7 +978,7 @@ static int dvb_init(struct saa7134_dev *dev) switch (dev->board) { case SAA7134_BOARD_PINNACLE_300I_DVBT_PAL: - printk("%s: pinnacle 300i dvb setup\n",dev->name); + dprintk("pinnacle 300i dvb setup\n"); dev->dvb.frontend = dvb_attach(mt352_attach, &pinnacle_300i, &dev->i2c_adap); if (dev->dvb.frontend) { @@ -1169,7 +987,7 @@ static int dvb_init(struct saa7134_dev *dev) break; case SAA7134_BOARD_AVERMEDIA_777: case SAA7134_BOARD_AVERMEDIA_A16AR: - printk("%s: avertv 777 dvb setup\n",dev->name); + dprintk("avertv 777 dvb setup\n"); dev->dvb.frontend = dvb_attach(mt352_attach, &avermedia_777, &dev->i2c_adap); if (dev->dvb.frontend) { @@ -1191,42 +1009,15 @@ static int dvb_init(struct saa7134_dev *dev) &philips_tu1216_60_config, &dev->i2c_adap); if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = philips_tu1216_tuner_60_init; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tu1216_tuner_60_set_params; + dev->dvb.frontend->ops.tuner_ops.init = philips_tu1216_init; + dev->dvb.frontend->ops.tuner_ops.set_params = philips_tda6651_pll_set; } break; case SAA7134_BOARD_FLYDVBTDUO: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &tda827x_lifeview_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = philips_tda827x_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_tda827x_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tda827x_tuner_set_params; - } - break; case SAA7134_BOARD_FLYDVBT_DUO_CARDBUS: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &tda827x_lifeview_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = philips_tda827x_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_tda827x_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tda827x_tuner_set_params; - } + configure_tda827x_fe(dev, &tda827x_lifeview_config); break; case SAA7134_BOARD_PHILIPS_EUROPA: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &philips_europa_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->original_demod_sleep = dev->dvb.frontend->ops.sleep; - dev->dvb.frontend->ops.sleep = philips_europa_demod_sleep; - dev->dvb.frontend->ops.tuner_ops.init = philips_europa_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_europa_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_td1316_tuner_set_params; - } - break; case SAA7134_BOARD_VIDEOMATE_DVBT_300: dev->dvb.frontend = dvb_attach(tda10046_attach, &philips_europa_config, @@ -1244,125 +1035,61 @@ static int dvb_init(struct saa7134_dev *dev) &philips_tu1216_61_config, &dev->i2c_adap); if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = philips_tu1216_tuner_61_init; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tu1216_tuner_61_set_params; + dev->dvb.frontend->ops.tuner_ops.init = philips_tu1216_init; + dev->dvb.frontend->ops.tuner_ops.set_params = philips_tda6651_pll_set; } break; case SAA7134_BOARD_PHILIPS_TIGER: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &philips_tiger_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; - dev->dvb.frontend->ops.tuner_ops.init = philips_tiger_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_tiger_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tiger_tuner_set_params; - } + configure_tda827x_fe(dev, &philips_tiger_config); break; case SAA7134_BOARD_PINNACLE_PCTV_310i: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &pinnacle_pctv_310i_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; - dev->dvb.frontend->ops.tuner_ops.init = philips_tiger_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_tiger_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tiger_tuner_set_params; - } + configure_tda827x_fe(dev, &pinnacle_pctv_310i_config); break; case SAA7134_BOARD_HAUPPAUGE_HVR1110: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &hauppauge_hvr_1110_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; - dev->dvb.frontend->ops.tuner_ops.init = philips_tiger_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_tiger_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tiger_tuner_set_params; - } + configure_tda827x_fe(dev, &hauppauge_hvr_1110_config); break; case SAA7134_BOARD_ASUSTeK_P7131_DUAL: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &asus_p7131_dual_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; - dev->dvb.frontend->ops.tuner_ops.init = asus_p7131_dual_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = asus_p7131_dual_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tiger_tuner_set_params; - } + configure_tda827x_fe(dev, &asus_p7131_dual_config); break; case SAA7134_BOARD_FLYDVBT_LR301: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &tda827x_lifeview_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = philips_tda827x_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = philips_tda827x_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tda827x_tuner_set_params; - } + configure_tda827x_fe(dev, &tda827x_lifeview_config); break; case SAA7134_BOARD_FLYDVB_TRIO: if(! use_frontend) { //terrestrial - dev->dvb.frontend = dvb_attach(tda10046_attach, - &lifeview_trio_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.sleep = lifeview_trio_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = - lifeview_trio_tuner_set_params; - } + configure_tda827x_fe(dev, &lifeview_trio_config); } else { //satellite dev->dvb.frontend = dvb_attach(tda10086_attach, &flydvbs, &dev->i2c_adap); if (dev->dvb.frontend) { if (dvb_attach(tda826x_attach, dev->dvb.frontend, 0x63, &dev->i2c_adap, 0) == NULL) { - printk("%s: Lifeview Trio, No tda826x found!\n", __FUNCTION__); + wprintk("%s: Lifeview Trio, No tda826x found!\n", __FUNCTION__); } if (dvb_attach(isl6421_attach, dev->dvb.frontend, &dev->i2c_adap, 0x08, 0, 0) == NULL) { - printk("%s: Lifeview Trio, No ISL6421 found!\n", __FUNCTION__); + wprintk("%s: Lifeview Trio, No ISL6421 found!\n", __FUNCTION__); } } } break; case SAA7134_BOARD_ADS_DUO_CARDBUS_PTV331: + case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: dev->dvb.frontend = dvb_attach(tda10046_attach, &ads_tech_duo_config, &dev->i2c_adap); if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = ads_duo_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = ads_duo_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = ads_duo_tuner_set_params; + if (dvb_attach(tda827x_attach,dev->dvb.frontend, + ads_tech_duo_config.tuner_address, + &dev->i2c_adap,&ads_duo_cfg) == NULL) { + wprintk("no tda827x tuner found at addr: %02x\n", + ads_tech_duo_config.tuner_address); + } } break; case SAA7134_BOARD_TEVION_DVBT_220RF: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &tevion_dvbt220rf_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.sleep = tevion_dvb220rf_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = tevion_dvb220rf_tuner_set_params; - } - break; - case SAA7134_BOARD_FLYDVBT_HYBRID_CARDBUS: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &ads_tech_duo_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = ads_duo_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = ads_duo_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = ads_duo_tuner_set_params; - } + configure_tda827x_fe(dev, &tevion_dvbt220rf_config); break; case SAA7134_BOARD_MEDION_MD8800_QUADRO: - dev->dvb.frontend = tda10046_attach(&md8800_dvbt_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.tuner_ops.init = philips_tiger_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = md8800_dvbt_analog_mode; - dev->dvb.frontend->ops.tuner_ops.set_params = md8800_dvbt_pll_set; - } + configure_tda827x_fe(dev, &md8800_dvbt_config); break; case SAA7134_BOARD_AVERMEDIA_AVERTVHD_A180: dev->dvb.frontend = dvb_attach(nxt200x_attach, &avertvhda180, @@ -1386,11 +1113,11 @@ static int dvb_init(struct saa7134_dev *dev) if (dev->dvb.frontend) { if (dvb_attach(tda826x_attach, dev->dvb.frontend, 0x60, &dev->i2c_adap, 0) == NULL) { - printk("%s: No tda826x found!\n", __FUNCTION__); + wprintk("%s: No tda826x found!\n", __FUNCTION__); } if (dvb_attach(isl6421_attach, dev->dvb.frontend, &dev->i2c_adap, 0x08, 0, 0) == NULL) { - printk("%s: No ISL6421 found!\n", __FUNCTION__); + wprintk("%s: No ISL6421 found!\n", __FUNCTION__); } } break; @@ -1415,41 +1142,45 @@ static int dvb_init(struct saa7134_dev *dev) } break; case SAA7134_BOARD_CINERGY_HT_PCMCIA: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &cinergy_ht_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; - dev->dvb.frontend->ops.tuner_ops.init = cinergy_ht_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = cinergy_ht_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = philips_tiger_tuner_set_params; - - } + configure_tda827x_fe(dev, &cinergy_ht_config); break; case SAA7134_BOARD_CINERGY_HT_PCI: - dev->dvb.frontend = dvb_attach(tda10046_attach, - &cinergy_ht_config, - &dev->i2c_adap); - if (dev->dvb.frontend) { - dev->dvb.frontend->ops.i2c_gate_ctrl = tda8290_i2c_gate_ctrl; - dev->dvb.frontend->ops.tuner_ops.init = cinergy_ht_tuner_init; - dev->dvb.frontend->ops.tuner_ops.sleep = cinergy_ht_tuner_sleep; - dev->dvb.frontend->ops.tuner_ops.set_params = md8800_dvbt_pll_set; - - } + configure_tda827x_fe(dev, &cinergy_ht_pci_config); + break; + case SAA7134_BOARD_PHILIPS_TIGER_S: + configure_tda827x_fe(dev, &philips_tiger_s_config); + break; + case SAA7134_BOARD_ASUS_P7131_4871: + configure_tda827x_fe(dev, &asus_p7131_4871_config); + break; + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: + configure_tda827x_fe(dev, &asus_p7131_hybrid_lna_config); break; default: - printk("%s: Huh? unknown DVB card?\n",dev->name); + wprintk("Huh? unknown DVB card?\n"); break; } if (NULL == dev->dvb.frontend) { - printk("%s: frontend initialization failed\n",dev->name); + printk(KERN_ERR "%s/dvb: frontend initialization failed\n", dev->name); return -1; } /* register everything else */ - return videobuf_dvb_register(&dev->dvb, THIS_MODULE, dev, &dev->pci->dev); + ret = videobuf_dvb_register(&dev->dvb, THIS_MODULE, dev, &dev->pci->dev); + + /* this sequence is necessary to make the tda1004x load its firmware + * and to enter analog mode of hybrid boards + */ + if (!ret) { + if (dev->dvb.frontend->ops.init) + dev->dvb.frontend->ops.init(dev->dvb.frontend); + if (dev->dvb.frontend->ops.sleep) + dev->dvb.frontend->ops.sleep(dev->dvb.frontend); + if (dev->dvb.frontend->ops.tuner_ops.sleep) + dev->dvb.frontend->ops.tuner_ops.sleep(dev->dvb.frontend); + } + return ret; } static int dvb_fini(struct saa7134_dev *dev) diff --git a/drivers/media/video/saa7134/saa7134-i2c.c b/drivers/media/video/saa7134/saa7134-i2c.c index cce8da6a4f9..1cb8c709ca9 100644 --- a/drivers/media/video/saa7134/saa7134-i2c.c +++ b/drivers/media/video/saa7134/saa7134-i2c.c @@ -370,6 +370,8 @@ static int attach_inform(struct i2c_client *client) tun_setup.type = tuner; tun_setup.addr = saa7134_boards[dev->board].tuner_addr; + tun_setup.config = saa7134_boards[dev->board].tuner_config; + tun_setup.tuner_callback = saa7134_tuner_callback; if ((tun_setup.addr == ADDR_UNSET)||(tun_setup.addr == client->addr)) { @@ -445,7 +447,7 @@ static void do_i2c_scan(char *name, struct i2c_client *c) unsigned char buf; int i,rc; - for (i = 0; i < 128; i++) { + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { c->addr = i; rc = i2c_master_recv(c,&buf,0); if (rc < 0) diff --git a/drivers/media/video/saa7134/saa7134-input.c b/drivers/media/video/saa7134/saa7134-input.c index 46c583f1e78..c0de37e3f5c 100644 --- a/drivers/media/video/saa7134/saa7134-input.c +++ b/drivers/media/video/saa7134/saa7134-input.c @@ -321,6 +321,7 @@ int saa7134_input_init1(struct saa7134_dev *dev) mask_keydown = 0x0040000; break; case SAA7134_BOARD_ASUSTeK_P7131_DUAL: + case SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA: ir_codes = ir_codes_asus_pc39; mask_keydown = 0x0040000; rc5_gpio = 1; diff --git a/drivers/media/video/saa7134/saa7134-video.c b/drivers/media/video/saa7134/saa7134-video.c index f2cb6305304..9985ded2095 100644 --- a/drivers/media/video/saa7134/saa7134-video.c +++ b/drivers/media/video/saa7134/saa7134-video.c @@ -26,6 +26,7 @@ #include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/slab.h> +#include <linux/sort.h> #include "saa7134-reg.h" #include "saa7134.h" @@ -516,14 +517,12 @@ static int res_get(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int return 1; } -static -int res_check(struct saa7134_fh *fh, unsigned int bit) +static int res_check(struct saa7134_fh *fh, unsigned int bit) { return (fh->resources & bit); } -static -int res_locked(struct saa7134_dev *dev, unsigned int bit) +static int res_locked(struct saa7134_dev *dev, unsigned int bit) { return (dev->resources & bit); } @@ -603,7 +602,14 @@ static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm) saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40); saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80); - saa7134_i2c_call_clients(dev,VIDIOC_S_STD,&norm->id); + /* only tell the tuner if this is a tv input */ + if (card_in(dev,dev->ctl_input).tv) { + if ((card(dev).tuner_type == TUNER_PHILIPS_TDA8290) + && ((card(dev).tuner_config == 1) + || (card(dev).tuner_config == 2))) + saa7134_set_gpio(dev, 22, 5); + saa7134_i2c_call_clients(dev,VIDIOC_S_STD,&norm->id); + } } static void video_mux(struct saa7134_dev *dev, int input) @@ -732,25 +738,6 @@ struct cliplist { __u8 disable; }; -static void sort_cliplist(struct cliplist *cl, int entries) -{ - struct cliplist swap; - int i,j,n; - - for (i = entries-2; i >= 0; i--) { - for (n = 0, j = 0; j <= i; j++) { - if (cl[j].position > cl[j+1].position) { - swap = cl[j]; - cl[j] = cl[j+1]; - cl[j+1] = swap; - n++; - } - } - if (0 == n) - break; - } -} - static void set_cliplist(struct saa7134_dev *dev, int reg, struct cliplist *cl, int entries, char *name) { @@ -784,15 +771,27 @@ static int clip_range(int val) return val; } +/* Sort into smallest position first order */ +static int cliplist_cmp(const void *a, const void *b) +{ + const struct cliplist *cla = a; + const struct cliplist *clb = b; + if (cla->position < clb->position) + return -1; + if (cla->position > clb->position) + return 1; + return 0; +} + static int setup_clipping(struct saa7134_dev *dev, struct v4l2_clip *clips, int nclips, int interlace) { struct cliplist col[16], row[16]; - int cols, rows, i; + int cols = 0, rows = 0, i; int div = interlace ? 2 : 1; - memset(col,0,sizeof(col)); cols = 0; - memset(row,0,sizeof(row)); rows = 0; + memset(col, 0, sizeof(col)); + memset(row, 0, sizeof(row)); for (i = 0; i < nclips && i < 8; i++) { col[cols].position = clip_range(clips[i].c.left); col[cols].enable = (1 << i); @@ -808,8 +807,8 @@ static int setup_clipping(struct saa7134_dev *dev, struct v4l2_clip *clips, row[rows].disable = (1 << i); rows++; } - sort_cliplist(col,cols); - sort_cliplist(row,rows); + sort(col, cols, sizeof col[0], cliplist_cmp, NULL); + sort(row, rows, sizeof row[0], cliplist_cmp, NULL); set_cliplist(dev,0x380,col,cols,"cols"); set_cliplist(dev,0x384,row,rows,"rows"); return 0; @@ -1261,19 +1260,14 @@ static struct videobuf_queue* saa7134_queue(struct saa7134_fh *fh) static int saa7134_resource(struct saa7134_fh *fh) { - int res = 0; + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return RESOURCE_VIDEO; - switch (fh->type) { - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - res = RESOURCE_VIDEO; - break; - case V4L2_BUF_TYPE_VBI_CAPTURE: - res = RESOURCE_VBI; - break; - default: - BUG(); - } - return res; + if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) + return RESOURCE_VBI; + + BUG(); + return 0; } static int video_open(struct inode *inode, struct file *file) @@ -1461,8 +1455,7 @@ static int video_release(struct inode *inode, struct file *file) return 0; } -static int -video_mmap(struct file *file, struct vm_area_struct * vma) +static int video_mmap(struct file *file, struct vm_area_struct * vma) { struct saa7134_fh *fh = file->private_data; @@ -2461,12 +2454,6 @@ int saa7134_video_init2(struct saa7134_dev *dev) return 0; } -int saa7134_video_fini(struct saa7134_dev *dev) -{ - /* nothing */ - return 0; -} - void saa7134_irq_video_intl(struct saa7134_dev *dev) { static const char *st[] = { diff --git a/drivers/media/video/saa7134/saa7134.h b/drivers/media/video/saa7134/saa7134.h index b3e3957c89b..62224cc958f 100644 --- a/drivers/media/video/saa7134/saa7134.h +++ b/drivers/media/video/saa7134/saa7134.h @@ -231,6 +231,10 @@ struct saa7134_format { #define SAA7134_BOARD_ENCORE_ENLTV 106 #define SAA7134_BOARD_ENCORE_ENLTV_FM 107 #define SAA7134_BOARD_CINERGY_HT_PCI 108 +#define SAA7134_BOARD_PHILIPS_TIGER_S 109 +#define SAA7134_BOARD_AVERMEDIA_M102 110 +#define SAA7134_BOARD_ASUS_P7131_4871 111 +#define SAA7134_BOARD_ASUSTeK_P7131_HYBRID_LNA 112 #define SAA7134_MAXBOARDS 8 #define SAA7134_INPUT_MAX 8 @@ -280,6 +284,7 @@ struct saa7134_board { unsigned char radio_addr; unsigned int tda9887_conf; + unsigned int tuner_config; /* peripheral I/O */ enum saa7134_video_out video_out; @@ -435,6 +440,8 @@ struct saa7134_dev { #ifdef VIDIOC_G_PRIORITY struct v4l2_prio_state prio; #endif + /* workstruct for loading modules */ + struct work_struct request_module_wk; /* insmod option/autodetected */ int autodetected; @@ -562,6 +569,8 @@ extern struct list_head saa7134_devlist; extern int saa7134_no_overlay; void saa7134_track_gpio(struct saa7134_dev *dev, char *msg); +void saa7134_set_gpio(struct saa7134_dev *dev, int bit_no, int value); +int saa7134_tuner_callback(void *ptr, int command, int arg); #define SAA7134_PGTABLE_SIZE 4096 @@ -620,7 +629,6 @@ int saa7134_common_ioctl(struct saa7134_dev *dev, int saa7134_video_init1(struct saa7134_dev *dev); int saa7134_video_init2(struct saa7134_dev *dev); -int saa7134_video_fini(struct saa7134_dev *dev); void saa7134_irq_video_intl(struct saa7134_dev *dev); void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status); diff --git a/drivers/media/video/se401.c b/drivers/media/video/se401.c index 038448f5a97..93fb04ed99a 100644 --- a/drivers/media/video/se401.c +++ b/drivers/media/video/se401.c @@ -450,6 +450,13 @@ static int se401_start_stream(struct usb_se401 *se401) } for (i=0; i<SE401_NUMSBUF; i++) { se401->sbuf[i].data=kmalloc(SE401_PACKETSIZE, GFP_KERNEL); + if (!se401->sbuf[i].data) { + for(i = i - 1; i >= 0; i--) { + kfree(se401->sbuf[i].data); + se401->sbuf[i].data = NULL; + } + return -ENOMEM; + } } se401->bayeroffset=0; @@ -458,13 +465,26 @@ static int se401_start_stream(struct usb_se401 *se401) se401->scratch_overflow=0; for (i=0; i<SE401_NUMSCRATCH; i++) { se401->scratch[i].data=kmalloc(SE401_PACKETSIZE, GFP_KERNEL); + if (!se401->scratch[i].data) { + for(i = i - 1; i >= 0; i--) { + kfree(se401->scratch[i].data); + se401->scratch[i].data = NULL; + } + goto nomem_sbuf; + } se401->scratch[i].state=BUFFER_UNUSED; } for (i=0; i<SE401_NUMSBUF; i++) { urb=usb_alloc_urb(0, GFP_KERNEL); - if(!urb) - return -ENOMEM; + if(!urb) { + for(i = i - 1; i >= 0; i--) { + usb_kill_urb(se401->urb[i]); + usb_free_urb(se401->urb[i]); + se401->urb[i] = NULL; + } + goto nomem_scratch; + } usb_fill_bulk_urb(urb, se401->dev, usb_rcvbulkpipe(se401->dev, SE401_VIDEO_ENDPOINT), @@ -482,6 +502,18 @@ static int se401_start_stream(struct usb_se401 *se401) se401->framecount=0; return 0; + + nomem_scratch: + for (i=0; i<SE401_NUMSCRATCH; i++) { + kfree(se401->scratch[i].data); + se401->scratch[i].data = NULL; + } + nomem_sbuf: + for (i=0; i<SE401_NUMSBUF; i++) { + kfree(se401->sbuf[i].data); + se401->sbuf[i].data = NULL; + } + return -ENOMEM; } static int se401_stop_stream(struct usb_se401 *se401) diff --git a/drivers/media/video/sn9c102/Kconfig b/drivers/media/video/sn9c102/Kconfig index 1a7ccb666ab..19204f5686e 100644 --- a/drivers/media/video/sn9c102/Kconfig +++ b/drivers/media/video/sn9c102/Kconfig @@ -1,6 +1,6 @@ config USB_SN9C102 tristate "USB SN9C1xx PC Camera Controller support" - depends on USB && VIDEO_V4L1 + depends on USB && VIDEO_V4L2 ---help--- Say Y here if you want support for cameras based on SONiX SN9C101, SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers. diff --git a/drivers/media/video/sn9c102/Makefile b/drivers/media/video/sn9c102/Makefile index 30e3dfe537f..a56d16f69c7 100644 --- a/drivers/media/video/sn9c102/Makefile +++ b/drivers/media/video/sn9c102/Makefile @@ -1,7 +1,14 @@ -sn9c102-objs := sn9c102_core.o sn9c102_hv7131d.o sn9c102_mi0343.o \ - sn9c102_ov7630.o sn9c102_ov7660.o sn9c102_pas106b.o \ - sn9c102_pas202bcb.o sn9c102_tas5110c1b.o \ - sn9c102_tas5130d1b.o +sn9c102-objs := sn9c102_core.o \ + sn9c102_hv7131d.o \ + sn9c102_hv7131r.o \ + sn9c102_mi0343.o \ + sn9c102_mi0360.o \ + sn9c102_ov7630.o \ + sn9c102_ov7660.o \ + sn9c102_pas106b.o \ + sn9c102_pas202bcb.o \ + sn9c102_tas5110c1b.o \ + sn9c102_tas5110d.o \ + sn9c102_tas5130d1b.o obj-$(CONFIG_USB_SN9C102) += sn9c102.o - diff --git a/drivers/media/video/sn9c102/sn9c102.h b/drivers/media/video/sn9c102/sn9c102.h index 5428f34e7c5..680e7463452 100644 --- a/drivers/media/video/sn9c102/sn9c102.h +++ b/drivers/media/video/sn9c102/sn9c102.h @@ -78,8 +78,13 @@ enum sn9c102_stream_state { typedef char sn9c102_sof_header_t[62]; +struct sn9c102_sof_t { + sn9c102_sof_header_t header; + u16 bytesread; +}; + struct sn9c102_sysfs_attr { - u8 reg, i2c_reg; + u16 reg, i2c_reg; sn9c102_sof_header_t frame_header; }; @@ -112,7 +117,7 @@ struct sn9c102_device { struct v4l2_jpegcompression compression; struct sn9c102_sysfs_attr sysfs; - sn9c102_sof_header_t sof_header; + struct sn9c102_sof_t sof; u16 reg[384]; struct sn9c102_module_param module_param; @@ -182,8 +187,8 @@ do { \ if ((level) == 1 || (level) == 2) \ pr_info("sn9c102: " fmt "\n", ## args); \ else if ((level) == 3) \ - pr_debug("sn9c102: [%s:%d] " fmt "\n", __FUNCTION__, \ - __LINE__ , ## args); \ + pr_debug("sn9c102: [%s:%d] " fmt "\n", \ + __FUNCTION__, __LINE__ , ## args); \ } \ } while (0) #else @@ -194,8 +199,8 @@ do { \ #undef PDBG #define PDBG(fmt, args...) \ -dev_info(&cam->usbdev->dev, "[%s:%d] " fmt "\n", \ - __FUNCTION__, __LINE__ , ## args) +dev_info(&cam->usbdev->dev, "[%s:%s:%d] " fmt "\n", __FILE__, __FUNCTION__, \ + __LINE__ , ## args) #undef PDBGG #define PDBGG(fmt, args...) do {;} while(0) /* placeholder */ diff --git a/drivers/media/video/sn9c102/sn9c102_core.c b/drivers/media/video/sn9c102/sn9c102_core.c index d0e2b40a772..89f83354de3 100644 --- a/drivers/media/video/sn9c102/sn9c102_core.c +++ b/drivers/media/video/sn9c102/sn9c102_core.c @@ -44,11 +44,12 @@ /*****************************************************************************/ #define SN9C102_MODULE_NAME "V4L2 driver for SN9C1xx PC Camera Controllers" -#define SN9C102_MODULE_AUTHOR "(C) 2004-2006 Luca Risolia" +#define SN9C102_MODULE_ALIAS "sn9c1xx" +#define SN9C102_MODULE_AUTHOR "(C) 2004-2007 Luca Risolia" #define SN9C102_AUTHOR_EMAIL "<luca.risolia@studio.unibo.it>" #define SN9C102_MODULE_LICENSE "GPL" -#define SN9C102_MODULE_VERSION "1:1.34" -#define SN9C102_MODULE_VERSION_CODE KERNEL_VERSION(1, 1, 34) +#define SN9C102_MODULE_VERSION "1:1.39" +#define SN9C102_MODULE_VERSION_CODE KERNEL_VERSION(1, 1, 39) /*****************************************************************************/ @@ -56,6 +57,7 @@ MODULE_DEVICE_TABLE(usb, sn9c102_id_table); MODULE_AUTHOR(SN9C102_MODULE_AUTHOR " " SN9C102_AUTHOR_EMAIL); MODULE_DESCRIPTION(SN9C102_MODULE_NAME); +MODULE_ALIAS(SN9C102_MODULE_ALIAS); MODULE_VERSION(SN9C102_MODULE_VERSION); MODULE_LICENSE(SN9C102_MODULE_LICENSE); @@ -106,8 +108,7 @@ MODULE_PARM_DESC(debug, "\n1 = critical errors" "\n2 = significant informations" "\n3 = more verbose messages" - "\nLevel 3 is useful for testing only, when only " - "one device is used." + "\nLevel 3 is useful for testing only." "\nDefault value is "__MODULE_STRING(SN9C102_DEBUG_LEVEL)"." "\n"); #endif @@ -121,8 +122,8 @@ sn9c102_request_buffers(struct sn9c102_device* cam, u32 count, struct v4l2_pix_format* p = &(cam->sensor.pix_format); struct v4l2_rect* r = &(cam->sensor.cropcap.bounds); size_t imagesize = cam->module_param.force_munmap || io == IO_READ ? - (p->width * p->height * p->priv) / 8 : - (r->width * r->height * p->priv) / 8; + (p->width * p->height * p->priv) / 8 : + (r->width * r->height * p->priv) / 8; void* buff = NULL; u32 i; @@ -208,27 +209,40 @@ static void sn9c102_queue_unusedframes(struct sn9c102_device* cam) } /*****************************************************************************/ - -int sn9c102_write_regs(struct sn9c102_device* cam, u8* buff, u16 index) +/* + * Write a sequence of count value/register pairs. Returns -1 after the + * first failed write, or 0 for no errors. + */ +int sn9c102_write_regs(struct sn9c102_device* cam, const u8 valreg[][2], + int count) { struct usb_device* udev = cam->usbdev; + u8* value = cam->control_buffer; /* Needed for DMA'able memory */ int i, res; - if (index + sizeof(buff) >= ARRAY_SIZE(cam->reg)) - return -1; + for (i = 0; i < count; i++) { + u8 index = valreg[i][1]; + + /* + * index is a u8, so it must be <256 and can't be out of range. + * If we put in a check anyway, gcc annoys us with a warning + * that our check is useless. People get all uppity when they + * see warnings in the kernel compile. + */ + + *value = valreg[i][0]; + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x08, 0x41, index, 0, + value, 1, SN9C102_CTRL_TIMEOUT); + if (res < 0) { + DBG(3, "Failed to write a register (value 0x%02X, " + "index 0x%02X, error %d)", *value, index, res); + return -1; + } - res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41, - index, 0, buff, sizeof(buff), - SN9C102_CTRL_TIMEOUT*sizeof(buff)); - if (res < 0) { - DBG(3, "Failed to write registers (index 0x%02X, error %d)", - index, res); - return -1; + cam->reg[index] = *value; } - for (i = 0; i < sizeof(buff); i++) - cam->reg[index+i] = buff[i]; - return 0; } @@ -485,18 +499,43 @@ static size_t sn9c102_sof_length(struct sn9c102_device* cam) static void* sn9c102_find_sof_header(struct sn9c102_device* cam, void* mem, size_t len) { - char sof_header[6] = {0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96}; - size_t soflen = 0, i; + static const char marker[6] = {0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96}; + const char *m = mem; + size_t soflen = 0, i, j; soflen = sn9c102_sof_length(cam); - for (i = 0; (len >= soflen) && (i <= len - soflen); i++) - if (!memcmp(mem + i, sof_header, sizeof(sof_header))) { - memcpy(cam->sof_header, mem + i, - sizeof(sn9c102_sof_header_t)); - /* Skip the header */ - return mem + i + soflen; + for (i = 0; i < len; i++) { + size_t b; + + /* Read the variable part of the header */ + if (unlikely(cam->sof.bytesread >= sizeof(marker))) { + cam->sof.header[cam->sof.bytesread] = *(m+i); + if (++cam->sof.bytesread == soflen) { + cam->sof.bytesread = 0; + return mem + i; + } + continue; + } + + /* Search for the SOF marker (fixed part) in the header */ + for (j = 0, b=cam->sof.bytesread; j+b < sizeof(marker); j++) { + if (unlikely(i+j) == len) + return NULL; + if (*(m+i+j) == marker[cam->sof.bytesread]) { + cam->sof.header[cam->sof.bytesread] = *(m+i+j); + if (++cam->sof.bytesread == sizeof(marker)) { + PDBGG("Bytes to analyze: %zd. SOF " + "starts at byte #%zd", len, i); + i += j+1; + break; + } + } else { + cam->sof.bytesread = 0; + break; } + } + } return NULL; } @@ -505,7 +544,7 @@ sn9c102_find_sof_header(struct sn9c102_device* cam, void* mem, size_t len) static void* sn9c102_find_eof_header(struct sn9c102_device* cam, void* mem, size_t len) { - char eof_header[4][4] = { + static const u8 eof_header[4][4] = { {0x00, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x00, 0x00}, {0x80, 0x00, 0x00, 0x00}, @@ -513,10 +552,16 @@ sn9c102_find_eof_header(struct sn9c102_device* cam, void* mem, size_t len) }; size_t i, j; + /* The EOF header does not exist in compressed data */ if (cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X || cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_JPEG) - return NULL; /* EOF header does not exist in compressed data */ + return NULL; + /* + The EOF header might cross the packet boundary, but this is not a + problem, since the end of a frame is determined by checking its size + in the first place. + */ for (i = 0; (len >= 4) && (i <= len - 4); i++) for (j = 0; j < ARRAY_SIZE(eof_header); j++) if (!memcmp(mem + i, eof_header[j], 4)) @@ -529,7 +574,7 @@ sn9c102_find_eof_header(struct sn9c102_device* cam, void* mem, size_t len) static void sn9c102_write_jpegheader(struct sn9c102_device* cam, struct sn9c102_frame_t* f) { - static u8 jpeg_header[589] = { + static const u8 jpeg_header[589] = { 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x06, 0x04, 0x05, 0x06, 0x05, 0x04, 0x06, 0x06, 0x05, 0x06, 0x07, 0x07, 0x06, 0x08, 0x0a, 0x10, 0x0a, 0x0a, 0x09, 0x09, 0x0a, 0x14, 0x0e, @@ -639,6 +684,7 @@ static void sn9c102_urb_complete(struct urb *urb) cam->stream = STREAM_OFF; if ((*f)) (*f)->state = F_QUEUED; + cam->sof.bytesread = 0; DBG(3, "Stream interrupted by application"); wake_up(&cam->wait_stream); } @@ -676,6 +722,7 @@ static void sn9c102_urb_complete(struct urb *urb) if (status) { DBG(3, "Error in isochronous frame"); (*f)->state = F_ERROR; + cam->sof.bytesread = 0; continue; } @@ -692,13 +739,13 @@ end_of_frame: if (eof) img = (eof > pos) ? eof - pos - 1 : 0; - if ((*f)->buf.bytesused+img > imagesize) { + if ((*f)->buf.bytesused + img > imagesize) { u32 b; b = (*f)->buf.bytesused + img - imagesize; img = imagesize - (*f)->buf.bytesused; - DBG(3, "Expected EOF not found: " - "video frame cut"); + PDBGG("Expected EOF not found: video " + "frame cut"); if (eof) DBG(3, "Exceeded limit: +%u " "bytes", (unsigned)(b)); @@ -719,11 +766,6 @@ end_of_frame: V4L2_PIX_FMT_JPEG) && eof)) { u32 b; - if (cam->sensor.pix_format.pixelformat - == V4L2_PIX_FMT_JPEG) - sn9c102_write_eoimarker(cam, - (*f)); - b = (*f)->buf.bytesused; (*f)->state = F_DONE; (*f)->buf.sequence= ++cam->frame_count; @@ -741,7 +783,7 @@ end_of_frame: spin_unlock(&cam->queue_lock); memcpy(cam->sysfs.frame_header, - cam->sof_header, soflen); + cam->sof.header, soflen); DBG(3, "Video frame captured: %lu " "bytes", (unsigned long)(b)); @@ -791,7 +833,13 @@ start_of_frame: V4L2_PIX_FMT_SN9C10X || cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_JPEG) { - eof = sof - soflen; + if (sof - pos >= soflen) { + eof = sof - soflen; + } else { /* remove header */ + eof = pos; + (*f)->buf.bytesused -= + (soflen - (sof - pos)); + } goto end_of_frame; } else { DBG(3, "SOF before expected EOF after " @@ -878,6 +926,7 @@ static int sn9c102_start_transfer(struct sn9c102_device* cam) } cam->frame_current = NULL; + cam->sof.bytesread = 0; for (i = 0; i < SN9C102_URBS; i++) { err = usb_submit_urb(cam->urb[i], GFP_KERNEL); @@ -959,9 +1008,9 @@ static u16 sn9c102_strtou16(const char* buff, size_t len, ssize_t* count) if (len < 6) { strncpy(str, buff, len); - str[len+1] = '\0'; + str[len] = '\0'; } else { - strncpy(str, buff, 4); + strncpy(str, buff, 6); str[6] = '\0'; } @@ -1062,7 +1111,7 @@ static ssize_t sn9c102_show_val(struct class_device* cd, char* buf) count = sprintf(buf, "%d\n", val); - DBG(3, "Read bytes: %zd", count); + DBG(3, "Read bytes: %zd, value: %d", count, val); mutex_unlock(&sn9c102_sysfs_lock); @@ -1197,7 +1246,7 @@ static ssize_t sn9c102_show_i2c_val(struct class_device* cd, char* buf) count = sprintf(buf, "%d\n", val); - DBG(3, "Read bytes: %zd", count); + DBG(3, "Read bytes: %zd, value: %d", count, val); mutex_unlock(&sn9c102_sysfs_lock); @@ -1371,35 +1420,35 @@ static CLASS_DEVICE_ATTR(frame_header, S_IRUGO, static int sn9c102_create_sysfs(struct sn9c102_device* cam) { - struct video_device *v4ldev = cam->v4ldev; + struct class_device *classdev = &(cam->v4ldev->class_dev); int err = 0; - if ((err = video_device_create_file(v4ldev, &class_device_attr_reg))) + if ((err = class_device_create_file(classdev, &class_device_attr_reg))) goto err_out; - if ((err = video_device_create_file(v4ldev, &class_device_attr_val))) + if ((err = class_device_create_file(classdev, &class_device_attr_val))) goto err_reg; - if ((err = video_device_create_file(v4ldev, + if ((err = class_device_create_file(classdev, &class_device_attr_frame_header))) goto err_val; if (cam->sensor.sysfs_ops) { - if ((err = video_device_create_file(v4ldev, + if ((err = class_device_create_file(classdev, &class_device_attr_i2c_reg))) goto err_frame_header; - if ((err = video_device_create_file(v4ldev, + if ((err = class_device_create_file(classdev, &class_device_attr_i2c_val))) goto err_i2c_reg; } if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) { - if ((err = video_device_create_file(v4ldev, + if ((err = class_device_create_file(classdev, &class_device_attr_green))) goto err_i2c_val; } else { - if ((err = video_device_create_file(v4ldev, + if ((err = class_device_create_file(classdev, &class_device_attr_blue))) goto err_i2c_val; - if ((err = video_device_create_file(v4ldev, + if ((err = class_device_create_file(classdev, &class_device_attr_red))) goto err_blue; } @@ -1407,19 +1456,19 @@ static int sn9c102_create_sysfs(struct sn9c102_device* cam) return 0; err_blue: - video_device_remove_file(v4ldev, &class_device_attr_blue); + class_device_remove_file(classdev, &class_device_attr_blue); err_i2c_val: if (cam->sensor.sysfs_ops) - video_device_remove_file(v4ldev, &class_device_attr_i2c_val); + class_device_remove_file(classdev, &class_device_attr_i2c_val); err_i2c_reg: if (cam->sensor.sysfs_ops) - video_device_remove_file(v4ldev, &class_device_attr_i2c_reg); + class_device_remove_file(classdev, &class_device_attr_i2c_reg); err_frame_header: - video_device_remove_file(v4ldev, &class_device_attr_frame_header); + class_device_remove_file(classdev, &class_device_attr_frame_header); err_val: - video_device_remove_file(v4ldev, &class_device_attr_val); + class_device_remove_file(classdev, &class_device_attr_val); err_reg: - video_device_remove_file(v4ldev, &class_device_attr_reg); + class_device_remove_file(classdev, &class_device_attr_reg); err_out: return err; } @@ -1477,10 +1526,10 @@ sn9c102_set_compression(struct sn9c102_device* cam, case BRIDGE_SN9C101: case BRIDGE_SN9C102: case BRIDGE_SN9C103: - if (compression->quality == 0) + if (compression->quality == 0) err += sn9c102_write_reg(cam, cam->reg[0x17] | 0x01, 0x17); - else if (compression->quality == 1) + else if (compression->quality == 1) err += sn9c102_write_reg(cam, cam->reg[0x17] & 0xfe, 0x17); break; @@ -1489,10 +1538,10 @@ sn9c102_set_compression(struct sn9c102_device* cam, if (compression->quality == 0) { for (i = 0; i <= 63; i++) { err += sn9c102_write_reg(cam, - SN9C102_Y_QTABLE0[i], + SN9C102_Y_QTABLE1[i], 0x100 + i); err += sn9c102_write_reg(cam, - SN9C102_UV_QTABLE0[i], + SN9C102_UV_QTABLE1[i], 0x140 + i); } err += sn9c102_write_reg(cam, cam->reg[0x18] & 0xbf, @@ -1597,9 +1646,13 @@ static int sn9c102_init(struct sn9c102_device* cam) if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102 || cam->bridge == BRIDGE_SN9C103) { + if (s->pix_format.pixelformat == V4L2_PIX_FMT_JPEG) + s->pix_format.pixelformat= V4L2_PIX_FMT_SBGGR8; cam->compression.quality = cam->reg[0x17] & 0x01 ? 0 : 1; } else { + if (s->pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X) + s->pix_format.pixelformat = V4L2_PIX_FMT_JPEG; cam->compression.quality = cam->reg[0x18] & 0x40 ? 0 : 1; err += sn9c102_set_compression(cam, &cam->compression); @@ -1805,7 +1858,7 @@ sn9c102_read(struct file* filp, char __user * buf, size_t count, loff_t* f_pos) DBG(3, "Close and open the device again to choose " "the read method"); mutex_unlock(&cam->fileop_mutex); - return -EINVAL; + return -EBUSY; } if (cam->io == IO_NONE) { @@ -1845,16 +1898,16 @@ sn9c102_read(struct file* filp, char __user * buf, size_t count, loff_t* f_pos) return err; } } else { - timeout = wait_event_interruptible_timeout - ( cam->wait_frame, - (!list_empty(&cam->outqueue)) || - (cam->state & DEV_DISCONNECTED) || - (cam->state & DEV_MISCONFIGURED), - cam->module_param.frame_timeout * - 1000 * msecs_to_jiffies(1) ); - if (timeout < 0) { - mutex_unlock(&cam->fileop_mutex); - return timeout; + timeout = wait_event_interruptible_timeout + ( cam->wait_frame, + (!list_empty(&cam->outqueue)) || + (cam->state & DEV_DISCONNECTED) || + (cam->state & DEV_MISCONFIGURED), + cam->module_param.frame_timeout * + 1000 * msecs_to_jiffies(1) ); + if (timeout < 0) { + mutex_unlock(&cam->fileop_mutex); + return timeout; } else if (timeout == 0 && !(cam->state & DEV_DISCONNECTED)) { DBG(1, "Video frame timeout elapsed"); @@ -2001,7 +2054,12 @@ static int sn9c102_mmap(struct file* filp, struct vm_area_struct *vma) return -EIO; } - if (cam->io != IO_MMAP || !(vma->vm_flags & VM_WRITE) || + if (!(vma->vm_flags & (VM_WRITE | VM_READ))) { + mutex_unlock(&cam->fileop_mutex); + return -EACCES; + } + + if (cam->io != IO_MMAP || size != PAGE_ALIGN(cam->frame[0].buf.length)) { mutex_unlock(&cam->fileop_mutex); return -EINVAL; @@ -2267,7 +2325,7 @@ sn9c102_vidioc_s_crop(struct sn9c102_device* cam, void __user * arg) if (cam->frame[i].vma_use_count) { DBG(3, "VIDIOC_S_CROP failed. " "Unmap the buffers first."); - return -EINVAL; + return -EBUSY; } /* Preserve R,G or B origin */ @@ -2410,8 +2468,8 @@ sn9c102_vidioc_enum_fmt(struct sn9c102_device* cam, void __user * arg) case BRIDGE_SN9C101: case BRIDGE_SN9C102: case BRIDGE_SN9C103: - strcpy(fmtd.description, "compressed"); - fmtd.pixelformat = V4L2_PIX_FMT_SN9C10X; + strcpy(fmtd.description, "compressed"); + fmtd.pixelformat = V4L2_PIX_FMT_SN9C10X; break; case BRIDGE_SN9C105: case BRIDGE_SN9C120: @@ -2445,8 +2503,10 @@ sn9c102_vidioc_g_fmt(struct sn9c102_device* cam, void __user * arg) if (format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; - pfmt->bytesperline = (pfmt->pixelformat==V4L2_PIX_FMT_SN9C10X || - pfmt->pixelformat==V4L2_PIX_FMT_JPEG) + pfmt->colorspace = (pfmt->pixelformat == V4L2_PIX_FMT_JPEG) ? + V4L2_COLORSPACE_JPEG : V4L2_COLORSPACE_SRGB; + pfmt->bytesperline = (pfmt->pixelformat == V4L2_PIX_FMT_SN9C10X || + pfmt->pixelformat == V4L2_PIX_FMT_JPEG) ? 0 : (pfmt->width * pfmt->priv) / 8; pfmt->sizeimage = pfmt->height * ((pfmt->width*pfmt->priv)/8); pfmt->field = V4L2_FIELD_NONE; @@ -2521,9 +2581,9 @@ sn9c102_vidioc_try_s_fmt(struct sn9c102_device* cam, unsigned int cmd, case BRIDGE_SN9C101: case BRIDGE_SN9C102: case BRIDGE_SN9C103: - if (pix->pixelformat != V4L2_PIX_FMT_SN9C10X && - pix->pixelformat != V4L2_PIX_FMT_SBGGR8) - pix->pixelformat = pfmt->pixelformat; + if (pix->pixelformat != V4L2_PIX_FMT_SN9C10X && + pix->pixelformat != V4L2_PIX_FMT_SBGGR8) + pix->pixelformat = pfmt->pixelformat; break; case BRIDGE_SN9C105: case BRIDGE_SN9C120: @@ -2533,7 +2593,8 @@ sn9c102_vidioc_try_s_fmt(struct sn9c102_device* cam, unsigned int cmd, break; } pix->priv = pfmt->priv; /* bpp */ - pix->colorspace = pfmt->colorspace; + pix->colorspace = (pix->pixelformat == V4L2_PIX_FMT_JPEG) ? + V4L2_COLORSPACE_JPEG : V4L2_COLORSPACE_SRGB; pix->bytesperline = (pix->pixelformat == V4L2_PIX_FMT_SN9C10X || pix->pixelformat == V4L2_PIX_FMT_JPEG) ? 0 : (pix->width * pix->priv) / 8; @@ -2551,7 +2612,7 @@ sn9c102_vidioc_try_s_fmt(struct sn9c102_device* cam, unsigned int cmd, if (cam->frame[i].vma_use_count) { DBG(3, "VIDIOC_S_FMT failed. Unmap the " "buffers first."); - return -EINVAL; + return -EBUSY; } if (cam->stream == STREAM_ON) @@ -2666,14 +2727,14 @@ sn9c102_vidioc_reqbufs(struct sn9c102_device* cam, void __user * arg) if (cam->io == IO_READ) { DBG(3, "Close and open the device again to choose the mmap " "I/O method"); - return -EINVAL; + return -EBUSY; } for (i = 0; i < cam->nbuffers; i++) if (cam->frame[i].vma_use_count) { DBG(3, "VIDIOC_REQBUFS failed. Previous buffers are " "still mapped."); - return -EINVAL; + return -EBUSY; } if (cam->stream == STREAM_ON) @@ -2785,15 +2846,15 @@ sn9c102_vidioc_dqbuf(struct sn9c102_device* cam, struct file* filp, if (err) return err; } else { - timeout = wait_event_interruptible_timeout - ( cam->wait_frame, - (!list_empty(&cam->outqueue)) || - (cam->state & DEV_DISCONNECTED) || - (cam->state & DEV_MISCONFIGURED), - cam->module_param.frame_timeout * - 1000 * msecs_to_jiffies(1) ); - if (timeout < 0) - return timeout; + timeout = wait_event_interruptible_timeout + ( cam->wait_frame, + (!list_empty(&cam->outqueue)) || + (cam->state & DEV_DISCONNECTED) || + (cam->state & DEV_MISCONFIGURED), + cam->module_param.frame_timeout * + 1000 * msecs_to_jiffies(1) ); + if (timeout < 0) + return timeout; else if (timeout == 0 && !(cam->state & DEV_DISCONNECTED)) { DBG(1, "Video frame timeout elapsed"); @@ -2837,9 +2898,6 @@ sn9c102_vidioc_streamon(struct sn9c102_device* cam, void __user * arg) if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP) return -EINVAL; - if (list_empty(&cam->inqueue)) - return -EINVAL; - cam->stream = STREAM_ON; DBG(3, "Stream on"); @@ -3166,8 +3224,8 @@ sn9c102_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) r = sn9c102_read_reg(cam, 0x00); if (r < 0 || (r != 0x10 && r != 0x11 && r != 0x12)) { - DBG(1, "Sorry, this is not a SN9C1xx based camera " - "(vid/pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + DBG(1, "Sorry, this is not a SN9C1xx-based camera " + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); err = -ENODEV; goto fail; } @@ -3177,19 +3235,19 @@ sn9c102_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) case BRIDGE_SN9C101: case BRIDGE_SN9C102: DBG(2, "SN9C10[12] PC Camera Controller detected " - "(vid/pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); break; case BRIDGE_SN9C103: DBG(2, "SN9C103 PC Camera Controller detected " - "(vid/pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); break; case BRIDGE_SN9C105: DBG(2, "SN9C105 PC Camera Controller detected " - "(vid/pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); break; case BRIDGE_SN9C120: DBG(2, "SN9C120 PC Camera Controller detected " - "(vid/pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); break; } @@ -3260,6 +3318,8 @@ sn9c102_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) "device controlling. Error #%d", err); #else DBG(2, "Optional device control through 'sysfs' interface disabled"); + DBG(3, "Compile the kernel with the 'CONFIG_VIDEO_ADV_DEBUG' " + "configuration option to enable it."); #endif usb_set_intfdata(intf, cam); diff --git a/drivers/media/video/sn9c102/sn9c102_devtable.h b/drivers/media/video/sn9c102/sn9c102_devtable.h index 3a682eca6c6..f49bd8c5b86 100644 --- a/drivers/media/video/sn9c102/sn9c102_devtable.h +++ b/drivers/media/video/sn9c102/sn9c102_devtable.h @@ -89,16 +89,22 @@ static const struct usb_device_id sn9c102_id_table[] = { { SN9C102_USB_DEVICE(0x0471, 0x0327, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0471, 0x0328, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60c0, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0c45, 0x60c2, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60c8, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60cc, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60ea, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60ec, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0c45, 0x60ef, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60fa, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60fb, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60fc, BRIDGE_SN9C105), }, { SN9C102_USB_DEVICE(0x0c45, 0x60fe, BRIDGE_SN9C105), }, /* SN9C120 */ + { SN9C102_USB_DEVICE(0x0c45, 0x6102, BRIDGE_SN9C120), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6108, BRIDGE_SN9C120), }, + { SN9C102_USB_DEVICE(0x0c45, 0x610f, BRIDGE_SN9C120), }, { SN9C102_USB_DEVICE(0x0c45, 0x6130, BRIDGE_SN9C120), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6138, BRIDGE_SN9C120), }, { SN9C102_USB_DEVICE(0x0c45, 0x613a, BRIDGE_SN9C120), }, { SN9C102_USB_DEVICE(0x0c45, 0x613b, BRIDGE_SN9C120), }, { SN9C102_USB_DEVICE(0x0c45, 0x613c, BRIDGE_SN9C120), }, @@ -114,12 +120,15 @@ static const struct usb_device_id sn9c102_id_table[] = { Functions must return 0 on success, the appropriate error otherwise. */ extern int sn9c102_probe_hv7131d(struct sn9c102_device* cam); +extern int sn9c102_probe_hv7131r(struct sn9c102_device* cam); extern int sn9c102_probe_mi0343(struct sn9c102_device* cam); +extern int sn9c102_probe_mi0360(struct sn9c102_device* cam); extern int sn9c102_probe_ov7630(struct sn9c102_device* cam); extern int sn9c102_probe_ov7660(struct sn9c102_device* cam); extern int sn9c102_probe_pas106b(struct sn9c102_device* cam); extern int sn9c102_probe_pas202bcb(struct sn9c102_device* cam); extern int sn9c102_probe_tas5110c1b(struct sn9c102_device* cam); +extern int sn9c102_probe_tas5110d(struct sn9c102_device* cam); extern int sn9c102_probe_tas5130d1b(struct sn9c102_device* cam); /* @@ -128,13 +137,16 @@ extern int sn9c102_probe_tas5130d1b(struct sn9c102_device* cam); the order of the list below, from top to bottom. */ static int (*sn9c102_sensor_table[])(struct sn9c102_device*) = { + &sn9c102_probe_hv7131d, /* strong detection based on SENSOR ids */ + &sn9c102_probe_hv7131r, /* strong detection based on SENSOR ids */ &sn9c102_probe_mi0343, /* strong detection based on SENSOR ids */ + &sn9c102_probe_mi0360, /* strong detection based on SENSOR ids */ &sn9c102_probe_pas106b, /* strong detection based on SENSOR ids */ &sn9c102_probe_pas202bcb, /* strong detection based on SENSOR ids */ - &sn9c102_probe_hv7131d, /* strong detection based on SENSOR ids */ &sn9c102_probe_ov7630, /* strong detection based on SENSOR ids */ &sn9c102_probe_ov7660, /* strong detection based on SENSOR ids */ &sn9c102_probe_tas5110c1b, /* detection based on USB pid/vid */ + &sn9c102_probe_tas5110d, /* detection based on USB pid/vid */ &sn9c102_probe_tas5130d1b, /* detection based on USB pid/vid */ NULL, }; diff --git a/drivers/media/video/sn9c102/sn9c102_hv7131d.c b/drivers/media/video/sn9c102/sn9c102_hv7131d.c index 7ae368f60d8..28a861aed04 100644 --- a/drivers/media/video/sn9c102/sn9c102_hv7131d.c +++ b/drivers/media/video/sn9c102/sn9c102_hv7131d.c @@ -22,19 +22,13 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor hv7131d; - - static int hv7131d_init(struct sn9c102_device* cam) { - int err = 0; + int err; - err += sn9c102_write_reg(cam, 0x00, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x00, 0x14); - err += sn9c102_write_reg(cam, 0x60, 0x17); - err += sn9c102_write_reg(cam, 0x0e, 0x18); - err += sn9c102_write_reg(cam, 0xf2, 0x19); + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x00, 0x14}, {0x60, 0x17}, + {0x0e, 0x18}, {0xf2, 0x19}); err += sn9c102_i2c_write(cam, 0x01, 0x04); err += sn9c102_i2c_write(cam, 0x02, 0x00); @@ -153,7 +147,7 @@ static int hv7131d_set_pix_format(struct sn9c102_device* cam, static struct sn9c102_sensor hv7131d = { .name = "HV7131D", .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", - .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103, + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_2WIRES, @@ -250,11 +244,10 @@ static struct sn9c102_sensor hv7131d = { int sn9c102_probe_hv7131d(struct sn9c102_device* cam) { - int r0 = 0, r1 = 0, err = 0; + int r0 = 0, r1 = 0, err; - err += sn9c102_write_reg(cam, 0x01, 0x01); - err += sn9c102_write_reg(cam, 0x00, 0x01); - err += sn9c102_write_reg(cam, 0x28, 0x17); + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); if (err) return -EIO; @@ -263,7 +256,7 @@ int sn9c102_probe_hv7131d(struct sn9c102_device* cam) if (r0 < 0 || r1 < 0) return -EIO; - if (r0 != 0x00 && r1 != 0x04) + if (r0 != 0x00 || r1 != 0x04) return -ENODEV; sn9c102_attach_sensor(cam, &hv7131d); diff --git a/drivers/media/video/sn9c102/sn9c102_hv7131r.c b/drivers/media/video/sn9c102/sn9c102_hv7131r.c new file mode 100644 index 00000000000..5a495baa5f9 --- /dev/null +++ b/drivers/media/video/sn9c102/sn9c102_hv7131r.c @@ -0,0 +1,366 @@ +/*************************************************************************** + * Plug-in for HV7131R image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * 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 "sn9c102_sensor.h" + + +static int hv7131r_init(struct sn9c102_device* cam) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + err = sn9c102_write_const_regs(cam, {0x00, 0x03}, {0x1a, 0x04}, + {0x20, 0x05}, {0x20, 0x06}, + {0x03, 0x10}, {0x00, 0x14}, + {0x60, 0x17}, {0x0a, 0x18}, + {0xf0, 0x19}, {0x1d, 0x1a}, + {0x10, 0x1b}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); + + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02}, + {0x00, 0x03}, {0x1a, 0x04}, + {0x44, 0x05}, {0x3e, 0x06}, + {0x1a, 0x07}, {0x03, 0x10}, + {0x08, 0x14}, {0xa3, 0x17}, + {0x4b, 0x18}, {0x00, 0x19}, + {0x1d, 0x1a}, {0x10, 0x1b}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x29, 0x21}, + {0x40, 0x22}, {0x54, 0x23}, + {0x66, 0x24}, {0x76, 0x25}, + {0x85, 0x26}, {0x94, 0x27}, + {0xa1, 0x28}, {0xae, 0x29}, + {0xbb, 0x2a}, {0xc7, 0x2b}, + {0xd3, 0x2c}, {0xde, 0x2d}, + {0xea, 0x2e}, {0xf4, 0x2f}, + {0xff, 0x30}, {0x00, 0x3F}, + {0xC7, 0x40}, {0x01, 0x41}, + {0x44, 0x42}, {0x00, 0x43}, + {0x44, 0x44}, {0x00, 0x45}, + {0x44, 0x46}, {0x00, 0x47}, + {0xC7, 0x48}, {0x01, 0x49}, + {0xC7, 0x4A}, {0x01, 0x4B}, + {0xC7, 0x4C}, {0x01, 0x4D}, + {0x44, 0x4E}, {0x00, 0x4F}, + {0x44, 0x50}, {0x00, 0x51}, + {0x44, 0x52}, {0x00, 0x53}, + {0xC7, 0x54}, {0x01, 0x55}, + {0xC7, 0x56}, {0x01, 0x57}, + {0xC7, 0x58}, {0x01, 0x59}, + {0x44, 0x5A}, {0x00, 0x5B}, + {0x44, 0x5C}, {0x00, 0x5D}, + {0x44, 0x5E}, {0x00, 0x5F}, + {0xC7, 0x60}, {0x01, 0x61}, + {0xC7, 0x62}, {0x01, 0x63}, + {0xC7, 0x64}, {0x01, 0x65}, + {0x44, 0x66}, {0x00, 0x67}, + {0x44, 0x68}, {0x00, 0x69}, + {0x44, 0x6A}, {0x00, 0x6B}, + {0xC7, 0x6C}, {0x01, 0x6D}, + {0xC7, 0x6E}, {0x01, 0x6F}, + {0xC7, 0x70}, {0x01, 0x71}, + {0x44, 0x72}, {0x00, 0x73}, + {0x44, 0x74}, {0x00, 0x75}, + {0x44, 0x76}, {0x00, 0x77}, + {0xC7, 0x78}, {0x01, 0x79}, + {0xC7, 0x7A}, {0x01, 0x7B}, + {0xC7, 0x7C}, {0x01, 0x7D}, + {0x44, 0x7E}, {0x00, 0x7F}, + {0x14, 0x84}, {0x00, 0x85}, + {0x27, 0x86}, {0x00, 0x87}, + {0x07, 0x88}, {0x00, 0x89}, + {0xEC, 0x8A}, {0x0f, 0x8B}, + {0xD8, 0x8C}, {0x0f, 0x8D}, + {0x3D, 0x8E}, {0x00, 0x8F}, + {0x3D, 0x90}, {0x00, 0x91}, + {0xCD, 0x92}, {0x0f, 0x93}, + {0xf7, 0x94}, {0x0f, 0x95}, + {0x0C, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x66, 0x99}, + {0x05, 0x9A}, {0x00, 0x9B}, + {0x04, 0x9C}, {0x00, 0x9D}, + {0x08, 0x9E}, {0x00, 0x9F}, + {0x2D, 0xC0}, {0x2D, 0xC1}, + {0x3A, 0xC2}, {0x05, 0xC3}, + {0x04, 0xC4}, {0x3F, 0xC5}, + {0x00, 0xC6}, {0x00, 0xC7}, + {0x50, 0xC8}, {0x3C, 0xC9}, + {0x28, 0xCA}, {0xD8, 0xCB}, + {0x14, 0xCC}, {0xEC, 0xCD}, + {0x32, 0xCE}, {0xDD, 0xCF}, + {0x32, 0xD0}, {0xDD, 0xD1}, + {0x6A, 0xD2}, {0x50, 0xD3}, + {0x00, 0xD4}, {0x00, 0xD5}, + {0x00, 0xD6}); + break; + default: + break; + } + + err += sn9c102_i2c_write(cam, 0x20, 0x00); + err += sn9c102_i2c_write(cam, 0x21, 0xd6); + err += sn9c102_i2c_write(cam, 0x25, 0x06); + + return err; +} + + +static int hv7131r_get_ctrl(struct sn9c102_device* cam, + struct v4l2_control* ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_GAIN: + if ((ctrl->value = sn9c102_i2c_read(cam, 0x30)) < 0) + return -EIO; + return 0; + case V4L2_CID_RED_BALANCE: + if ((ctrl->value = sn9c102_i2c_read(cam, 0x31)) < 0) + return -EIO; + ctrl->value = ctrl->value & 0x3f; + return 0; + case V4L2_CID_BLUE_BALANCE: + if ((ctrl->value = sn9c102_i2c_read(cam, 0x33)) < 0) + return -EIO; + ctrl->value = ctrl->value & 0x3f; + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + if ((ctrl->value = sn9c102_i2c_read(cam, 0x32)) < 0) + return -EIO; + ctrl->value = ctrl->value & 0x3f; + return 0; + case V4L2_CID_BLACK_LEVEL: + if ((ctrl->value = sn9c102_i2c_read(cam, 0x01)) < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x08) ? 1 : 0; + return 0; + default: + return -EINVAL; + } +} + + +static int hv7131r_set_ctrl(struct sn9c102_device* cam, + const struct v4l2_control* ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x30, ctrl->value); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_write(cam, 0x31, ctrl->value); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_write(cam, 0x33, ctrl->value); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_write(cam, 0x32, ctrl->value); + break; + case V4L2_CID_BLACK_LEVEL: + { + int r = sn9c102_i2c_read(cam, 0x01); + if (r < 0) + return -EIO; + err += sn9c102_i2c_write(cam, 0x01, + (ctrl->value<<3) | (r&0xf7)); + } + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int hv7131r_set_crop(struct sn9c102_device* cam, + const struct v4l2_rect* rect) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int hv7131r_set_pix_format(struct sn9c102_device* cam, + const struct v4l2_pix_format* pix) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_write_reg(cam, 0xa0, 0x19); + err += sn9c102_i2c_write(cam, 0x01, 0x04); + } else { + err += sn9c102_write_reg(cam, 0x30, 0x19); + err += sn9c102_i2c_write(cam, 0x01, 0x04); + } + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_write_reg(cam, 0xa5, 0x17); + err += sn9c102_i2c_write(cam, 0x01, 0x24); + } else { + err += sn9c102_write_reg(cam, 0xa3, 0x17); + err += sn9c102_i2c_write(cam, 0x01, 0x04); + } + break; + default: + break; + } + + return err; +} + + +static struct sn9c102_sensor hv7131r = { + .name = "HV7131R", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C103 | BRIDGE_SN9C105 | BRIDGE_SN9C120, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x11, + .init = &hv7131r_init, + .qctrl = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0xff, + .step = 0x01, + .default_value = 0x40, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x08, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x1a, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x2f, + .flags = 0, + }, + { + .id = V4L2_CID_BLACK_LEVEL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "auto black level compensation", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + }, + .get_ctrl = &hv7131r_get_ctrl, + .set_ctrl = &hv7131r_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &hv7131r_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &hv7131r_set_pix_format +}; + + +int sn9c102_probe_hv7131r(struct sn9c102_device* cam) +{ + int devid, err; + + err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x44, 0x02}, + {0x34, 0x01}, {0x20, 0x17}, + {0x34, 0x01}, {0x46, 0x01}); + + if (err) + return -EIO; + + devid = sn9c102_i2c_try_read(cam, &hv7131r, 0x00); + if (devid < 0) + return -EIO; + + if (devid != 0x02) + return -ENODEV; + + sn9c102_attach_sensor(cam, &hv7131r); + + return 0; +} diff --git a/drivers/media/video/sn9c102/sn9c102_mi0343.c b/drivers/media/video/sn9c102/sn9c102_mi0343.c index a33d1bc10f9..9200845d011 100644 --- a/drivers/media/video/sn9c102/sn9c102_mi0343.c +++ b/drivers/media/video/sn9c102/sn9c102_mi0343.c @@ -22,36 +22,30 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor mi0343; -static u8 mi0343_i2c_data[5+1]; - - static int mi0343_init(struct sn9c102_device* cam) { + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); int err = 0; - err += sn9c102_write_reg(cam, 0x00, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x0a, 0x14); - err += sn9c102_write_reg(cam, 0x40, 0x01); - err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x07, 0x18); - err += sn9c102_write_reg(cam, 0xa0, 0x19); - - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x0d, 0x00, 0x01, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x0d, 0x00, 0x00, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x03, 0x01, 0xe1, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x04, 0x02, 0x81, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x05, 0x00, 0x17, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x06, 0x00, 0x11, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, mi0343.i2c_slave_id, - 0x62, 0x04, 0x9a, 0, 0); + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x0a, 0x14}, {0x40, 0x01}, + {0x20, 0x17}, {0x07, 0x18}, + {0xa0, 0x19}); + + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x01, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03, + 0x01, 0xe1, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04, + 0x02, 0x81, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05, + 0x00, 0x17, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06, + 0x00, 0x11, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x62, + 0x04, 0x9a, 0, 0); return err; } @@ -60,43 +54,46 @@ static int mi0343_init(struct sn9c102_device* cam) static int mi0343_get_ctrl(struct sn9c102_device* cam, struct v4l2_control* ctrl) { + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + u8 data[5+1]; + switch (ctrl->id) { case V4L2_CID_EXPOSURE: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x09, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x09, + 2+1, data) < 0) return -EIO; - ctrl->value = mi0343_i2c_data[2]; + ctrl->value = data[2]; return 0; case V4L2_CID_GAIN: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x35, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x35, + 2+1, data) < 0) return -EIO; break; case V4L2_CID_HFLIP: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x20, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, + 2+1, data) < 0) return -EIO; - ctrl->value = mi0343_i2c_data[3] & 0x20 ? 1 : 0; + ctrl->value = data[3] & 0x20 ? 1 : 0; return 0; case V4L2_CID_VFLIP: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x20, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, + 2+1, data) < 0) return -EIO; - ctrl->value = mi0343_i2c_data[3] & 0x80 ? 1 : 0; + ctrl->value = data[3] & 0x80 ? 1 : 0; return 0; case V4L2_CID_RED_BALANCE: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x2d, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2d, + 2+1, data) < 0) return -EIO; break; case V4L2_CID_BLUE_BALANCE: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x2c, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2c, + 2+1, data) < 0) return -EIO; break; case SN9C102_V4L2_CID_GREEN_BALANCE: - if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, - 0x2e, 2+1, mi0343_i2c_data) < 0) + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2e, + 2+1, data) < 0) return -EIO; break; default: @@ -108,7 +105,7 @@ static int mi0343_get_ctrl(struct sn9c102_device* cam, case V4L2_CID_RED_BALANCE: case V4L2_CID_BLUE_BALANCE: case SN9C102_V4L2_CID_GREEN_BALANCE: - ctrl->value = mi0343_i2c_data[3] | (mi0343_i2c_data[2] << 8); + ctrl->value = data[3] | (data[2] << 8); if (ctrl->value >= 0x10 && ctrl->value <= 0x3f) ctrl->value -= 0x10; else if (ctrl->value >= 0x60 && ctrl->value <= 0x7f) @@ -124,6 +121,7 @@ static int mi0343_get_ctrl(struct sn9c102_device* cam, static int mi0343_set_ctrl(struct sn9c102_device* cam, const struct v4l2_control* ctrl) { + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); u16 reg = 0; int err = 0; @@ -143,50 +141,42 @@ static int mi0343_set_ctrl(struct sn9c102_device* cam, switch (ctrl->id) { case V4L2_CID_EXPOSURE: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x09, ctrl->value, 0x00, 0, 0); break; case V4L2_CID_GAIN: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x35, reg >> 8, reg & 0xff, 0, 0); break; case V4L2_CID_HFLIP: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x20, ctrl->value ? 0x40:0x00, ctrl->value ? 0x20:0x00, 0, 0); break; case V4L2_CID_VFLIP: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x20, ctrl->value ? 0x80:0x00, ctrl->value ? 0x80:0x00, 0, 0); break; case V4L2_CID_RED_BALANCE: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x2d, reg >> 8, reg & 0xff, 0, 0); break; case V4L2_CID_BLUE_BALANCE: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x2c, reg >> 8, reg & 0xff, 0, 0); break; case SN9C102_V4L2_CID_GREEN_BALANCE: - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x2b, reg >> 8, reg & 0xff, 0, 0); - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x2e, reg >> 8, reg & 0xff, 0, 0); break; @@ -216,16 +206,15 @@ static int mi0343_set_crop(struct sn9c102_device* cam, static int mi0343_set_pix_format(struct sn9c102_device* cam, const struct v4l2_pix_format* pix) { + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); int err = 0; if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) { - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0a, 0x00, 0x03, 0, 0); err += sn9c102_write_reg(cam, 0x20, 0x19); } else { - err += sn9c102_i2c_try_raw_write(cam, &mi0343, 4, - mi0343.i2c_slave_id, + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0a, 0x00, 0x05, 0, 0); err += sn9c102_write_reg(cam, 0xa0, 0x19); } @@ -237,7 +226,7 @@ static int mi0343_set_pix_format(struct sn9c102_device* cam, static struct sn9c102_sensor mi0343 = { .name = "MI-0343", .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", - .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103, + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_2WIRES, .i2c_slave_id = 0x5d, @@ -343,19 +332,20 @@ static struct sn9c102_sensor mi0343 = { int sn9c102_probe_mi0343(struct sn9c102_device* cam) { + u8 data[5+1]; int err = 0; - err += sn9c102_write_reg(cam, 0x01, 0x01); - err += sn9c102_write_reg(cam, 0x00, 0x01); - err += sn9c102_write_reg(cam, 0x28, 0x17); + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); + if (err) return -EIO; if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, 0x00, - 2, mi0343_i2c_data) < 0) + 2, data) < 0) return -EIO; - if (mi0343_i2c_data[4] != 0x32 && mi0343_i2c_data[3] != 0xe3) + if (data[4] != 0x32 || data[3] != 0xe3) return -ENODEV; sn9c102_attach_sensor(cam, &mi0343); diff --git a/drivers/media/video/sn9c102/sn9c102_mi0360.c b/drivers/media/video/sn9c102/sn9c102_mi0360.c new file mode 100644 index 00000000000..64698acb0b1 --- /dev/null +++ b/drivers/media/video/sn9c102/sn9c102_mi0360.c @@ -0,0 +1,338 @@ +/*************************************************************************** + * Plug-in for MI-0360 image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * 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 "sn9c102_sensor.h" + + +static int mi0360_init(struct sn9c102_device* cam) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + int err = 0; + + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x0a, 0x14}, {0x40, 0x01}, + {0x20, 0x17}, {0x07, 0x18}, + {0xa0, 0x19}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); + + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x01, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03, + 0x01, 0xe1, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04, + 0x02, 0x81, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05, + 0x00, 0x17, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06, + 0x00, 0x11, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x62, + 0x04, 0x9a, 0, 0); + + return err; +} + + +static int mi0360_get_ctrl(struct sn9c102_device* cam, + struct v4l2_control* ctrl) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + u8 data[5+1]; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x09, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[2]; + return 0; + case V4L2_CID_GAIN: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x35, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[3]; + return 0; + case V4L2_CID_RED_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2c, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[3]; + return 0; + case V4L2_CID_BLUE_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2d, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[3]; + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2e, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[3]; + return 0; + case V4L2_CID_HFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[3] & 0x20 ? 1 : 0; + return 0; + case V4L2_CID_VFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, + 2+1, data) < 0) + return -EIO; + ctrl->value = data[3] & 0x80 ? 1 : 0; + return 0; + default: + return -EINVAL; + } + + return 0; +} + + +static int mi0360_set_ctrl(struct sn9c102_device* cam, + const struct v4l2_control* ctrl) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x09, ctrl->value, 0x00, + 0, 0); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x35, 0x03, ctrl->value, + 0, 0); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2c, 0x03, ctrl->value, + 0, 0); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2d, 0x03, ctrl->value, + 0, 0); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2b, 0x03, ctrl->value, + 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2e, 0x03, ctrl->value, + 0, 0); + break; + case V4L2_CID_HFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, ctrl->value ? 0x40:0x00, + ctrl->value ? 0x20:0x00, + 0, 0); + break; + case V4L2_CID_VFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, ctrl->value ? 0x80:0x00, + ctrl->value ? 0x80:0x00, + 0, 0); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int mi0360_set_crop(struct sn9c102_device* cam, + const struct v4l2_rect* rect) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 0, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int mi0360_set_pix_format(struct sn9c102_device* cam, + const struct v4l2_pix_format* pix) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) { + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x0a, 0x00, 0x02, 0, 0); + err += sn9c102_write_reg(cam, 0x20, 0x19); + } else { + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x0a, 0x00, 0x05, 0, 0); + err += sn9c102_write_reg(cam, 0x60, 0x19); + } + + return err; +} + + +static struct sn9c102_sensor mi0360 = { + .name = "MI-0360", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C103, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x5d, + .init = &mi0360_init, + .qctrl = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0x0f, + .step = 0x01, + .default_value = 0x05, + .flags = 0, + }, + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x25, + .flags = 0, + }, + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "horizontal mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "vertical mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x0f, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x32, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x25, + .flags = 0, + }, + }, + .get_ctrl = &mi0360_get_ctrl, + .set_ctrl = &mi0360_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &mi0360_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &mi0360_set_pix_format +}; + + +int sn9c102_probe_mi0360(struct sn9c102_device* cam) +{ + u8 data[5+1]; + int err; + + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); + if (err) + return -EIO; + + if (sn9c102_i2c_try_raw_read(cam, &mi0360, mi0360.i2c_slave_id, 0x00, + 2+1, data) < 0) + return -EIO; + + if (data[2] != 0x82 || data[3] != 0x43) + return -ENODEV; + + sn9c102_attach_sensor(cam, &mi0360); + + return 0; +} diff --git a/drivers/media/video/sn9c102/sn9c102_ov7630.c b/drivers/media/video/sn9c102/sn9c102_ov7630.c index 7df09ff38e6..31b6080b061 100644 --- a/drivers/media/video/sn9c102/sn9c102_ov7630.c +++ b/drivers/media/video/sn9c102/sn9c102_ov7630.c @@ -22,9 +22,6 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor ov7630; - - static int ov7630_init(struct sn9c102_device* cam) { int err = 0; @@ -32,21 +29,20 @@ static int ov7630_init(struct sn9c102_device* cam) switch (sn9c102_get_bridge(cam)) { case BRIDGE_SN9C101: case BRIDGE_SN9C102: - err += sn9c102_write_reg(cam, 0x00, 0x14); - err += sn9c102_write_reg(cam, 0x60, 0x17); - err += sn9c102_write_reg(cam, 0x0f, 0x18); - err += sn9c102_write_reg(cam, 0x50, 0x19); + err = sn9c102_write_const_regs(cam, {0x00, 0x14}, + {0x60, 0x17}, {0x0f, 0x18}, + {0x50, 0x19}); err += sn9c102_i2c_write(cam, 0x12, 0x8d); err += sn9c102_i2c_write(cam, 0x12, 0x0d); err += sn9c102_i2c_write(cam, 0x11, 0x00); - err += sn9c102_i2c_write(cam, 0x15, 0x34); - err += sn9c102_i2c_write(cam, 0x16, 0x03); - err += sn9c102_i2c_write(cam, 0x17, 0x1c); - err += sn9c102_i2c_write(cam, 0x18, 0xbd); - err += sn9c102_i2c_write(cam, 0x19, 0x06); - err += sn9c102_i2c_write(cam, 0x1a, 0xf6); - err += sn9c102_i2c_write(cam, 0x1b, 0x04); + err += sn9c102_i2c_write(cam, 0x15, 0x35); + err += sn9c102_i2c_write(cam, 0x16, 0x03); + err += sn9c102_i2c_write(cam, 0x17, 0x1c); + err += sn9c102_i2c_write(cam, 0x18, 0xbd); + err += sn9c102_i2c_write(cam, 0x19, 0x06); + err += sn9c102_i2c_write(cam, 0x1a, 0xf6); + err += sn9c102_i2c_write(cam, 0x1b, 0x04); err += sn9c102_i2c_write(cam, 0x20, 0x44); err += sn9c102_i2c_write(cam, 0x23, 0xee); err += sn9c102_i2c_write(cam, 0x26, 0xa0); @@ -65,42 +61,26 @@ static int ov7630_init(struct sn9c102_device* cam) err += sn9c102_i2c_write(cam, 0x71, 0x00); err += sn9c102_i2c_write(cam, 0x74, 0x21); err += sn9c102_i2c_write(cam, 0x7d, 0xf7); + break; case BRIDGE_SN9C103: - err += sn9c102_write_reg(cam, 0x00, 0x02); - err += sn9c102_write_reg(cam, 0x00, 0x03); - err += sn9c102_write_reg(cam, 0x1a, 0x04); - err += sn9c102_write_reg(cam, 0x20, 0x05); - err += sn9c102_write_reg(cam, 0x20, 0x06); - err += sn9c102_write_reg(cam, 0x20, 0x07); - err += sn9c102_write_reg(cam, 0x03, 0x10); - err += sn9c102_write_reg(cam, 0x0a, 0x14); - err += sn9c102_write_reg(cam, 0x60, 0x17); - err += sn9c102_write_reg(cam, 0x0f, 0x18); - err += sn9c102_write_reg(cam, 0x50, 0x19); - err += sn9c102_write_reg(cam, 0x1d, 0x1a); - err += sn9c102_write_reg(cam, 0x10, 0x1b); - err += sn9c102_write_reg(cam, 0x02, 0x1c); - err += sn9c102_write_reg(cam, 0x03, 0x1d); - err += sn9c102_write_reg(cam, 0x0f, 0x1e); - err += sn9c102_write_reg(cam, 0x0c, 0x1f); - err += sn9c102_write_reg(cam, 0x00, 0x20); - err += sn9c102_write_reg(cam, 0x10, 0x21); - err += sn9c102_write_reg(cam, 0x20, 0x22); - err += sn9c102_write_reg(cam, 0x30, 0x23); - err += sn9c102_write_reg(cam, 0x40, 0x24); - err += sn9c102_write_reg(cam, 0x50, 0x25); - err += sn9c102_write_reg(cam, 0x60, 0x26); - err += sn9c102_write_reg(cam, 0x70, 0x27); - err += sn9c102_write_reg(cam, 0x80, 0x28); - err += sn9c102_write_reg(cam, 0x90, 0x29); - err += sn9c102_write_reg(cam, 0xa0, 0x2a); - err += sn9c102_write_reg(cam, 0xb0, 0x2b); - err += sn9c102_write_reg(cam, 0xc0, 0x2c); - err += sn9c102_write_reg(cam, 0xd0, 0x2d); - err += sn9c102_write_reg(cam, 0xe0, 0x2e); - err += sn9c102_write_reg(cam, 0xf0, 0x2f); - err += sn9c102_write_reg(cam, 0xff, 0x30); + err = sn9c102_write_const_regs(cam, {0x00, 0x02}, {0x00, 0x03}, + {0x1a, 0x04}, {0x20, 0x05}, + {0x20, 0x06}, {0x20, 0x07}, + {0x03, 0x10}, {0x0a, 0x14}, + {0x60, 0x17}, {0x0f, 0x18}, + {0x50, 0x19}, {0x1d, 0x1a}, + {0x10, 0x1b}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); err += sn9c102_i2c_write(cam, 0x12, 0x8d); err += sn9c102_i2c_write(cam, 0x12, 0x0d); @@ -108,23 +88,23 @@ static int ov7630_init(struct sn9c102_device* cam) err += sn9c102_i2c_write(cam, 0x11, 0x01); err += sn9c102_i2c_write(cam, 0x1b, 0x04); err += sn9c102_i2c_write(cam, 0x20, 0x44); - err += sn9c102_i2c_write(cam, 0x23, 0xee); - err += sn9c102_i2c_write(cam, 0x26, 0xa0); - err += sn9c102_i2c_write(cam, 0x27, 0x9a); + err += sn9c102_i2c_write(cam, 0x23, 0xee); + err += sn9c102_i2c_write(cam, 0x26, 0xa0); + err += sn9c102_i2c_write(cam, 0x27, 0x9a); err += sn9c102_i2c_write(cam, 0x28, 0x20); - err += sn9c102_i2c_write(cam, 0x29, 0x30); - err += sn9c102_i2c_write(cam, 0x2f, 0x3d); - err += sn9c102_i2c_write(cam, 0x30, 0x24); - err += sn9c102_i2c_write(cam, 0x32, 0x86); - err += sn9c102_i2c_write(cam, 0x60, 0xa9); - err += sn9c102_i2c_write(cam, 0x61, 0x42); - err += sn9c102_i2c_write(cam, 0x65, 0x00); - err += sn9c102_i2c_write(cam, 0x69, 0x38); - err += sn9c102_i2c_write(cam, 0x6f, 0x88); - err += sn9c102_i2c_write(cam, 0x70, 0x0b); - err += sn9c102_i2c_write(cam, 0x71, 0x00); - err += sn9c102_i2c_write(cam, 0x74, 0x21); - err += sn9c102_i2c_write(cam, 0x7d, 0xf7); + err += sn9c102_i2c_write(cam, 0x29, 0x30); + err += sn9c102_i2c_write(cam, 0x2f, 0x3d); + err += sn9c102_i2c_write(cam, 0x30, 0x24); + err += sn9c102_i2c_write(cam, 0x32, 0x86); + err += sn9c102_i2c_write(cam, 0x60, 0xa9); + err += sn9c102_i2c_write(cam, 0x61, 0x42); + err += sn9c102_i2c_write(cam, 0x65, 0x00); + err += sn9c102_i2c_write(cam, 0x69, 0x38); + err += sn9c102_i2c_write(cam, 0x6f, 0x88); + err += sn9c102_i2c_write(cam, 0x70, 0x0b); + err += sn9c102_i2c_write(cam, 0x71, 0x00); + err += sn9c102_i2c_write(cam, 0x74, 0x21); + err += sn9c102_i2c_write(cam, 0x7d, 0xf7); break; default: break; @@ -428,15 +408,14 @@ int sn9c102_probe_ov7630(struct sn9c102_device* cam) switch (sn9c102_get_bridge(cam)) { case BRIDGE_SN9C101: case BRIDGE_SN9C102: - err += sn9c102_write_reg(cam, 0x01, 0x01); - err += sn9c102_write_reg(cam, 0x00, 0x01); - err += sn9c102_write_reg(cam, 0x28, 0x17); + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, + {0x00, 0x01}, {0x28, 0x17}); + break; case BRIDGE_SN9C103: /* do _not_ change anything! */ - err += sn9c102_write_reg(cam, 0x09, 0x01); - err += sn9c102_write_reg(cam, 0x42, 0x01); - err += sn9c102_write_reg(cam, 0x28, 0x17); - err += sn9c102_write_reg(cam, 0x44, 0x02); + err = sn9c102_write_const_regs(cam, {0x09, 0x01}, + {0x42, 0x01}, {0x28, 0x17}, + {0x44, 0x02}); pid = sn9c102_i2c_try_read(cam, &ov7630, 0x0a); if (err || pid < 0) { /* try a different initialization */ err = sn9c102_write_reg(cam, 0x01, 0x01); diff --git a/drivers/media/video/sn9c102/sn9c102_ov7660.c b/drivers/media/video/sn9c102/sn9c102_ov7660.c index d670c24d443..c898e948fe8 100644 --- a/drivers/media/video/sn9c102/sn9c102_ov7660.c +++ b/drivers/media/video/sn9c102/sn9c102_ov7660.c @@ -22,160 +22,84 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor ov7660; - - static int ov7660_init(struct sn9c102_device* cam) { int err = 0; - err += sn9c102_write_reg(cam, 0x40, 0x02); - err += sn9c102_write_reg(cam, 0x00, 0x03); - err += sn9c102_write_reg(cam, 0x1a, 0x04); - err += sn9c102_write_reg(cam, 0x03, 0x10); - err += sn9c102_write_reg(cam, 0x08, 0x14); - err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x8b, 0x18); - err += sn9c102_write_reg(cam, 0x00, 0x19); - err += sn9c102_write_reg(cam, 0x1d, 0x1a); - err += sn9c102_write_reg(cam, 0x10, 0x1b); - err += sn9c102_write_reg(cam, 0x02, 0x1c); - err += sn9c102_write_reg(cam, 0x03, 0x1d); - err += sn9c102_write_reg(cam, 0x0f, 0x1e); - err += sn9c102_write_reg(cam, 0x0c, 0x1f); - err += sn9c102_write_reg(cam, 0x00, 0x20); - err += sn9c102_write_reg(cam, 0x29, 0x21); - err += sn9c102_write_reg(cam, 0x40, 0x22); - err += sn9c102_write_reg(cam, 0x54, 0x23); - err += sn9c102_write_reg(cam, 0x66, 0x24); - err += sn9c102_write_reg(cam, 0x76, 0x25); - err += sn9c102_write_reg(cam, 0x85, 0x26); - err += sn9c102_write_reg(cam, 0x94, 0x27); - err += sn9c102_write_reg(cam, 0xa1, 0x28); - err += sn9c102_write_reg(cam, 0xae, 0x29); - err += sn9c102_write_reg(cam, 0xbb, 0x2a); - err += sn9c102_write_reg(cam, 0xc7, 0x2b); - err += sn9c102_write_reg(cam, 0xd3, 0x2c); - err += sn9c102_write_reg(cam, 0xde, 0x2d); - err += sn9c102_write_reg(cam, 0xea, 0x2e); - err += sn9c102_write_reg(cam, 0xf4, 0x2f); - err += sn9c102_write_reg(cam, 0xff, 0x30); - err += sn9c102_write_reg(cam, 0x00, 0x3F); - err += sn9c102_write_reg(cam, 0xC7, 0x40); - err += sn9c102_write_reg(cam, 0x01, 0x41); - err += sn9c102_write_reg(cam, 0x44, 0x42); - err += sn9c102_write_reg(cam, 0x00, 0x43); - err += sn9c102_write_reg(cam, 0x44, 0x44); - err += sn9c102_write_reg(cam, 0x00, 0x45); - err += sn9c102_write_reg(cam, 0x44, 0x46); - err += sn9c102_write_reg(cam, 0x00, 0x47); - err += sn9c102_write_reg(cam, 0xC7, 0x48); - err += sn9c102_write_reg(cam, 0x01, 0x49); - err += sn9c102_write_reg(cam, 0xC7, 0x4A); - err += sn9c102_write_reg(cam, 0x01, 0x4B); - err += sn9c102_write_reg(cam, 0xC7, 0x4C); - err += sn9c102_write_reg(cam, 0x01, 0x4D); - err += sn9c102_write_reg(cam, 0x44, 0x4E); - err += sn9c102_write_reg(cam, 0x00, 0x4F); - err += sn9c102_write_reg(cam, 0x44, 0x50); - err += sn9c102_write_reg(cam, 0x00, 0x51); - err += sn9c102_write_reg(cam, 0x44, 0x52); - err += sn9c102_write_reg(cam, 0x00, 0x53); - err += sn9c102_write_reg(cam, 0xC7, 0x54); - err += sn9c102_write_reg(cam, 0x01, 0x55); - err += sn9c102_write_reg(cam, 0xC7, 0x56); - err += sn9c102_write_reg(cam, 0x01, 0x57); - err += sn9c102_write_reg(cam, 0xC7, 0x58); - err += sn9c102_write_reg(cam, 0x01, 0x59); - err += sn9c102_write_reg(cam, 0x44, 0x5A); - err += sn9c102_write_reg(cam, 0x00, 0x5B); - err += sn9c102_write_reg(cam, 0x44, 0x5C); - err += sn9c102_write_reg(cam, 0x00, 0x5D); - err += sn9c102_write_reg(cam, 0x44, 0x5E); - err += sn9c102_write_reg(cam, 0x00, 0x5F); - err += sn9c102_write_reg(cam, 0xC7, 0x60); - err += sn9c102_write_reg(cam, 0x01, 0x61); - err += sn9c102_write_reg(cam, 0xC7, 0x62); - err += sn9c102_write_reg(cam, 0x01, 0x63); - err += sn9c102_write_reg(cam, 0xC7, 0x64); - err += sn9c102_write_reg(cam, 0x01, 0x65); - err += sn9c102_write_reg(cam, 0x44, 0x66); - err += sn9c102_write_reg(cam, 0x00, 0x67); - err += sn9c102_write_reg(cam, 0x44, 0x68); - err += sn9c102_write_reg(cam, 0x00, 0x69); - err += sn9c102_write_reg(cam, 0x44, 0x6A); - err += sn9c102_write_reg(cam, 0x00, 0x6B); - err += sn9c102_write_reg(cam, 0xC7, 0x6C); - err += sn9c102_write_reg(cam, 0x01, 0x6D); - err += sn9c102_write_reg(cam, 0xC7, 0x6E); - err += sn9c102_write_reg(cam, 0x01, 0x6F); - err += sn9c102_write_reg(cam, 0xC7, 0x70); - err += sn9c102_write_reg(cam, 0x01, 0x71); - err += sn9c102_write_reg(cam, 0x44, 0x72); - err += sn9c102_write_reg(cam, 0x00, 0x73); - err += sn9c102_write_reg(cam, 0x44, 0x74); - err += sn9c102_write_reg(cam, 0x00, 0x75); - err += sn9c102_write_reg(cam, 0x44, 0x76); - err += sn9c102_write_reg(cam, 0x00, 0x77); - err += sn9c102_write_reg(cam, 0xC7, 0x78); - err += sn9c102_write_reg(cam, 0x01, 0x79); - err += sn9c102_write_reg(cam, 0xC7, 0x7A); - err += sn9c102_write_reg(cam, 0x01, 0x7B); - err += sn9c102_write_reg(cam, 0xC7, 0x7C); - err += sn9c102_write_reg(cam, 0x01, 0x7D); - err += sn9c102_write_reg(cam, 0x44, 0x7E); - err += sn9c102_write_reg(cam, 0x00, 0x7F); - err += sn9c102_write_reg(cam, 0x14, 0x84); - err += sn9c102_write_reg(cam, 0x00, 0x85); - err += sn9c102_write_reg(cam, 0x27, 0x86); - err += sn9c102_write_reg(cam, 0x00, 0x87); - err += sn9c102_write_reg(cam, 0x07, 0x88); - err += sn9c102_write_reg(cam, 0x00, 0x89); - err += sn9c102_write_reg(cam, 0xEC, 0x8A); - err += sn9c102_write_reg(cam, 0x0f, 0x8B); - err += sn9c102_write_reg(cam, 0xD8, 0x8C); - err += sn9c102_write_reg(cam, 0x0f, 0x8D); - err += sn9c102_write_reg(cam, 0x3D, 0x8E); - err += sn9c102_write_reg(cam, 0x00, 0x8F); - err += sn9c102_write_reg(cam, 0x3D, 0x90); - err += sn9c102_write_reg(cam, 0x00, 0x91); - err += sn9c102_write_reg(cam, 0xCD, 0x92); - err += sn9c102_write_reg(cam, 0x0f, 0x93); - err += sn9c102_write_reg(cam, 0xf7, 0x94); - err += sn9c102_write_reg(cam, 0x0f, 0x95); - err += sn9c102_write_reg(cam, 0x0C, 0x96); - err += sn9c102_write_reg(cam, 0x00, 0x97); - err += sn9c102_write_reg(cam, 0x00, 0x98); - err += sn9c102_write_reg(cam, 0x66, 0x99); - err += sn9c102_write_reg(cam, 0x05, 0x9A); - err += sn9c102_write_reg(cam, 0x00, 0x9B); - err += sn9c102_write_reg(cam, 0x04, 0x9C); - err += sn9c102_write_reg(cam, 0x00, 0x9D); - err += sn9c102_write_reg(cam, 0x08, 0x9E); - err += sn9c102_write_reg(cam, 0x00, 0x9F); - err += sn9c102_write_reg(cam, 0x2D, 0xC0); - err += sn9c102_write_reg(cam, 0x2D, 0xC1); - err += sn9c102_write_reg(cam, 0x3A, 0xC2); - err += sn9c102_write_reg(cam, 0x05, 0xC3); - err += sn9c102_write_reg(cam, 0x04, 0xC4); - err += sn9c102_write_reg(cam, 0x3F, 0xC5); - err += sn9c102_write_reg(cam, 0x00, 0xC6); - err += sn9c102_write_reg(cam, 0x00, 0xC7); - err += sn9c102_write_reg(cam, 0x50, 0xC8); - err += sn9c102_write_reg(cam, 0x3C, 0xC9); - err += sn9c102_write_reg(cam, 0x28, 0xCA); - err += sn9c102_write_reg(cam, 0xD8, 0xCB); - err += sn9c102_write_reg(cam, 0x14, 0xCC); - err += sn9c102_write_reg(cam, 0xEC, 0xCD); - err += sn9c102_write_reg(cam, 0x32, 0xCE); - err += sn9c102_write_reg(cam, 0xDD, 0xCF); - err += sn9c102_write_reg(cam, 0x32, 0xD0); - err += sn9c102_write_reg(cam, 0xDD, 0xD1); - err += sn9c102_write_reg(cam, 0x6A, 0xD2); - err += sn9c102_write_reg(cam, 0x50, 0xD3); - err += sn9c102_write_reg(cam, 0x00, 0xD4); - err += sn9c102_write_reg(cam, 0x00, 0xD5); - err += sn9c102_write_reg(cam, 0x00, 0xD6); + err = sn9c102_write_const_regs(cam, {0x40, 0x02}, {0x00, 0x03}, + {0x1a, 0x04}, {0x03, 0x10}, + {0x08, 0x14}, {0x20, 0x17}, + {0x8b, 0x18}, {0x00, 0x19}, + {0x1d, 0x1a}, {0x10, 0x1b}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x29, 0x21}, + {0x40, 0x22}, {0x54, 0x23}, + {0x66, 0x24}, {0x76, 0x25}, + {0x85, 0x26}, {0x94, 0x27}, + {0xa1, 0x28}, {0xae, 0x29}, + {0xbb, 0x2a}, {0xc7, 0x2b}, + {0xd3, 0x2c}, {0xde, 0x2d}, + {0xea, 0x2e}, {0xf4, 0x2f}, + {0xff, 0x30}, {0x00, 0x3F}, + {0xC7, 0x40}, {0x01, 0x41}, + {0x44, 0x42}, {0x00, 0x43}, + {0x44, 0x44}, {0x00, 0x45}, + {0x44, 0x46}, {0x00, 0x47}, + {0xC7, 0x48}, {0x01, 0x49}, + {0xC7, 0x4A}, {0x01, 0x4B}, + {0xC7, 0x4C}, {0x01, 0x4D}, + {0x44, 0x4E}, {0x00, 0x4F}, + {0x44, 0x50}, {0x00, 0x51}, + {0x44, 0x52}, {0x00, 0x53}, + {0xC7, 0x54}, {0x01, 0x55}, + {0xC7, 0x56}, {0x01, 0x57}, + {0xC7, 0x58}, {0x01, 0x59}, + {0x44, 0x5A}, {0x00, 0x5B}, + {0x44, 0x5C}, {0x00, 0x5D}, + {0x44, 0x5E}, {0x00, 0x5F}, + {0xC7, 0x60}, {0x01, 0x61}, + {0xC7, 0x62}, {0x01, 0x63}, + {0xC7, 0x64}, {0x01, 0x65}, + {0x44, 0x66}, {0x00, 0x67}, + {0x44, 0x68}, {0x00, 0x69}, + {0x44, 0x6A}, {0x00, 0x6B}, + {0xC7, 0x6C}, {0x01, 0x6D}, + {0xC7, 0x6E}, {0x01, 0x6F}, + {0xC7, 0x70}, {0x01, 0x71}, + {0x44, 0x72}, {0x00, 0x73}, + {0x44, 0x74}, {0x00, 0x75}, + {0x44, 0x76}, {0x00, 0x77}, + {0xC7, 0x78}, {0x01, 0x79}, + {0xC7, 0x7A}, {0x01, 0x7B}, + {0xC7, 0x7C}, {0x01, 0x7D}, + {0x44, 0x7E}, {0x00, 0x7F}, + {0x14, 0x84}, {0x00, 0x85}, + {0x27, 0x86}, {0x00, 0x87}, + {0x07, 0x88}, {0x00, 0x89}, + {0xEC, 0x8A}, {0x0f, 0x8B}, + {0xD8, 0x8C}, {0x0f, 0x8D}, + {0x3D, 0x8E}, {0x00, 0x8F}, + {0x3D, 0x90}, {0x00, 0x91}, + {0xCD, 0x92}, {0x0f, 0x93}, + {0xf7, 0x94}, {0x0f, 0x95}, + {0x0C, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x66, 0x99}, + {0x05, 0x9A}, {0x00, 0x9B}, + {0x04, 0x9C}, {0x00, 0x9D}, + {0x08, 0x9E}, {0x00, 0x9F}, + {0x2D, 0xC0}, {0x2D, 0xC1}, + {0x3A, 0xC2}, {0x05, 0xC3}, + {0x04, 0xC4}, {0x3F, 0xC5}, + {0x00, 0xC6}, {0x00, 0xC7}, + {0x50, 0xC8}, {0x3C, 0xC9}, + {0x28, 0xCA}, {0xD8, 0xCB}, + {0x14, 0xCC}, {0xEC, 0xCD}, + {0x32, 0xCE}, {0xDD, 0xCF}, + {0x32, 0xD0}, {0xDD, 0xD1}, + {0x6A, 0xD2}, {0x50, 0xD3}, + {0x00, 0xD4}, {0x00, 0xD5}, + {0x00, 0xD6}); err += sn9c102_i2c_write(cam, 0x12, 0x80); err += sn9c102_i2c_write(cam, 0x11, 0x09); @@ -572,13 +496,11 @@ static struct sn9c102_sensor ov7660 = { int sn9c102_probe_ov7660(struct sn9c102_device* cam) { - int pid, ver, err = 0; + int pid, ver, err; - err += sn9c102_write_reg(cam, 0x01, 0xf1); - err += sn9c102_write_reg(cam, 0x00, 0xf1); - err += sn9c102_write_reg(cam, 0x01, 0x01); - err += sn9c102_write_reg(cam, 0x00, 0x01); - err += sn9c102_write_reg(cam, 0x28, 0x17); + err = sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1}, + {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); pid = sn9c102_i2c_try_read(cam, &ov7660, 0x0a); ver = sn9c102_i2c_try_read(cam, &ov7660, 0x0b); diff --git a/drivers/media/video/sn9c102/sn9c102_pas106b.c b/drivers/media/video/sn9c102/sn9c102_pas106b.c index 8d79a5fae5d..67151964801 100644 --- a/drivers/media/video/sn9c102/sn9c102_pas106b.c +++ b/drivers/media/video/sn9c102/sn9c102_pas106b.c @@ -23,19 +23,13 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor pas106b; - - static int pas106b_init(struct sn9c102_device* cam) { int err = 0; - err += sn9c102_write_reg(cam, 0x00, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x00, 0x14); - err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x20, 0x19); - err += sn9c102_write_reg(cam, 0x09, 0x18); + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x00, 0x14}, {0x20, 0x17}, + {0x20, 0x19}, {0x09, 0x18}); err += sn9c102_i2c_write(cam, 0x02, 0x0c); err += sn9c102_i2c_write(cam, 0x05, 0x5a); @@ -172,7 +166,7 @@ static int pas106b_set_pix_format(struct sn9c102_device* cam, static struct sn9c102_sensor pas106b = { .name = "PAS106B", .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", - .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103, + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, .frequency = SN9C102_I2C_400KHZ | SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_2WIRES, @@ -279,16 +273,17 @@ static struct sn9c102_sensor pas106b = { int sn9c102_probe_pas106b(struct sn9c102_device* cam) { - int r0 = 0, r1 = 0, err = 0; + int r0 = 0, r1 = 0, err; unsigned int pid = 0; /* Minimal initialization to enable the I2C communication NOTE: do NOT change the values! */ - err += sn9c102_write_reg(cam, 0x01, 0x01); /* sensor power down */ - err += sn9c102_write_reg(cam, 0x00, 0x01); /* sensor power on */ - err += sn9c102_write_reg(cam, 0x28, 0x17); /* sensor clock at 24 MHz */ + err = sn9c102_write_const_regs(cam, + {0x01, 0x01}, /* sensor power down */ + {0x00, 0x01}, /* sensor power on */ + {0x28, 0x17});/* sensor clock 24 MHz */ if (err) return -EIO; diff --git a/drivers/media/video/sn9c102/sn9c102_pas202bcb.c b/drivers/media/video/sn9c102/sn9c102_pas202bcb.c index 7894f01b56e..c1b8d6b63b4 100644 --- a/drivers/media/video/sn9c102/sn9c102_pas202bcb.c +++ b/drivers/media/video/sn9c102/sn9c102_pas202bcb.c @@ -28,9 +28,6 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor pas202bcb; - - static int pas202bcb_init(struct sn9c102_device* cam) { int err = 0; @@ -38,47 +35,29 @@ static int pas202bcb_init(struct sn9c102_device* cam) switch (sn9c102_get_bridge(cam)) { case BRIDGE_SN9C101: case BRIDGE_SN9C102: - err += sn9c102_write_reg(cam, 0x00, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x00, 0x14); - err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x30, 0x19); - err += sn9c102_write_reg(cam, 0x09, 0x18); + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, + {0x00, 0x11}, {0x00, 0x14}, + {0x20, 0x17}, {0x30, 0x19}, + {0x09, 0x18}); break; case BRIDGE_SN9C103: - err += sn9c102_write_reg(cam, 0x00, 0x02); - err += sn9c102_write_reg(cam, 0x00, 0x03); - err += sn9c102_write_reg(cam, 0x1a, 0x04); - err += sn9c102_write_reg(cam, 0x20, 0x05); - err += sn9c102_write_reg(cam, 0x20, 0x06); - err += sn9c102_write_reg(cam, 0x20, 0x07); - err += sn9c102_write_reg(cam, 0x00, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x00, 0x14); - err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x30, 0x19); - err += sn9c102_write_reg(cam, 0x09, 0x18); - err += sn9c102_write_reg(cam, 0x02, 0x1c); - err += sn9c102_write_reg(cam, 0x03, 0x1d); - err += sn9c102_write_reg(cam, 0x0f, 0x1e); - err += sn9c102_write_reg(cam, 0x0c, 0x1f); - err += sn9c102_write_reg(cam, 0x00, 0x20); - err += sn9c102_write_reg(cam, 0x10, 0x21); - err += sn9c102_write_reg(cam, 0x20, 0x22); - err += sn9c102_write_reg(cam, 0x30, 0x23); - err += sn9c102_write_reg(cam, 0x40, 0x24); - err += sn9c102_write_reg(cam, 0x50, 0x25); - err += sn9c102_write_reg(cam, 0x60, 0x26); - err += sn9c102_write_reg(cam, 0x70, 0x27); - err += sn9c102_write_reg(cam, 0x80, 0x28); - err += sn9c102_write_reg(cam, 0x90, 0x29); - err += sn9c102_write_reg(cam, 0xa0, 0x2a); - err += sn9c102_write_reg(cam, 0xb0, 0x2b); - err += sn9c102_write_reg(cam, 0xc0, 0x2c); - err += sn9c102_write_reg(cam, 0xd0, 0x2d); - err += sn9c102_write_reg(cam, 0xe0, 0x2e); - err += sn9c102_write_reg(cam, 0xf0, 0x2f); - err += sn9c102_write_reg(cam, 0xff, 0x30); + err = sn9c102_write_const_regs(cam, {0x00, 0x02}, + {0x00, 0x03}, {0x1a, 0x04}, + {0x20, 0x05}, {0x20, 0x06}, + {0x20, 0x07}, {0x00, 0x10}, + {0x00, 0x11}, {0x00, 0x14}, + {0x20, 0x17}, {0x30, 0x19}, + {0x09, 0x18}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); break; default: break; @@ -328,15 +307,15 @@ int sn9c102_probe_pas202bcb(struct sn9c102_device* cam) switch (sn9c102_get_bridge(cam)) { case BRIDGE_SN9C101: case BRIDGE_SN9C102: - err += sn9c102_write_reg(cam, 0x01, 0x01); /* power down */ - err += sn9c102_write_reg(cam, 0x40, 0x01); /* power on */ - err += sn9c102_write_reg(cam, 0x28, 0x17); /* clock 24 MHz */ + err = sn9c102_write_const_regs(cam, + {0x01, 0x01}, /* power down */ + {0x40, 0x01}, /* power on */ + {0x28, 0x17});/* clock 24 MHz */ break; case BRIDGE_SN9C103: /* do _not_ change anything! */ - err += sn9c102_write_reg(cam, 0x09, 0x01); - err += sn9c102_write_reg(cam, 0x44, 0x01); - err += sn9c102_write_reg(cam, 0x44, 0x02); - err += sn9c102_write_reg(cam, 0x29, 0x17); + err = sn9c102_write_const_regs(cam, {0x09, 0x01}, + {0x44, 0x01}, {0x44, 0x02}, + {0x29, 0x17}); break; default: break; diff --git a/drivers/media/video/sn9c102/sn9c102_sensor.h b/drivers/media/video/sn9c102/sn9c102_sensor.h index 05f2942639c..1bbf64c897a 100644 --- a/drivers/media/video/sn9c102/sn9c102_sensor.h +++ b/drivers/media/video/sn9c102/sn9c102_sensor.h @@ -114,9 +114,17 @@ extern int sn9c102_i2c_write(struct sn9c102_device*, u8 address, u8 value); extern int sn9c102_i2c_read(struct sn9c102_device*, u8 address); /* I/O on registers in the bridge. Could be used by the sensor methods too */ -extern int sn9c102_write_regs(struct sn9c102_device*, u8* buff, u16 index); -extern int sn9c102_write_reg(struct sn9c102_device*, u8 value, u16 index); extern int sn9c102_pread_reg(struct sn9c102_device*, u16 index); +extern int sn9c102_write_reg(struct sn9c102_device*, u8 value, u16 index); +extern int sn9c102_write_regs(struct sn9c102_device*, const u8 valreg[][2], + int count); +/* + * Write multiple registers with constant values. For example: + * sn9c102_write_const_regs(cam, {0x00, 0x14}, {0x60, 0x17}, {0x0f, 0x18}); + */ +#define sn9c102_write_const_regs(device, data...) \ + ({ const static u8 _data[][2] = {data}; \ + sn9c102_write_regs(device, _data, ARRAY_SIZE(_data)); }) /*****************************************************************************/ diff --git a/drivers/media/video/sn9c102/sn9c102_tas5110c1b.c b/drivers/media/video/sn9c102/sn9c102_tas5110c1b.c index 90023ad63ad..0e7ec8662c7 100644 --- a/drivers/media/video/sn9c102/sn9c102_tas5110c1b.c +++ b/drivers/media/video/sn9c102/sn9c102_tas5110c1b.c @@ -22,21 +22,14 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor tas5110c1b; - - static int tas5110c1b_init(struct sn9c102_device* cam) { int err = 0; - err += sn9c102_write_reg(cam, 0x01, 0x01); - err += sn9c102_write_reg(cam, 0x44, 0x01); - err += sn9c102_write_reg(cam, 0x00, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x0a, 0x14); - err += sn9c102_write_reg(cam, 0x60, 0x17); - err += sn9c102_write_reg(cam, 0x06, 0x18); - err += sn9c102_write_reg(cam, 0xfb, 0x19); + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x44, 0x01}, + {0x00, 0x10}, {0x00, 0x11}, + {0x0a, 0x14}, {0x60, 0x17}, + {0x06, 0x18}, {0xfb, 0x19}); err += sn9c102_i2c_write(cam, 0xc0, 0x80); @@ -98,7 +91,7 @@ static int tas5110c1b_set_pix_format(struct sn9c102_device* cam, static struct sn9c102_sensor tas5110c1b = { .name = "TAS5110C1B", .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", - .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103, + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, .sysfs_ops = SN9C102_I2C_WRITE, .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_3WIRES, @@ -146,7 +139,6 @@ int sn9c102_probe_tas5110c1b(struct sn9c102_device* cam) const struct usb_device_id tas5110c1b_id_table[] = { { USB_DEVICE(0x0c45, 0x6001), }, { USB_DEVICE(0x0c45, 0x6005), }, - { USB_DEVICE(0x0c45, 0x6007), }, { USB_DEVICE(0x0c45, 0x60ab), }, { } }; diff --git a/drivers/media/video/sn9c102/sn9c102_tas5110d.c b/drivers/media/video/sn9c102/sn9c102_tas5110d.c new file mode 100644 index 00000000000..83a39e8b5e7 --- /dev/null +++ b/drivers/media/video/sn9c102/sn9c102_tas5110d.c @@ -0,0 +1,118 @@ +/*************************************************************************** + * Plug-in for TAS5110D image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * 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 "sn9c102_sensor.h" + + +static int tas5110d_init(struct sn9c102_device* cam) +{ + int err; + + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x04, 0x01}, + {0x0a, 0x14}, {0x60, 0x17}, + {0x06, 0x18}, {0xfb, 0x19}); + + err += sn9c102_i2c_write(cam, 0x9a, 0xca); + + return err; +} + + +static int tas5110d_set_crop(struct sn9c102_device* cam, + const struct v4l2_rect* rect) +{ + struct sn9c102_sensor* s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 69, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 9; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + err += sn9c102_write_reg(cam, 0x14, 0x1a); + err += sn9c102_write_reg(cam, 0x0a, 0x1b); + + return err; +} + + +static int tas5110d_set_pix_format(struct sn9c102_device* cam, + const struct v4l2_pix_format* pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x3b, 0x19); + else + err += sn9c102_write_reg(cam, 0xfb, 0x19); + + return err; +} + + +static struct sn9c102_sensor tas5110d = { + .name = "TAS5110D", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .sysfs_ops = SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x61, + .init = &tas5110d_init, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + }, + .set_crop = &tas5110d_set_crop, + .pix_format = { + .width = 352, + .height = 288, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &tas5110d_set_pix_format +}; + + +int sn9c102_probe_tas5110d(struct sn9c102_device* cam) +{ + const struct usb_device_id tas5110d_id_table[] = { + { USB_DEVICE(0x0c45, 0x6007), }, + { } + }; + + if (!sn9c102_match_id(cam, tas5110d_id_table)) + return -ENODEV; + + sn9c102_attach_sensor(cam, &tas5110d); + + return 0; +} diff --git a/drivers/media/video/sn9c102/sn9c102_tas5130d1b.c b/drivers/media/video/sn9c102/sn9c102_tas5130d1b.c index cb1b318bc1f..50406503fc4 100644 --- a/drivers/media/video/sn9c102/sn9c102_tas5130d1b.c +++ b/drivers/media/video/sn9c102/sn9c102_tas5130d1b.c @@ -22,21 +22,14 @@ #include "sn9c102_sensor.h" -static struct sn9c102_sensor tas5130d1b; - - static int tas5130d1b_init(struct sn9c102_device* cam) { - int err = 0; + int err; - err += sn9c102_write_reg(cam, 0x01, 0x01); - err += sn9c102_write_reg(cam, 0x20, 0x17); - err += sn9c102_write_reg(cam, 0x04, 0x01); - err += sn9c102_write_reg(cam, 0x01, 0x10); - err += sn9c102_write_reg(cam, 0x00, 0x11); - err += sn9c102_write_reg(cam, 0x00, 0x14); - err += sn9c102_write_reg(cam, 0x60, 0x17); - err += sn9c102_write_reg(cam, 0x07, 0x18); + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x20, 0x17}, + {0x04, 0x01}, {0x01, 0x10}, + {0x00, 0x11}, {0x00, 0x14}, + {0x60, 0x17}, {0x07, 0x18}); return err; } @@ -99,7 +92,7 @@ static int tas5130d1b_set_pix_format(struct sn9c102_device* cam, static struct sn9c102_sensor tas5130d1b = { .name = "TAS5130D1B", .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", - .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103, + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, .sysfs_ops = SN9C102_I2C_WRITE, .frequency = SN9C102_I2C_100KHZ, .interface = SN9C102_I2C_3WIRES, diff --git a/drivers/media/video/tda7432.c b/drivers/media/video/tda7432.c index d1ccc064206..43225802a55 100644 --- a/drivers/media/video/tda7432.c +++ b/drivers/media/video/tda7432.c @@ -45,7 +45,6 @@ #include <linux/slab.h> #include <linux/videodev.h> #include <linux/i2c.h> -#include <linux/i2c-algo-bit.h> #include <media/v4l2-common.h> #include <media/i2c-addr.h> diff --git a/drivers/media/video/tda8290.c b/drivers/media/video/tda8290.c index 027c8a074df..1a1bef0e9c3 100644 --- a/drivers/media/video/tda8290.c +++ b/drivers/media/video/tda8290.c @@ -192,14 +192,52 @@ static struct tda827xa_data tda827xa_analog[] = { { .lomax = 0, .svco = 0, .spd = 0, .scr = 0, .sbs = 0, .gc3 = 0} /* End */ }; +static void tda827xa_lna_gain(struct i2c_client *c, int high) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned char buf[] = {0x22, 0x01}; + int arg; + struct i2c_msg msg = {.addr = c->addr, .flags = 0, .buf = buf, .len = sizeof(buf)}; + if (t->config) { + if (high) + tuner_dbg("setting LNA to high gain\n"); + else + tuner_dbg("setting LNA to low gain\n"); + } + switch (t->config) { + case 0: /* no LNA */ + break; + case 1: /* switch is GPIO 0 of tda8290 */ + case 2: + /* turn Vsync on */ + if (t->std & V4L2_STD_MN) + arg = 1; + else + arg = 0; + if (t->tuner_callback) + t->tuner_callback(c->adapter->algo_data, 1, arg); + buf[1] = high ? 0 : 1; + if (t->config == 2) + buf[1] = high ? 1 : 0; + i2c_transfer(c->adapter, &msg, 1); + break; + case 3: /* switch with GPIO of saa713x */ + if (t->tuner_callback) + t->tuner_callback(c->adapter->algo_data, 0, high); + break; + } +} + static void tda827xa_tune(struct i2c_client *c, u16 ifc, unsigned int freq) { - unsigned char tuner_reg[14]; - unsigned char reg2[2]; + unsigned char tuner_reg[11]; u32 N; int i; struct tuner *t = i2c_get_clientdata(c); - struct i2c_msg msg = {.addr = t->tda827x_addr, .flags = 0}; + struct i2c_msg msg = {.addr = t->tda827x_addr, .flags = 0, .buf = tuner_reg}; + + tda827xa_lna_gain( c, 1); + msleep(10); if (t->mode == V4L2_TUNER_RADIO) freq = freq / 1000; @@ -222,48 +260,58 @@ static void tda827xa_tune(struct i2c_client *c, u16 ifc, unsigned int freq) tuner_reg[5] = (tda827xa_analog[i].spd << 5) + (tda827xa_analog[i].svco << 3) + tda827xa_analog[i].sbs; tuner_reg[6] = 0x8b + (tda827xa_analog[i].gc3 << 4); - tuner_reg[7] = 0x0c; + tuner_reg[7] = 0x1c; tuner_reg[8] = 4; tuner_reg[9] = 0x20; - tuner_reg[10] = 0xff; - tuner_reg[11] = 0xe0; - tuner_reg[12] = 0; - tuner_reg[13] = 0x39 + (t->tda827x_lpsel << 1); + tuner_reg[10] = 0x00; + msg.len = 11; + i2c_transfer(c->adapter, &msg, 1); - msg.buf = tuner_reg; - msg.len = 14; + tuner_reg[0] = 0x90; + tuner_reg[1] = 0xff; + tuner_reg[2] = 0xe0; + tuner_reg[3] = 0; + tuner_reg[4] = 0x99 + (t->tda827x_lpsel << 1); + msg.len = 5; i2c_transfer(c->adapter, &msg, 1); - msg.buf= reg2; + tuner_reg[0] = 0xa0; + tuner_reg[1] = 0xc0; msg.len = 2; - reg2[0] = 0x60; - reg2[1] = 0x3c; i2c_transfer(c->adapter, &msg, 1); - reg2[0] = 0xa0; - reg2[1] = 0xc0; + tuner_reg[0] = 0x30; + tuner_reg[1] = 0x10 + tda827xa_analog[i].scr; i2c_transfer(c->adapter, &msg, 1); - msleep(2); - reg2[0] = 0x30; - reg2[1] = 0x10 + tda827xa_analog[i].scr; + msg.flags = I2C_M_RD; + i2c_transfer(c->adapter, &msg, 1); + msg.flags = 0; + tuner_reg[1] >>= 4; + tuner_dbg("AGC2 gain is: %d\n", tuner_reg[1]); + if (tuner_reg[1] < 1) + tda827xa_lna_gain( c, 0); + + msleep(100); + tuner_reg[0] = 0x60; + tuner_reg[1] = 0x3c; i2c_transfer(c->adapter, &msg, 1); - msleep(550); - reg2[0] = 0x50; - reg2[1] = 0x8f + (tda827xa_analog[i].gc3 << 4); + msleep(163); + tuner_reg[0] = 0x50; + tuner_reg[1] = 0x8f + (tda827xa_analog[i].gc3 << 4); i2c_transfer(c->adapter, &msg, 1); - reg2[0] = 0x80; - reg2[1] = 0x28; + tuner_reg[0] = 0x80; + tuner_reg[1] = 0x28; i2c_transfer(c->adapter, &msg, 1); - reg2[0] = 0xb0; - reg2[1] = 0x01; + tuner_reg[0] = 0xb0; + tuner_reg[1] = 0x01; i2c_transfer(c->adapter, &msg, 1); - reg2[0] = 0xc0; - reg2[1] = 0x19 + (t->tda827x_lpsel << 1); + tuner_reg[0] = 0xc0; + tuner_reg[1] = 0x19 + (t->tda827x_lpsel << 1); i2c_transfer(c->adapter, &msg, 1); } @@ -319,7 +367,9 @@ static int tda8290_tune(struct i2c_client *c, u16 ifc, unsigned int freq) unsigned char addr_pll_stat = 0x1b; unsigned char adc_sat, agc_stat, pll_stat; + int i; + tuner_dbg("tda827xa config is 0x%02x\n", t->config); i2c_master_send(c, easy_mode, 2); i2c_master_send(c, agc_out_on, 2); i2c_master_send(c, soft_reset, 2); @@ -340,17 +390,22 @@ static int tda8290_tune(struct i2c_client *c, u16 ifc, unsigned int freq) tda827xa_tune(c, ifc, freq); else tda827x_tune(c, ifc, freq); + for (i = 0; i < 3; i++) { + i2c_master_send(c, &addr_pll_stat, 1); + i2c_master_recv(c, &pll_stat, 1); + if (pll_stat & 0x80) { + i2c_master_send(c, &addr_adc_sat, 1); + i2c_master_recv(c, &adc_sat, 1); + i2c_master_send(c, &addr_agc_stat, 1); + i2c_master_recv(c, &agc_stat, 1); + tuner_dbg("tda8290 is locked, AGC: %d\n", agc_stat); + break; + } else { + tuner_dbg("tda8290 not locked, no signal?\n"); + msleep(100); + } + } /* adjust headroom resp. gain */ - i2c_master_send(c, &addr_adc_sat, 1); - i2c_master_recv(c, &adc_sat, 1); - i2c_master_send(c, &addr_agc_stat, 1); - i2c_master_recv(c, &agc_stat, 1); - i2c_master_send(c, &addr_pll_stat, 1); - i2c_master_recv(c, &pll_stat, 1); - if (pll_stat & 0x80) - tuner_dbg("tda8290 is locked, AGC: %d\n", agc_stat); - else - tuner_dbg("tda8290 not locked, no signal?\n"); if ((agc_stat > 115) || (!(pll_stat & 0x80) && (adc_sat < 20))) { tuner_dbg("adjust gain, step 1. Agc: %d, ADC stat: %d, lock: %d\n", agc_stat, adc_sat, pll_stat & 0x80); @@ -407,7 +462,6 @@ static void set_audio(struct tuner *t) char* mode; t->tda827x_lpsel = 0; - mode = "xx"; if (t->std & V4L2_STD_MN) { t->sgIF = 92; t->tda8290_easy_mode = 0x01; @@ -437,8 +491,12 @@ static void set_audio(struct tuner *t) t->sgIF = 20; t->tda8290_easy_mode = 0x40; mode = "LC"; + } else { + t->sgIF = 124; + t->tda8290_easy_mode = 0x10; + mode = "xx"; } - tuner_dbg("setting tda8290 to system %s\n", mode); + tuner_dbg("setting tda8290 to system %s\n", mode); } static void set_tv_freq(struct i2c_client *c, unsigned int freq) @@ -487,11 +545,16 @@ static void standby(struct i2c_client *c) static void tda8290_init_if(struct i2c_client *c) { + struct tuner *t = i2c_get_clientdata(c); unsigned char set_VS[] = { 0x30, 0x6F }; + unsigned char set_GP00_CF[] = { 0x20, 0x01 }; unsigned char set_GP01_CF[] = { 0x20, 0x0B }; + if ((t->config == 1) || (t->config == 2)) + i2c_master_send(c, set_GP00_CF, 2); + else + i2c_master_send(c, set_GP01_CF, 2); i2c_master_send(c, set_VS, 2); - i2c_master_send(c, set_GP01_CF, 2); } static void tda8290_init_tuner(struct i2c_client *c) @@ -576,6 +639,7 @@ int tda8290_init(struct i2c_client *c) t->has_signal = has_signal; t->standby = standby; t->tda827x_lpsel = 0; + t->mode = V4L2_TUNER_ANALOG_TV; tda8290_init_tuner(c); tda8290_init_if(c); diff --git a/drivers/media/video/tda9875.c b/drivers/media/video/tda9875.c index 00f0e8b6e03..d1104417087 100644 --- a/drivers/media/video/tda9875.c +++ b/drivers/media/video/tda9875.c @@ -27,7 +27,6 @@ #include <linux/videodev.h> #include <media/v4l2-common.h> #include <linux/i2c.h> -#include <linux/i2c-algo-bit.h> #include <linux/init.h> diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c index 7be73e3763d..505591a7abe 100644 --- a/drivers/media/video/tuner-core.c +++ b/drivers/media/video/tuner-core.c @@ -144,7 +144,8 @@ static void set_freq(struct i2c_client *c, unsigned long freq) } static void set_type(struct i2c_client *c, unsigned int type, - unsigned int new_mode_mask) + unsigned int new_mode_mask, unsigned int new_config, + int (*tuner_callback) (void *dev, int command,int arg)) { struct tuner *t = i2c_get_clientdata(c); unsigned char buffer[4]; @@ -159,15 +160,20 @@ static void set_type(struct i2c_client *c, unsigned int type, return; } + t->type = type; + t->config = new_config; + if (tuner_callback != NULL) { + tuner_dbg("defining GPIO callback\n"); + t->tuner_callback = tuner_callback; + } + /* This code detects calls by card attach_inform */ if (NULL == t->i2c.dev.driver) { tuner_dbg ("tuner 0x%02x: called during i2c_client register by adapter's attach_inform\n", c->addr); - t->type=type; return; } - t->type = type; switch (t->type) { case TUNER_MT2032: microtune_init(c); @@ -234,10 +240,11 @@ static void set_addr(struct i2c_client *c, struct tuner_setup *tun_setup) tuner_dbg("set addr for type %i\n", t->type); - if ( t->type == UNSET && ((tun_setup->addr == ADDR_UNSET && - (t->mode_mask & tun_setup->mode_mask)) || - tun_setup->addr == c->addr)) { - set_type(c, tun_setup->type, tun_setup->mode_mask); + if ( (t->type == UNSET && ((tun_setup->addr == ADDR_UNSET) && + (t->mode_mask & tun_setup->mode_mask))) || + (tun_setup->addr == c->addr)) { + set_type(c, tun_setup->type, tun_setup->mode_mask, + tun_setup->config, tun_setup->tuner_callback); } } @@ -496,7 +503,7 @@ static int tuner_attach(struct i2c_adapter *adap, int addr, int kind) register_client: tuner_info("chip found @ 0x%x (%s)\n", addr << 1, adap->name); i2c_attach_client (&t->i2c); - set_type (&t->i2c,t->type, t->mode_mask); + set_type (&t->i2c,t->type, t->mode_mask, t->config, t->tuner_callback); return 0; } @@ -576,10 +583,11 @@ static int tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) switch (cmd) { /* --- configuration --- */ case TUNER_SET_TYPE_ADDR: - tuner_dbg ("Calling set_type_addr for type=%d, addr=0x%02x, mode=0x%02x\n", + tuner_dbg ("Calling set_type_addr for type=%d, addr=0x%02x, mode=0x%02x, config=0x%02x\n", ((struct tuner_setup *)arg)->type, ((struct tuner_setup *)arg)->addr, - ((struct tuner_setup *)arg)->mode_mask); + ((struct tuner_setup *)arg)->mode_mask, + ((struct tuner_setup *)arg)->config); set_addr(client, (struct tuner_setup *)arg); break; @@ -804,9 +812,8 @@ static int tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) return 0; } -static int tuner_suspend(struct device *dev, pm_message_t state) +static int tuner_suspend(struct i2c_client *c, pm_message_t state) { - struct i2c_client *c = container_of (dev, struct i2c_client, dev); struct tuner *t = i2c_get_clientdata (c); tuner_dbg ("suspend\n"); @@ -814,9 +821,8 @@ static int tuner_suspend(struct device *dev, pm_message_t state) return 0; } -static int tuner_resume(struct device *dev) +static int tuner_resume(struct i2c_client *c) { - struct i2c_client *c = container_of (dev, struct i2c_client, dev); struct tuner *t = i2c_get_clientdata (c); tuner_dbg ("resume\n"); @@ -837,10 +843,10 @@ static struct i2c_driver driver = { .attach_adapter = tuner_probe, .detach_client = tuner_detach, .command = tuner_command, + .suspend = tuner_suspend, + .resume = tuner_resume, .driver = { .name = "tuner", - .suspend = tuner_suspend, - .resume = tuner_resume, }, }; static struct i2c_client client_template = { diff --git a/drivers/media/video/tvaudio.c b/drivers/media/video/tvaudio.c index d506dfaa45a..a2da5d2afff 100644 --- a/drivers/media/video/tvaudio.c +++ b/drivers/media/video/tvaudio.c @@ -25,7 +25,6 @@ #include <linux/slab.h> #include <linux/videodev.h> #include <linux/i2c.h> -#include <linux/i2c-algo-bit.h> #include <linux/init.h> #include <linux/smp_lock.h> #include <linux/kthread.h> @@ -33,6 +32,7 @@ #include <media/tvaudio.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <media/i2c-addr.h> @@ -1775,6 +1775,9 @@ static int chip_command(struct i2c_client *client, /* the thread will call checkmode() later */ } break; + + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_TVAUDIO, 0); } return 0; } diff --git a/drivers/media/video/tveeprom.c b/drivers/media/video/tveeprom.c index 4e7c1fa668d..a1136da74ba 100644 --- a/drivers/media/video/tveeprom.c +++ b/drivers/media/video/tveeprom.c @@ -163,7 +163,7 @@ hauppauge_tuner[] = /* 60-69 */ { TUNER_PHILIPS_FM1216ME_MK3, "LG S001D MK3"}, { TUNER_ABSENT, "LG M001D MK3"}, - { TUNER_ABSENT, "LG S701D MK3"}, + { TUNER_PHILIPS_FM1216ME_MK3, "LG S701D MK3"}, { TUNER_ABSENT, "LG M701D MK3"}, { TUNER_ABSENT, "Temic 4146FM5"}, { TUNER_ABSENT, "Temic 4136FY5"}, @@ -229,6 +229,36 @@ hauppauge_tuner[] = /* 120-129 */ { TUNER_ABSENT, "Xceive XC3028"}, { TUNER_ABSENT, "Philips FQ1216LME MK5"}, + { TUNER_ABSENT, "Philips FQD1216LME"}, + { TUNER_ABSENT, "Conexant CX24118A"}, + { TUNER_ABSENT, "TCL DMF11WIP"}, + { TUNER_ABSENT, "TCL MFNM05_4H_E"}, + { TUNER_ABSENT, "TCL MNM05_4H_E"}, + { TUNER_ABSENT, "TCL MPE05_2H_E"}, + { TUNER_ABSENT, "TCL MQNM05_4_U"}, + { TUNER_ABSENT, "TCL M2523_5NH_E"}, + /* 130-139 */ + { TUNER_ABSENT, "TCL M2523_3DBH_E"}, + { TUNER_ABSENT, "TCL M2523_3DIH_E"}, + { TUNER_ABSENT, "TCL MFPE05_2_U"}, + { TUNER_ABSENT, "Philips FMD1216MEX"}, + { TUNER_ABSENT, "Philips FRH2036B"}, + { TUNER_ABSENT, "Panasonic ENGF75_01GF"}, + { TUNER_ABSENT, "MaxLinear MXL5005"}, + { TUNER_ABSENT, "MaxLinear MXL5003"}, + { TUNER_ABSENT, "Xceive XC2028"}, + { TUNER_ABSENT, "Microtune MT2131"}, + /* 140-149 */ + { TUNER_ABSENT, "Philips 8275A_8295"}, + { TUNER_ABSENT, "TCL MF02GIP_5N_E"}, + { TUNER_ABSENT, "TCL MF02GIP_3DB_E"}, + { TUNER_ABSENT, "TCL MF02GIP_3DI_E"}, + { TUNER_ABSENT, "Microtune MT2266"}, + { TUNER_ABSENT, "TCL MF10WPP_4N_E"}, + { TUNER_ABSENT, "LG TAPQ_H702F"}, + { TUNER_ABSENT, "TCL M09WPP_4N_E"}, + { TUNER_ABSENT, "MaxLinear MXL5005_v2"}, + { TUNER_ABSENT, "Philips 18271_8295"}, }; static struct HAUPPAUGE_AUDIOIC @@ -280,11 +310,16 @@ audioIC[] = {AUDIO_CHIP_INTERNAL, "CX883"}, {AUDIO_CHIP_INTERNAL, "CX882"}, {AUDIO_CHIP_INTERNAL, "CX25840"}, - /* 35-38 */ + /* 35-39 */ {AUDIO_CHIP_INTERNAL, "CX25841"}, {AUDIO_CHIP_INTERNAL, "CX25842"}, {AUDIO_CHIP_INTERNAL, "CX25843"}, {AUDIO_CHIP_INTERNAL, "CX23418"}, + {AUDIO_CHIP_INTERNAL, "CX23885"}, + /* 40-42 */ + {AUDIO_CHIP_INTERNAL, "CX23888"}, + {AUDIO_CHIP_INTERNAL, "SAA7131"}, + {AUDIO_CHIP_INTERNAL, "CX23887"}, }; /* This list is supplied by Hauppauge. Thanks! */ @@ -301,8 +336,10 @@ static const char *decoderIC[] = { "CX880", "CX881", "CX883", "SAA7111", "SAA7113", /* 25-29 */ "CX882", "TVP5150A", "CX25840", "CX25841", "CX25842", - /* 30-31 */ - "CX25843", "CX23418", + /* 30-34 */ + "CX25843", "CX23418", "NEC61153", "CX23885", "CX23888", + /* 35-37 */ + "SAA7131", "CX25837", "CX23887" }; static int hasRadioTuner(int tunerType) diff --git a/drivers/media/video/upd64031a.c b/drivers/media/video/upd64031a.c index 28d1133a3b7..0b2a961efd2 100644 --- a/drivers/media/video/upd64031a.c +++ b/drivers/media/video/upd64031a.c @@ -27,6 +27,7 @@ #include <linux/i2c.h> #include <linux/videodev2.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <media/upd64031a.h> // --------------------- read registers functions define ----------------------- @@ -179,6 +180,9 @@ static int upd64031a_command(struct i2c_client *client, unsigned int cmd, void * } #endif + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_UPD64031A, 0); + default: break; } diff --git a/drivers/media/video/upd64083.c b/drivers/media/video/upd64083.c index fe38224150d..401bd21f46e 100644 --- a/drivers/media/video/upd64083.c +++ b/drivers/media/video/upd64083.c @@ -26,6 +26,7 @@ #include <linux/i2c.h> #include <linux/videodev2.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #include <media/upd64083.h> MODULE_DESCRIPTION("uPD64083 driver"); @@ -155,6 +156,10 @@ static int upd64083_command(struct i2c_client *client, unsigned int cmd, void *a break; } #endif + + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_UPD64083, 0); + default: break; } diff --git a/drivers/media/video/usbvideo/usbvideo.c b/drivers/media/video/usbvideo/usbvideo.c index d34d8c8b737..687f026753b 100644 --- a/drivers/media/video/usbvideo/usbvideo.c +++ b/drivers/media/video/usbvideo/usbvideo.c @@ -628,24 +628,21 @@ EXPORT_SYMBOL(usbvideo_HexDump); /* ******************************************************************** */ /* XXX: this piece of crap really wants some error handling.. */ -static void usbvideo_ClientIncModCount(struct uvd *uvd) +static int usbvideo_ClientIncModCount(struct uvd *uvd) { if (uvd == NULL) { err("%s: uvd == NULL", __FUNCTION__); - return; + return -EINVAL; } if (uvd->handle == NULL) { err("%s: uvd->handle == NULL", __FUNCTION__); - return; - } - if (uvd->handle->md_module == NULL) { - err("%s: uvd->handle->md_module == NULL", __FUNCTION__); - return; + return -EINVAL; } if (!try_module_get(uvd->handle->md_module)) { err("%s: try_module_get() == 0", __FUNCTION__); - return; + return -ENODEV; } + return 0; } static void usbvideo_ClientDecModCount(struct uvd *uvd) @@ -712,8 +709,6 @@ int usbvideo_register( cams->num_cameras = num_cams; cams->cam = (struct uvd *) &cams[1]; cams->md_module = md; - if (cams->md_module == NULL) - warn("%s: module == NULL!", __FUNCTION__); mutex_init(&cams->lock); /* to 1 == available */ for (i = 0; i < num_cams; i++) { @@ -1119,7 +1114,8 @@ static int usbvideo_v4l_open(struct inode *inode, struct file *file) if (uvd->debug > 1) info("%s($%p)", __FUNCTION__, dev); - usbvideo_ClientIncModCount(uvd); + if (0 < usbvideo_ClientIncModCount(uvd)) + return -ENODEV; mutex_lock(&uvd->lock); if (uvd->user) { diff --git a/drivers/media/video/usbvision/usbvision-cards.c b/drivers/media/video/usbvision/usbvision-cards.c index a40e5838515..13f69fe6360 100644 --- a/drivers/media/video/usbvision/usbvision-cards.c +++ b/drivers/media/video/usbvision/usbvision-cards.c @@ -1,6 +1,6 @@ /* - * USBVISION.H - * usbvision header file + * usbvision-cards.c + * usbvision cards definition file * * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de> * @@ -28,129 +28,1060 @@ #include <media/v4l2-dev.h> #include <media/tuner.h> #include "usbvision.h" +#include "usbvision-cards.h" /* Supported Devices: A table for usbvision.c*/ struct usbvision_device_data_st usbvision_device_data[] = { - {0xFFF0, 0xFFF0, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, -1, -1, "Custom Dummy USBVision Device"}, - {0x0A6F, 0x0400, -1, CODEC_SAA7113, 4, V4L2_STD_NTSC, 1, 0, 1, 0, 0, -1, -1, -1, -1, -1, "Xanboo"}, - {0x050D, 0x0208, -1, CODEC_SAA7113, 2, V4L2_STD_PAL, 1, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Belkin USBView II"}, - {0x0571, 0x0002, 0, CODEC_SAA7111, 2, V4L2_STD_PAL, 0, 0, 1, 0, 0, -1, -1, -1, -1, 7, "echoFX InterView Lite"}, - {0x0573, 0x0003, -1, CODEC_SAA7111, 2, V4L2_STD_NTSC, 1, 0, 1, 0, 0, -1, -1, -1, -1, -1, "USBGear USBG-V1 resp. HAMA USB"}, - {0x0573, 0x0400, -1, CODEC_SAA7113, 4, V4L2_STD_NTSC, 0, 0, 1, 0, 0, -1, -1, 0, 3, 7, "D-Link V100"}, - {0x0573, 0x2000, -1, CODEC_SAA7111, 2, V4L2_STD_NTSC, 1, 0, 1, 0, 0, -1, -1, -1, -1, -1, "X10 USB Camera"}, - {0x0573, 0x2d00, -1, CODEC_SAA7111, 2, V4L2_STD_PAL, 1, 0, 1, 0, 0, -1, -1, -1, 3, 7, "Osprey 50"}, - {0x0573, 0x2d01, -1, CODEC_SAA7113, 2, V4L2_STD_NTSC, 0, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Hauppauge USB-Live Model 600"}, - {0x0573, 0x2101, -1, CODEC_SAA7113, 2, V4L2_STD_PAL, 2, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Zoran Co. PMD (Nogatech) AV-grabber Manhattan"}, - {0x0573, 0x4100, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, 20, -1, "Nogatech USB-TV (NTSC) FM"}, - {0x0573, 0x4110, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, 20, -1, "PNY USB-TV (NTSC) FM"}, - {0x0573, 0x4450, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "PixelView PlayTv-USB PRO (PAL) FM"}, - {0x0573, 0x4550, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "ZTV ZT-721 2.4GHz USB A/V Receiver"}, - {0x0573, 0x4d00, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 0, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, 20, -1, "Hauppauge WinTv-USB USA"}, - {0x0573, 0x4d01, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 0, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB"}, - {0x0573, 0x4d02, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 0, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB (NTSC)"}, - {0x0573, 0x4d03, -1, CODEC_SAA7111, 3, V4L2_STD_SECAM, 1, 0, 1, 1, TUNER_PHILIPS_SECAM, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB (SECAM) "}, - {0x0573, 0x4d10, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB (NTSC) FM"}, - {0x0573, 0x4d11, -1, CODEC_SAA7111, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB (PAL) FM"}, - {0x0573, 0x4d12, -1, CODEC_SAA7111, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB (PAL) FM"}, - {0x0573, 0x4d2a, 0, CODEC_SAA7113, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_MICROTUNE_4049FM5, -1, -1, 0, 3, 7, "Hauppauge WinTv USB (NTSC) FM Model 602 40201 Rev B285"}, - {0x0573, 0x4d2b, 0, CODEC_SAA7113, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_MICROTUNE_4049FM5, -1, -1, 0, 3, 7, "Hauppauge WinTv USB (NTSC) FM Model 602 40201 Rev B282"}, - {0x0573, 0x4d2c, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_PHILIPS_FM1216ME_MK3, -1, -1, 0, 3, 7, "Hauppauge WinTv USB (PAL/SECAM) 40209 Rev E1A5"}, - {0x0573, 0x4d20, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB II (PAL) FM Model 40201 Rev B226"}, - {0x0573, 0x4d21, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB II (PAL)"}, - {0x0573, 0x4d22, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB II (PAL) MODEL 566"}, - {0x0573, 0x4d23, -1, CODEC_SAA7113, 3, V4L2_STD_SECAM, 1, 0, 1, 1, TUNER_PHILIPS_SECAM, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB (SECAM) 4D23"}, - {0x0573, 0x4d25, -1, CODEC_SAA7113, 3, V4L2_STD_SECAM, 1, 0, 1, 1, TUNER_PHILIPS_SECAM, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB (SECAM) Model 40209 Rev B234"}, - {0x0573, 0x4d26, -1, CODEC_SAA7113, 3, V4L2_STD_SECAM, 1, 0, 1, 1, TUNER_PHILIPS_SECAM, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB (SECAM) Model 40209 Rev B243"}, - {0x0573, 0x4d27, -1, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_ALPS_TSBE1_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB Model 40204 Rev B281"}, - {0x0573, 0x4d28, -1, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_ALPS_TSBE1_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB Model 40204 Rev B283"}, - {0x0573, 0x4d29, -1, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB Model 40205 Rev B298"}, - {0x0573, 0x4d30, -1, CODEC_SAA7113, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB FM Model 40211 Rev B123"}, - {0x0573, 0x4d31, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB III (PAL) FM Model 568"}, - {0x0573, 0x4d32, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB III (PAL) FM Model 573"}, - {0x0573, 0x4d35, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_MICROTUNE_4049FM5, -1, -1, 0, 3, 7, "Hauppauge WinTv-USB III (PAL) FM Model 40219 Rev B252"}, - {0x0573, 0x4d37, 0, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_FM1216ME_MK3, -1, -1, 0, 3, 7, "Hauppauge WinTV USB device Model 40219 Rev E189"}, - {0x0768, 0x0006, -1, CODEC_SAA7113, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, 5, 5, -1, "Camtel Technology USB TV Genie Pro FM Model TVB330"}, - {0x07d0, 0x0001, -1, CODEC_SAA7113, 2, V4L2_STD_PAL, 0, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Digital Video Creator I"}, - {0x07d0, 0x0002, -1, CODEC_SAA7111, 2, V4L2_STD_NTSC, 0, 0, 1, 0, 0, -1, -1, 82, 20, 7, "Global Village GV-007 (NTSC)"}, - {0x07d0, 0x0003, 0, CODEC_SAA7113, 2, V4L2_STD_NTSC, 0, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Dazzle Fusion Model DVC-50 Rev 1 (NTSC)"}, - {0x07d0, 0x0004, 0, CODEC_SAA7113, 2, V4L2_STD_PAL, 0, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Dazzle Fusion Model DVC-80 Rev 1 (PAL)"}, - {0x07d0, 0x0005, 0, CODEC_SAA7113, 2, V4L2_STD_SECAM, 0, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Dazzle Fusion Model DVC-90 Rev 1 (SECAM)"}, - {0x2304, 0x010d, -1, CODEC_SAA7111, 3, V4L2_STD_PAL, 1, 0, 0, 1, TUNER_TEMIC_4066FY5_PAL_I, -1, -1, -1, -1, -1, "Pinnacle Studio PCTV USB (PAL)"}, - {0x2304, 0x0109, -1, CODEC_SAA7111, 3, V4L2_STD_SECAM, 1, 0, 1, 1, TUNER_PHILIPS_SECAM, -1, -1, -1, -1, -1, "Pinnacle Studio PCTV USB (SECAM)"}, - {0x2304, 0x0110, -1, CODEC_SAA7111, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_PHILIPS_PAL, -1, -1,128, 23, -1, "Pinnacle Studio PCTV USB (PAL) FM"}, - {0x2304, 0x0111, -1, CODEC_SAA7111, 3, V4L2_STD_PAL, 1, 0, 1, 1, TUNER_PHILIPS_PAL, -1, -1, -1, -1, -1, "Miro PCTV USB"}, - {0x2304, 0x0112, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, -1, -1, "Pinnacle Studio PCTV USB (NTSC) FM"}, - {0x2304, 0x0210, -1, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_TEMIC_4009FR5_PAL, -1, -1, 0, 3, 7, "Pinnacle Studio PCTV USB (PAL) FM"}, - {0x2304, 0x0212, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 1, 1, 1, TUNER_TEMIC_4039FR5_NTSC, -1, -1, 0, 3, 7, "Pinnacle Studio PCTV USB (NTSC) FM"}, - {0x2304, 0x0214, -1, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_TEMIC_4009FR5_PAL, -1, -1, 0, 3, 7, "Pinnacle Studio PCTV USB (PAL) FM"}, - {0x2304, 0x0300, -1, CODEC_SAA7113, 2, V4L2_STD_NTSC, 1, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Pinnacle Studio Linx Video input cable (NTSC)"}, - {0x2304, 0x0301, -1, CODEC_SAA7113, 2, V4L2_STD_PAL, 1, 0, 1, 0, 0, -1, -1, 0, 3, 7, "Pinnacle Studio Linx Video input cable (PAL)"}, - {0x2304, 0x0419, -1, CODEC_SAA7113, 3, V4L2_STD_PAL, 1, 1, 1, 1, TUNER_TEMIC_4009FR5_PAL, -1, -1, 0, 3, 7, "Pinnacle PCTV Bungee USB (PAL) FM"}, - {0x2400, 0x4200, -1, CODEC_SAA7111, 3, V4L2_STD_NTSC, 1, 0, 1, 1, TUNER_PHILIPS_NTSC_M, -1, -1, -1, -1, -1, "Hauppauge WinTv-USB"}, - {} /* Terminating entry */ + [XANBOO] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 4, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Xanboo", + }, + [BELKIN_VIDEOBUS_II] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Belkin USB VideoBus II Adapter", + }, + [BELKIN_VIDEOBUS] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Belkin Components USB VideoBus", + }, + [BELKIN_USB_VIDEOBUS_II] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Belkin USB VideoBus II", + }, + [ECHOFX_INTERVIEW_LITE] = { + .Interface = 0, + .Codec = CODEC_SAA7111, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = -1, + .Y_Offset = -1, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "echoFX InterView Lite", + }, + [USBGEAR_USBG_V1] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "USBGear USBG-V1 resp. HAMA USB", + }, + [D_LINK_V100] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 4, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "D-Link V100", + }, + [X10_USB_CAMERA] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "X10 USB Camera", + }, + [HPG_WINTV_LIVE_PAL_BG] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = -1, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Live (PAL B/G)", + }, + [HPG_WINTV_LIVE_PRO_NTSC_MN] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Live Pro (NTSC M/N)", + }, + [ZORAN_PMD_NOGATECH] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 2, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Zoran Co. PMD (Nogatech) AV-grabber Manhattan", + }, + [NOGATECH_USB_TV_NTSC_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = -1, + .Y_Offset = 20, + .ModelString = "Nogatech USB-TV (NTSC) FM", + }, + [PNY_USB_TV_NTSC_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = -1, + .Y_Offset = 20, + .ModelString = "PNY USB-TV (NTSC) FM", + }, + [PV_PLAYTV_USB_PRO_PAL_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "PixelView PlayTv-USB PRO (PAL) FM", + }, + [ZT_721] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "ZTV ZT-721 2.4GHz USB A/V Receiver", + }, + [HPG_WINTV_NTSC_MN] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = -1, + .Y_Offset = 20, + .ModelString = "Hauppauge WinTV USB (NTSC M/N)", + }, + [HPG_WINTV_PAL_BG] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL B/G)", + }, + [HPG_WINTV_PAL_I] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL I)", + }, + [HPG_WINTV_PAL_SECAM_L] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_SECAM, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_SECAM, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL/SECAM L)", + }, + [HPG_WINTV_PAL_D_K] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL D/K)", + }, + [HPG_WINTV_NTSC_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (NTSC FM)", + }, + [HPG_WINTV_PAL_BG_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL B/G FM)", + }, + [HPG_WINTV_PAL_I_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL I FM)", + }, + [HPG_WINTV_PAL_D_K_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTV USB (PAL D/K FM)", + }, + [HPG_WINTV_PRO_NTSC_MN] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_MICROTUNE_4049FM5, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (NTSC M/N)", + }, + [HPG_WINTV_PRO_NTSC_MN_V2] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_MICROTUNE_4049FM5, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (NTSC M/N) V2", + }, + [HPG_WINTV_PRO_PAL] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_FM1216ME_MK3, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL/SECAM B/G/I/D/K/L)", + }, + [HPG_WINTV_PRO_NTSC_MN_V3] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (NTSC M/N) V3", + }, + [HPG_WINTV_PRO_PAL_BG] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL B/G)", + }, + [HPG_WINTV_PRO_PAL_I] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL I)", + }, + [HPG_WINTV_PRO_PAL_SECAM_L] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_SECAM, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_SECAM, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL/SECAM L)", + }, + [HPG_WINTV_PRO_PAL_D_K] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL D/K)", + }, + [HPG_WINTV_PRO_PAL_SECAM] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_SECAM, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_SECAM, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL/SECAM BGDK/I/L)", + }, + [HPG_WINTV_PRO_PAL_SECAM_V2] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_SECAM, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_SECAM, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL/SECAM BGDK/I/L) V2", + }, + [HPG_WINTV_PRO_PAL_BG_V2] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_ALPS_TSBE1_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL B/G) V2", + }, + [HPG_WINTV_PRO_PAL_BG_D_K] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_ALPS_TSBE1_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL B/G,D/K)", + }, + [HPG_WINTV_PRO_PAL_I_D_K] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL I,D/K)", + }, + [HPG_WINTV_PRO_NTSC_MN_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (NTSC M/N FM)", + }, + [HPG_WINTV_PRO_PAL_BG_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL B/G FM)", + }, + [HPG_WINTV_PRO_PAL_I_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL I FM)", + }, + [HPG_WINTV_PRO_PAL_D_K_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL D/K FM)", + }, + [HPG_WINTV_PRO_TEMIC_PAL_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_MICROTUNE_4049FM5, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (Temic PAL/SECAM B/G/I/D/K/L FM)", + }, + [HPG_WINTV_PRO_TEMIC_PAL_BG_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_MICROTUNE_4049FM5, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (Temic PAL B/G FM)", + }, + [HPG_WINTV_PRO_PAL_FM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_FM1216ME_MK3, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (PAL/SECAM B/G/I/D/K/L FM)", + }, + [HPG_WINTV_PRO_NTSC_MN_FM_V2] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Hauppauge WinTV USB Pro (NTSC M/N FM) V2", + }, + [CAMTEL_TVB330] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = 5, + .Y_Offset = 5, + .ModelString = "Camtel Technology USB TV Genie Pro FM Model TVB330", + }, + [DIGITAL_VIDEO_CREATOR_I] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Digital Video Creator I", + }, + [GLOBAL_VILLAGE_GV_007_NTSC] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 82, + .Y_Offset = 20, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Global Village GV-007 (NTSC)", + }, + [DAZZLE_DVC_50_REV_1_NTSC] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Dazzle Fusion Model DVC-50 Rev 1 (NTSC)", + }, + [DAZZLE_DVC_80_REV_1_PAL] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Dazzle Fusion Model DVC-80 Rev 1 (PAL)", + }, + [DAZZLE_DVC_90_REV_1_SECAM] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_SECAM, + .AudioChannels = 0, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Dazzle Fusion Model DVC-90 Rev 1 (SECAM)", + }, + [ESKAPE_LABS_MYTV2GO] = { + .Interface = 0, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_FM1216ME_MK3, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Eskape Labs MyTV2Go", + }, + [PINNA_PCTV_USB_PAL] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 0, + .Tuner = 1, + .TunerType = TUNER_TEMIC_4066FY5_PAL_I, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Pinnacle Studio PCTV USB (PAL)", + }, + [PINNA_PCTV_USB_SECAM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_SECAM, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_SECAM, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Pinnacle Studio PCTV USB (SECAM)", + }, + [PINNA_PCTV_USB_PAL_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = 128, + .Y_Offset = 23, + .ModelString = "Pinnacle Studio PCTV USB (PAL) FM", + }, + [MIRO_PCTV_USB] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_PAL, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Miro PCTV USB", + }, + [PINNA_PCTV_USB_NTSC_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Pinnacle Studio PCTV USB (NTSC) FM", + }, + [PINNA_PCTV_USB_PAL_FM_V2] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_TEMIC_4009FR5_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Pinnacle Studio PCTV USB (PAL) FM V2", + }, + [PINNA_PCTV_USB_NTSC_FM_V2] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_TEMIC_4039FR5_NTSC, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Pinnacle Studio PCTV USB (NTSC) FM V2", + }, + [PINNA_PCTV_USB_PAL_FM_V3] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_TEMIC_4009FR5_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Pinnacle Studio PCTV USB (PAL) FM V3", + }, + [PINNA_LINX_VD_IN_CAB_NTSC] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Pinnacle Studio Linx Video input cable (NTSC)", + }, + [PINNA_LINX_VD_IN_CAB_PAL] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 2, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 0, + .TunerType = 0, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Pinnacle Studio Linx Video input cable (PAL)", + }, + [PINNA_PCTV_BUNGEE_PAL_FM] = { + .Interface = -1, + .Codec = CODEC_SAA7113, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_PAL, + .AudioChannels = 1, + .Radio = 1, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_TEMIC_4009FR5_PAL, + .X_Offset = 0, + .Y_Offset = 3, + .Dvi_yuv_override = 1, + .Dvi_yuv = 7, + .ModelString = "Pinnacle PCTV Bungee USB (PAL) FM", + }, + [HPG_WINTV] = { + .Interface = -1, + .Codec = CODEC_SAA7111, + .VideoChannels = 3, + .VideoNorm = V4L2_STD_NTSC, + .AudioChannels = 1, + .Radio = 0, + .vbi = 1, + .Tuner = 1, + .TunerType = TUNER_PHILIPS_NTSC_M, + .X_Offset = -1, + .Y_Offset = -1, + .ModelString = "Hauppauge WinTv-USB", + }, }; +const int usbvision_device_data_size=ARRAY_SIZE(usbvision_device_data); /* Supported Devices */ struct usb_device_id usbvision_table [] = { - { USB_DEVICE(0xFFF0, 0xFFF0) }, /* Custom Dummy USBVision Device */ - { USB_DEVICE(0x0A6F, 0x0400) }, /* Xanboo */ - { USB_DEVICE(0x050d, 0x0208) }, /* Belkin USBView II */ - { USB_DEVICE(0x0571, 0x0002) }, /* echoFX InterView Lite */ - { USB_DEVICE(0x0573, 0x0003) }, /* USBGear USBG-V1 */ - { USB_DEVICE(0x0573, 0x0400) }, /* D-Link V100 */ - { USB_DEVICE(0x0573, 0x2000) }, /* X10 USB Camera */ - { USB_DEVICE(0x0573, 0x2d00) }, /* Osprey 50 */ - { USB_DEVICE(0x0573, 0x2d01) }, /* Hauppauge USB-Live Model 600 */ - { USB_DEVICE(0x0573, 0x2101) }, /* Zoran Co. PMD (Nogatech) AV-grabber Manhattan */ - { USB_DEVICE(0x0573, 0x4100) }, /* Nogatech USB-TV FM (NTSC) */ - { USB_DEVICE(0x0573, 0x4110) }, /* PNY USB-TV (NTSC) FM */ - { USB_DEVICE(0x0573, 0x4450) }, /* PixelView PlayTv-USB PRO (PAL) FM */ - { USB_DEVICE(0x0573, 0x4550) }, /* ZTV ZT-721 2.4GHz USB A/V Receiver */ - { USB_DEVICE(0x0573, 0x4d00) }, /* Hauppauge WinTv-USB USA */ - { USB_DEVICE(0x0573, 0x4d01) }, /* Hauppauge WinTv-USB */ - { USB_DEVICE(0x0573, 0x4d02) }, /* Hauppauge WinTv-USB UK */ - { USB_DEVICE(0x0573, 0x4d03) }, /* Hauppauge WinTv-USB France */ - { USB_DEVICE(0x0573, 0x4d10) }, /* Hauppauge WinTv-USB with FM USA radio */ - { USB_DEVICE(0x0573, 0x4d11) }, /* Hauppauge WinTv-USB (PAL) with FM radio */ - { USB_DEVICE(0x0573, 0x4d12) }, /* Hauppauge WinTv-USB UK with FM Radio */ - { USB_DEVICE(0x0573, 0x4d2a) }, /* Hauppague WinTv USB Model 602 40201 Rev B285 */ - { USB_DEVICE(0x0573, 0x4d2b) }, /* Hauppague WinTv USB Model 602 40201 Rev B282 */ - { USB_DEVICE(0x0573, 0x4d2c) }, /* Hauppague WinTv USB Model 40209 Rev. E1A5 PAL*/ - { USB_DEVICE(0x0573, 0x4d20) }, /* Hauppauge WinTv-USB II (PAL) FM Model 40201 Rev B226 */ - { USB_DEVICE(0x0573, 0x4d21) }, /* Hauppauge WinTv-USB II (PAL) with FM radio*/ - { USB_DEVICE(0x0573, 0x4d22) }, /* Hauppauge WinTv-USB II (PAL) Model 566 */ - { USB_DEVICE(0x0573, 0x4d23) }, /* Hauppauge WinTv-USB France 4D23*/ - { USB_DEVICE(0x0573, 0x4d25) }, /* Hauppauge WinTv-USB Model 40209 rev B234 */ - { USB_DEVICE(0x0573, 0x4d26) }, /* Hauppauge WinTv-USB Model 40209 Rev B243 */ - { USB_DEVICE(0x0573, 0x4d27) }, /* Hauppauge WinTv-USB Model 40204 Rev B281 */ - { USB_DEVICE(0x0573, 0x4d28) }, /* Hauppauge WinTv-USB Model 40204 Rev B283 */ - { USB_DEVICE(0x0573, 0x4d29) }, /* Hauppauge WinTv-USB Model 40205 Rev B298 */ - { USB_DEVICE(0x0573, 0x4d30) }, /* Hauppauge WinTv-USB FM Model 40211 Rev B123 */ - { USB_DEVICE(0x0573, 0x4d31) }, /* Hauppauge WinTv-USB III (PAL) with FM radio Model 568 */ - { USB_DEVICE(0x0573, 0x4d32) }, /* Hauppauge WinTv-USB III (PAL) FM Model 573 */ - { USB_DEVICE(0x0573, 0x4d35) }, /* Hauppauge WinTv-USB III (SECAM) FM Model 40219 Rev B252 */ - { USB_DEVICE(0x0573, 0x4d37) }, /* Hauppauge WinTv-USB Model 40219 Rev E189 */ - { USB_DEVICE(0x0768, 0x0006) }, /* Camtel Technology USB TV Genie Pro FM Model TVB330 */ - { USB_DEVICE(0x07d0, 0x0001) }, /* Digital Video Creator I */ - { USB_DEVICE(0x07d0, 0x0002) }, /* Global Village GV-007 (NTSC) */ - { USB_DEVICE(0x07d0, 0x0003) }, /* Dazzle Fusion Model DVC-50 Rev 1 (NTSC) */ - { USB_DEVICE(0x07d0, 0x0004) }, /* Dazzle Fusion Model DVC-80 Rev 1 (PAL) */ - { USB_DEVICE(0x07d0, 0x0005) }, /* Dazzle Fusion Model DVC-90 Rev 1 (SECAM) */ - { USB_DEVICE(0x2304, 0x010d) }, /* Pinnacle Studio PCTV USB (PAL) */ - { USB_DEVICE(0x2304, 0x0109) }, /* Pinnacle Studio PCTV USB (SECAM) */ - { USB_DEVICE(0x2304, 0x0110) }, /* Pinnacle Studio PCTV USB (PAL) */ - { USB_DEVICE(0x2304, 0x0111) }, /* Miro PCTV USB */ - { USB_DEVICE(0x2304, 0x0112) }, /* Pinnacle Studio PCTV USB (NTSC) with FM radio */ - { USB_DEVICE(0x2304, 0x0210) }, /* Pinnacle Studio PCTV USB (PAL) with FM radio */ - { USB_DEVICE(0x2304, 0x0212) }, /* Pinnacle Studio PCTV USB (NTSC) with FM radio */ - { USB_DEVICE(0x2304, 0x0214) }, /* Pinnacle Studio PCTV USB (PAL) with FM radio */ - { USB_DEVICE(0x2304, 0x0300) }, /* Pinnacle Studio Linx Video input cable (NTSC) */ - { USB_DEVICE(0x2304, 0x0301) }, /* Pinnacle Studio Linx Video input cable (PAL) */ - { USB_DEVICE(0x2304, 0x0419) }, /* Pinnacle PCTV Bungee USB (PAL) FM */ - { USB_DEVICE(0x2400, 0x4200) }, /* Hauppauge WinTv-USB2 Model 42012 */ - - { } /* Terminating entry */ + { USB_DEVICE(0x0a6f, 0x0400), .driver_info=XANBOO }, + { USB_DEVICE(0x050d, 0x0106), .driver_info=BELKIN_VIDEOBUS_II }, + { USB_DEVICE(0x050d, 0x0207), .driver_info=BELKIN_VIDEOBUS }, + { USB_DEVICE(0x050d, 0x0208), .driver_info=BELKIN_USB_VIDEOBUS_II }, + { USB_DEVICE(0x0571, 0x0002), .driver_info=ECHOFX_INTERVIEW_LITE }, + { USB_DEVICE(0x0573, 0x0003), .driver_info=USBGEAR_USBG_V1 }, + { USB_DEVICE(0x0573, 0x0400), .driver_info=D_LINK_V100 }, + { USB_DEVICE(0x0573, 0x2000), .driver_info=X10_USB_CAMERA }, + { USB_DEVICE(0x0573, 0x2d00), .driver_info=HPG_WINTV_LIVE_PAL_BG }, + { USB_DEVICE(0x0573, 0x2d01), .driver_info=HPG_WINTV_LIVE_PRO_NTSC_MN }, + { USB_DEVICE(0x0573, 0x2101), .driver_info=ZORAN_PMD_NOGATECH }, + { USB_DEVICE(0x0573, 0x4100), .driver_info=NOGATECH_USB_TV_NTSC_FM }, + { USB_DEVICE(0x0573, 0x4110), .driver_info=PNY_USB_TV_NTSC_FM }, + { USB_DEVICE(0x0573, 0x4450), .driver_info=PV_PLAYTV_USB_PRO_PAL_FM }, + { USB_DEVICE(0x0573, 0x4550), .driver_info=ZT_721 }, + { USB_DEVICE(0x0573, 0x4d00), .driver_info=HPG_WINTV_NTSC_MN }, + { USB_DEVICE(0x0573, 0x4d01), .driver_info=HPG_WINTV_PAL_BG }, + { USB_DEVICE(0x0573, 0x4d02), .driver_info=HPG_WINTV_PAL_I }, + { USB_DEVICE(0x0573, 0x4d03), .driver_info=HPG_WINTV_PAL_SECAM_L }, + { USB_DEVICE(0x0573, 0x4d04), .driver_info=HPG_WINTV_PAL_D_K }, + { USB_DEVICE(0x0573, 0x4d10), .driver_info=HPG_WINTV_NTSC_FM }, + { USB_DEVICE(0x0573, 0x4d11), .driver_info=HPG_WINTV_PAL_BG_FM }, + { USB_DEVICE(0x0573, 0x4d12), .driver_info=HPG_WINTV_PAL_I_FM }, + { USB_DEVICE(0x0573, 0x4d14), .driver_info=HPG_WINTV_PAL_D_K_FM }, + { USB_DEVICE(0x0573, 0x4d2a), .driver_info=HPG_WINTV_PRO_NTSC_MN }, + { USB_DEVICE(0x0573, 0x4d2b), .driver_info=HPG_WINTV_PRO_NTSC_MN_V2 }, + { USB_DEVICE(0x0573, 0x4d2c), .driver_info=HPG_WINTV_PRO_PAL }, + { USB_DEVICE(0x0573, 0x4d20), .driver_info=HPG_WINTV_PRO_NTSC_MN_V3 }, + { USB_DEVICE(0x0573, 0x4d21), .driver_info=HPG_WINTV_PRO_PAL_BG }, + { USB_DEVICE(0x0573, 0x4d22), .driver_info=HPG_WINTV_PRO_PAL_I }, + { USB_DEVICE(0x0573, 0x4d23), .driver_info=HPG_WINTV_PRO_PAL_SECAM_L }, + { USB_DEVICE(0x0573, 0x4d24), .driver_info=HPG_WINTV_PRO_PAL_D_K }, + { USB_DEVICE(0x0573, 0x4d25), .driver_info=HPG_WINTV_PRO_PAL_SECAM }, + { USB_DEVICE(0x0573, 0x4d26), .driver_info=HPG_WINTV_PRO_PAL_SECAM_V2 }, + { USB_DEVICE(0x0573, 0x4d27), .driver_info=HPG_WINTV_PRO_PAL_BG_V2 }, + { USB_DEVICE(0x0573, 0x4d28), .driver_info=HPG_WINTV_PRO_PAL_BG_D_K }, + { USB_DEVICE(0x0573, 0x4d29), .driver_info=HPG_WINTV_PRO_PAL_I_D_K }, + { USB_DEVICE(0x0573, 0x4d30), .driver_info=HPG_WINTV_PRO_NTSC_MN_FM }, + { USB_DEVICE(0x0573, 0x4d31), .driver_info=HPG_WINTV_PRO_PAL_BG_FM }, + { USB_DEVICE(0x0573, 0x4d32), .driver_info=HPG_WINTV_PRO_PAL_I_FM }, + { USB_DEVICE(0x0573, 0x4d34), .driver_info=HPG_WINTV_PRO_PAL_D_K_FM }, + { USB_DEVICE(0x0573, 0x4d35), .driver_info=HPG_WINTV_PRO_TEMIC_PAL_FM }, + { USB_DEVICE(0x0573, 0x4d36), .driver_info=HPG_WINTV_PRO_TEMIC_PAL_BG_FM }, + { USB_DEVICE(0x0573, 0x4d37), .driver_info=HPG_WINTV_PRO_PAL_FM }, + { USB_DEVICE(0x0573, 0x4d38), .driver_info=HPG_WINTV_PRO_NTSC_MN_FM_V2 }, + { USB_DEVICE(0x0768, 0x0006), .driver_info=CAMTEL_TVB330 }, + { USB_DEVICE(0x07d0, 0x0001), .driver_info=DIGITAL_VIDEO_CREATOR_I }, + { USB_DEVICE(0x07d0, 0x0002), .driver_info=GLOBAL_VILLAGE_GV_007_NTSC }, + { USB_DEVICE(0x07d0, 0x0003), .driver_info=DAZZLE_DVC_50_REV_1_NTSC }, + { USB_DEVICE(0x07d0, 0x0004), .driver_info=DAZZLE_DVC_80_REV_1_PAL }, + { USB_DEVICE(0x07d0, 0x0005), .driver_info=DAZZLE_DVC_90_REV_1_SECAM }, + { USB_DEVICE(0x07f8, 0x9104), .driver_info=ESKAPE_LABS_MYTV2GO }, + { USB_DEVICE(0x2304, 0x010d), .driver_info=PINNA_PCTV_USB_PAL }, + { USB_DEVICE(0x2304, 0x0109), .driver_info=PINNA_PCTV_USB_SECAM }, + { USB_DEVICE(0x2304, 0x0110), .driver_info=PINNA_PCTV_USB_PAL_FM }, + { USB_DEVICE(0x2304, 0x0111), .driver_info=MIRO_PCTV_USB }, + { USB_DEVICE(0x2304, 0x0112), .driver_info=PINNA_PCTV_USB_NTSC_FM }, + { USB_DEVICE(0x2304, 0x0210), .driver_info=PINNA_PCTV_USB_PAL_FM_V2 }, + { USB_DEVICE(0x2304, 0x0212), .driver_info=PINNA_PCTV_USB_NTSC_FM_V2 }, + { USB_DEVICE(0x2304, 0x0214), .driver_info=PINNA_PCTV_USB_PAL_FM_V3 }, + { USB_DEVICE(0x2304, 0x0300), .driver_info=PINNA_LINX_VD_IN_CAB_NTSC }, + { USB_DEVICE(0x2304, 0x0301), .driver_info=PINNA_LINX_VD_IN_CAB_PAL }, + { USB_DEVICE(0x2304, 0x0419), .driver_info=PINNA_PCTV_BUNGEE_PAL_FM }, + { USB_DEVICE(0x2400, 0x4200), .driver_info=HPG_WINTV }, }; MODULE_DEVICE_TABLE (usb, usbvision_table); diff --git a/drivers/media/video/usbvision/usbvision-cards.h b/drivers/media/video/usbvision/usbvision-cards.h new file mode 100644 index 00000000000..512c5cee414 --- /dev/null +++ b/drivers/media/video/usbvision/usbvision-cards.h @@ -0,0 +1,66 @@ +#define XANBOO 0 +#define BELKIN_VIDEOBUS_II 1 +#define BELKIN_VIDEOBUS 2 +#define BELKIN_USB_VIDEOBUS_II 3 +#define ECHOFX_INTERVIEW_LITE 4 +#define USBGEAR_USBG_V1 5 +#define D_LINK_V100 6 +#define X10_USB_CAMERA 7 +#define HPG_WINTV_LIVE_PAL_BG 8 +#define HPG_WINTV_LIVE_PRO_NTSC_MN 9 +#define ZORAN_PMD_NOGATECH 10 +#define NOGATECH_USB_TV_NTSC_FM 11 +#define PNY_USB_TV_NTSC_FM 12 +#define PV_PLAYTV_USB_PRO_PAL_FM 13 +#define ZT_721 14 +#define HPG_WINTV_NTSC_MN 15 +#define HPG_WINTV_PAL_BG 16 +#define HPG_WINTV_PAL_I 17 +#define HPG_WINTV_PAL_SECAM_L 18 +#define HPG_WINTV_PAL_D_K 19 +#define HPG_WINTV_NTSC_FM 20 +#define HPG_WINTV_PAL_BG_FM 21 +#define HPG_WINTV_PAL_I_FM 22 +#define HPG_WINTV_PAL_D_K_FM 23 +#define HPG_WINTV_PRO_NTSC_MN 24 +#define HPG_WINTV_PRO_NTSC_MN_V2 25 +#define HPG_WINTV_PRO_PAL 26 +#define HPG_WINTV_PRO_NTSC_MN_V3 27 +#define HPG_WINTV_PRO_PAL_BG 28 +#define HPG_WINTV_PRO_PAL_I 29 +#define HPG_WINTV_PRO_PAL_SECAM_L 30 +#define HPG_WINTV_PRO_PAL_D_K 31 +#define HPG_WINTV_PRO_PAL_SECAM 32 +#define HPG_WINTV_PRO_PAL_SECAM_V2 33 +#define HPG_WINTV_PRO_PAL_BG_V2 34 +#define HPG_WINTV_PRO_PAL_BG_D_K 35 +#define HPG_WINTV_PRO_PAL_I_D_K 36 +#define HPG_WINTV_PRO_NTSC_MN_FM 37 +#define HPG_WINTV_PRO_PAL_BG_FM 38 +#define HPG_WINTV_PRO_PAL_I_FM 39 +#define HPG_WINTV_PRO_PAL_D_K_FM 40 +#define HPG_WINTV_PRO_TEMIC_PAL_FM 41 +#define HPG_WINTV_PRO_TEMIC_PAL_BG_FM 42 +#define HPG_WINTV_PRO_PAL_FM 43 +#define HPG_WINTV_PRO_NTSC_MN_FM_V2 44 +#define CAMTEL_TVB330 45 +#define DIGITAL_VIDEO_CREATOR_I 46 +#define GLOBAL_VILLAGE_GV_007_NTSC 47 +#define DAZZLE_DVC_50_REV_1_NTSC 48 +#define DAZZLE_DVC_80_REV_1_PAL 49 +#define DAZZLE_DVC_90_REV_1_SECAM 50 +#define ESKAPE_LABS_MYTV2GO 51 +#define PINNA_PCTV_USB_PAL 52 +#define PINNA_PCTV_USB_SECAM 53 +#define PINNA_PCTV_USB_PAL_FM 54 +#define MIRO_PCTV_USB 55 +#define PINNA_PCTV_USB_NTSC_FM 56 +#define PINNA_PCTV_USB_PAL_FM_V2 57 +#define PINNA_PCTV_USB_NTSC_FM_V2 58 +#define PINNA_PCTV_USB_PAL_FM_V3 59 +#define PINNA_LINX_VD_IN_CAB_NTSC 60 +#define PINNA_LINX_VD_IN_CAB_PAL 61 +#define PINNA_PCTV_BUNGEE_PAL_FM 62 +#define HPG_WINTV 63 + +extern const int usbvision_device_data_size; diff --git a/drivers/media/video/usbvision/usbvision-core.c b/drivers/media/video/usbvision/usbvision-core.c index f2154dc072e..bcb551adb7e 100644 --- a/drivers/media/video/usbvision/usbvision-core.c +++ b/drivers/media/video/usbvision/usbvision-core.c @@ -2040,8 +2040,8 @@ int usbvision_set_input(struct usb_usbvision *usbvision) return 0; /* Set input format expected from decoder*/ - if (usbvision_device_data[usbvision->DevModel].Vin_Reg1 >= 0) { - value[0] = usbvision_device_data[usbvision->DevModel].Vin_Reg1 & 0xff; + if (usbvision_device_data[usbvision->DevModel].Vin_Reg1_override) { + value[0] = usbvision_device_data[usbvision->DevModel].Vin_Reg1; } else if(usbvision_device_data[usbvision->DevModel].Codec == CODEC_SAA7113) { /* SAA7113 uses 8 bit output */ value[0] = USBVISION_8_422_SYNC; @@ -2112,8 +2112,8 @@ int usbvision_set_input(struct usb_usbvision *usbvision) dvi_yuv_value = 0x00; /* U comes after V, Ya comes after U/V, Yb comes after Yb */ - if(usbvision_device_data[usbvision->DevModel].Dvi_yuv >= 0){ - dvi_yuv_value = usbvision_device_data[usbvision->DevModel].Dvi_yuv & 0xff; + if(usbvision_device_data[usbvision->DevModel].Dvi_yuv_override){ + dvi_yuv_value = usbvision_device_data[usbvision->DevModel].Dvi_yuv; } else if(usbvision_device_data[usbvision->DevModel].Codec == CODEC_SAA7113) { /* This changes as the fine sync control changes. Further investigation necessary */ @@ -2238,7 +2238,7 @@ static void call_usbvision_power_off(struct work_struct *work) PDEBUG(DBG_FUNC, ""); down_interruptible(&usbvision->lock); if(usbvision->user == 0) { - usbvision_i2c_usb_del_bus(&usbvision->i2c_adap); + usbvision_i2c_unregister(usbvision); usbvision_power_off(usbvision); usbvision->initialized = 0; diff --git a/drivers/media/video/usbvision/usbvision-i2c.c b/drivers/media/video/usbvision/usbvision-i2c.c index 609e1fd9c78..025be555194 100644 --- a/drivers/media/video/usbvision/usbvision-i2c.c +++ b/drivers/media/video/usbvision/usbvision-i2c.c @@ -1,8 +1,8 @@ /* - * I2C_ALGO_USB.C + * usbvision_i2c.c * i2c algorithm for USB-I2C Bridges * - * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de> + * Copyright (c) 1999-2007 Joerg Heckenbach <joerg@heckenbach-aw.de> * Dwaine Garden <dwainegarden@rogers.com> * * This module is part of usbvision driver project. @@ -39,7 +39,6 @@ #include "usbvision.h" #define DBG_I2C 1<<0 -#define DBG_ALGO 1<<1 static int i2c_debug = 0; @@ -49,22 +48,22 @@ MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); #define PDEBUG(level, fmt, args...) \ if (i2c_debug & (level)) info("[%s:%d] " fmt, __PRETTY_FUNCTION__, __LINE__ , ## args) -static int usbvision_i2c_write(void *data, unsigned char addr, char *buf, +static int usbvision_i2c_write(struct usb_usbvision *usbvision, unsigned char addr, char *buf, short len); -static int usbvision_i2c_read(void *data, unsigned char addr, char *buf, +static int usbvision_i2c_read(struct usb_usbvision *usbvision, unsigned char addr, char *buf, short len); static inline int try_write_address(struct i2c_adapter *i2c_adap, unsigned char addr, int retries) { - void *data; + struct usb_usbvision *usbvision; int i, ret = -1; char buf[4]; - data = i2c_get_adapdata(i2c_adap); + usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap); buf[0] = 0x00; for (i = 0; i <= retries; i++) { - ret = (usbvision_i2c_write(data, addr, buf, 1)); + ret = (usbvision_i2c_write(usbvision, addr, buf, 1)); if (ret == 1) break; /* success! */ udelay(5); @@ -73,8 +72,8 @@ static inline int try_write_address(struct i2c_adapter *i2c_adap, udelay(10); } if (i) { - PDEBUG(DBG_ALGO,"Needed %d retries for address %#2x", i, addr); - PDEBUG(DBG_ALGO,"Maybe there's no device at this address"); + PDEBUG(DBG_I2C,"Needed %d retries for address %#2x", i, addr); + PDEBUG(DBG_I2C,"Maybe there's no device at this address"); } return ret; } @@ -82,13 +81,13 @@ static inline int try_write_address(struct i2c_adapter *i2c_adap, static inline int try_read_address(struct i2c_adapter *i2c_adap, unsigned char addr, int retries) { - void *data; + struct usb_usbvision *usbvision; int i, ret = -1; char buf[4]; - data = i2c_get_adapdata(i2c_adap); + usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap); for (i = 0; i <= retries; i++) { - ret = (usbvision_i2c_read(data, addr, buf, 1)); + ret = (usbvision_i2c_read(usbvision, addr, buf, 1)); if (ret == 1) break; /* success! */ udelay(5); @@ -97,8 +96,8 @@ static inline int try_read_address(struct i2c_adapter *i2c_adap, udelay(10); } if (i) { - PDEBUG(DBG_ALGO,"Needed %d retries for address %#2x", i, addr); - PDEBUG(DBG_ALGO,"Maybe there's no device at this address"); + PDEBUG(DBG_I2C,"Needed %d retries for address %#2x", i, addr); + PDEBUG(DBG_I2C,"Maybe there's no device at this address"); } return ret; } @@ -152,32 +151,32 @@ static inline int usb_find_address(struct i2c_adapter *i2c_adap, } static int -usb_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num) +usbvision_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num) { struct i2c_msg *pmsg; - void *data; + struct usb_usbvision *usbvision; int i, ret; unsigned char addr; - data = i2c_get_adapdata(i2c_adap); + usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap); for (i = 0; i < num; i++) { pmsg = &msgs[i]; ret = usb_find_address(i2c_adap, pmsg, i2c_adap->retries, &addr); if (ret != 0) { - PDEBUG(DBG_ALGO,"got NAK from device, message #%d", i); + PDEBUG(DBG_I2C,"got NAK from device, message #%d", i); return (ret < 0) ? ret : -EREMOTEIO; } if (pmsg->flags & I2C_M_RD) { /* read bytes into buffer */ - ret = (usbvision_i2c_read(data, addr, pmsg->buf, pmsg->len)); + ret = (usbvision_i2c_read(usbvision, addr, pmsg->buf, pmsg->len)); if (ret < pmsg->len) { return (ret < 0) ? ret : -EREMOTEIO; } } else { /* write bytes from buffer */ - ret = (usbvision_i2c_write(data, addr, pmsg->buf, pmsg->len)); + ret = (usbvision_i2c_write(usbvision, addr, pmsg->buf, pmsg->len)); if (ret < pmsg->len) { return (ret < 0) ? ret : -EREMOTEIO; } @@ -191,7 +190,7 @@ static int algo_control(struct i2c_adapter *adapter, unsigned int cmd, unsigned return 0; } -static u32 usb_func(struct i2c_adapter *adap) +static u32 functionality(struct i2c_adapter *adap) { return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR | I2C_FUNC_PROTOCOL_MANGLING; } @@ -199,11 +198,11 @@ static u32 usb_func(struct i2c_adapter *adap) /* -----exported algorithm data: ------------------------------------- */ -static struct i2c_algorithm i2c_usb_algo = { - .master_xfer = usb_xfer, +static struct i2c_algorithm usbvision_algo = { + .master_xfer = usbvision_i2c_xfer, .smbus_xfer = NULL, .algo_control = algo_control, - .functionality = usb_func, + .functionality = functionality, }; @@ -213,41 +212,29 @@ static struct i2c_algorithm i2c_usb_algo = { static int usbvision_i2c_usb_add_bus(struct i2c_adapter *adap) { PDEBUG(DBG_I2C, "I2C debugging is enabled [i2c]"); - PDEBUG(DBG_ALGO, "ALGO debugging is enabled [i2c]"); + PDEBUG(DBG_I2C, "ALGO debugging is enabled [i2c]"); /* register new adapter to i2c module... */ - adap->algo = &i2c_usb_algo; + adap->algo = &usbvision_algo; adap->timeout = 100; /* default values, should */ adap->retries = 3; /* be replaced by defines */ i2c_add_adapter(adap); - PDEBUG(DBG_ALGO,"i2c bus for %s registered", adap->name); - - return 0; -} - - -int usbvision_i2c_usb_del_bus(struct i2c_adapter *adap) -{ - - i2c_del_adapter(adap); - - PDEBUG(DBG_ALGO,"i2c bus for %s unregistered", adap->name); + PDEBUG(DBG_I2C,"i2c bus for %s registered", adap->name); return 0; } - /* ----------------------------------------------------------------------- */ /* usbvision specific I2C functions */ /* ----------------------------------------------------------------------- */ static struct i2c_adapter i2c_adap_template; static struct i2c_client i2c_client_template; -int usbvision_init_i2c(struct usb_usbvision *usbvision) +int usbvision_i2c_register(struct usb_usbvision *usbvision) { memcpy(&usbvision->i2c_adap, &i2c_adap_template, sizeof(struct i2c_adapter)); @@ -265,7 +252,7 @@ int usbvision_init_i2c(struct usb_usbvision *usbvision) usbvision->i2c_client.adapter = &usbvision->i2c_adap; if (usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_IIC_LRNACK) < 0) { - printk(KERN_ERR "usbvision_init_i2c: can't write reg\n"); + printk(KERN_ERR "usbvision_register: can't write reg\n"); return -EBUSY; } @@ -287,6 +274,16 @@ int usbvision_init_i2c(struct usb_usbvision *usbvision) return usbvision_i2c_usb_add_bus(&usbvision->i2c_adap); } +int usbvision_i2c_unregister(struct usb_usbvision *usbvision) +{ + + i2c_del_adapter(&(usbvision->i2c_adap)); + + PDEBUG(DBG_I2C,"i2c bus for %s unregistered", usbvision->i2c_adap.name); + + return 0; +} + void call_i2c_clients(struct usb_usbvision *usbvision, unsigned int cmd, void *arg) { @@ -300,19 +297,12 @@ static int attach_inform(struct i2c_client *client) usbvision = (struct usb_usbvision *)i2c_get_adapdata(client->adapter); switch (client->addr << 1) { - case 0x43: - case 0x4b: - { - struct tuner_setup tun_setup; - - tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; - tun_setup.type = TUNER_TDA9887; - tun_setup.addr = client->addr; - - call_i2c_clients(usbvision, TUNER_SET_TYPE_ADDR, &tun_setup); - + case 0x42 << 1: + case 0x43 << 1: + case 0x4a << 1: + case 0x4b << 1: + PDEBUG(DBG_I2C,"attach_inform: tda9887 detected."); break; - } case 0x42: PDEBUG(DBG_I2C,"attach_inform: saa7114 detected."); break; @@ -480,7 +470,7 @@ static int usbvision_i2c_write_max4(struct usb_usbvision *usbvision, return len; } -static int usbvision_i2c_write(void *data, unsigned char addr, char *buf, +static int usbvision_i2c_write(struct usb_usbvision *usbvision, unsigned char addr, char *buf, short len) { char *bufPtr = buf; @@ -488,7 +478,6 @@ static int usbvision_i2c_write(void *data, unsigned char addr, char *buf, int wrcount = 0; int count; int maxLen = 4; - struct usb_usbvision *usbvision = (struct usb_usbvision *) data; while (len > 0) { count = (len > maxLen) ? maxLen : len; @@ -503,14 +492,13 @@ static int usbvision_i2c_write(void *data, unsigned char addr, char *buf, return wrcount; } -static int usbvision_i2c_read(void *data, unsigned char addr, char *buf, +static int usbvision_i2c_read(struct usb_usbvision *usbvision, unsigned char addr, char *buf, short len) { char temp[4]; int retval, i; int rdcount = 0; int count; - struct usb_usbvision *usbvision = (struct usb_usbvision *) data; while (len > 0) { count = (len > 3) ? 4 : len; diff --git a/drivers/media/video/usbvision/usbvision-video.c b/drivers/media/video/usbvision/usbvision-video.c index 6fc14557d62..216704170a4 100644 --- a/drivers/media/video/usbvision/usbvision-video.c +++ b/drivers/media/video/usbvision/usbvision-video.c @@ -76,6 +76,7 @@ #endif #include "usbvision.h" +#include "usbvision-cards.h" #define DRIVER_AUTHOR "Joerg Heckenbach <joerg@heckenbach-aw.de>, Dwaine Garden <DwaineGarden@rogers.com>" #define DRIVER_NAME "usbvision" @@ -150,7 +151,6 @@ static int PowerOnAtOpen = 1; // Set the default device to power on at startu static int video_nr = -1; // Sequential Number of Video Device static int radio_nr = -1; // Sequential Number of Radio Device static int vbi_nr = -1; // Sequential Number of VBI Device -static char *CustomDevice=NULL; // Set as nothing.... // Grab parameters for the device driver @@ -161,7 +161,6 @@ module_param(PowerOnAtOpen, int, 0444); module_param(video_nr, int, 0444); module_param(radio_nr, int, 0444); module_param(vbi_nr, int, 0444); -module_param(CustomDevice, charp, 0444); #else // Old Style MODULE_PARAM(isocMode, "i"); MODULE_PARM(video_debug, "i"); // Grab the Debug Mode of the device driver @@ -171,7 +170,6 @@ MODULE_PARM(SwitchSVideoInput, "i"); // To help people with Black and White ou MODULE_PARM(video_nr, "i"); // video_nr option allows to specify a certain /dev/videoX device (like /dev/video0 or /dev/video1 ...) MODULE_PARM(radio_nr, "i"); // radio_nr option allows to specify a certain /dev/radioX device (like /dev/radio0 or /dev/radio1 ...) MODULE_PARM(vbi_nr, "i"); // vbi_nr option allows to specify a certain /dev/vbiX device (like /dev/vbi0 or /dev/vbi1 ...) -MODULE_PARM(CustomDevice, "s"); // .... CustomDevice #endif MODULE_PARM_DESC(isocMode, " Set the default format for ISOC endpoint. Default: 0x60 (Compression On)"); @@ -180,7 +178,6 @@ MODULE_PARM_DESC(PowerOnAtOpen, " Set the default device to power on when device MODULE_PARM_DESC(video_nr, "Set video device number (/dev/videoX). Default: -1 (autodetect)"); MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); MODULE_PARM_DESC(vbi_nr, "Set vbi device number (/dev/vbiX). Default: -1 (autodetect)"); -MODULE_PARM_DESC(CustomDevice, " Define the fine tuning parameters for the device. Default: null"); // Misc stuff @@ -409,7 +406,7 @@ static int usbvision_v4l2_open(struct inode *inode, struct file *file) down(&usbvision->lock); if (usbvision->power == 0) { usbvision_power_on(usbvision); - usbvision_init_i2c(usbvision); + usbvision_i2c_register(usbvision); } /* Send init sequence only once, it's large! */ @@ -431,7 +428,7 @@ static int usbvision_v4l2_open(struct inode *inode, struct file *file) } else { if (PowerOnAtOpen) { - usbvision_i2c_usb_del_bus(&usbvision->i2c_adap); + usbvision_i2c_unregister(usbvision); usbvision_power_off(usbvision); usbvision->initialized = 0; } @@ -1239,7 +1236,7 @@ static int usbvision_radio_open(struct inode *inode, struct file *file) usbvision_reset_powerOffTimer(usbvision); if (usbvision->power == 0) { usbvision_power_on(usbvision); - usbvision_init_i2c(usbvision); + usbvision_i2c_register(usbvision); } } @@ -1261,7 +1258,7 @@ static int usbvision_radio_open(struct inode *inode, struct file *file) if (errCode) { if (PowerOnAtOpen) { - usbvision_i2c_usb_del_bus(&usbvision->i2c_adap); + usbvision_i2c_unregister(usbvision); usbvision_power_off(usbvision); usbvision->initialized = 0; } @@ -1744,8 +1741,8 @@ static void usbvision_configure_video(struct usb_usbvision *usbvision) model = usbvision->DevModel; usbvision->palette = usbvision_v4l2_format[2]; // V4L2_PIX_FMT_RGB24; - if (usbvision_device_data[usbvision->DevModel].Vin_Reg2 >= 0) { - usbvision->Vin_Reg2_Preset = usbvision_device_data[usbvision->DevModel].Vin_Reg2 & 0xff; + if (usbvision_device_data[usbvision->DevModel].Vin_Reg2_override) { + usbvision->Vin_Reg2_Preset = usbvision_device_data[usbvision->DevModel].Vin_Reg2; } else { usbvision->Vin_Reg2_Preset = 0; } @@ -1764,7 +1761,7 @@ static void usbvision_configure_video(struct usb_usbvision *usbvision) usbvision_audio_off(usbvision); //first switch off audio if (!PowerOnAtOpen) { usbvision_power_on(usbvision); //and then power up the noisy tuner - usbvision_init_i2c(usbvision); + usbvision_i2c_register(usbvision); } } @@ -1775,7 +1772,8 @@ static void usbvision_configure_video(struct usb_usbvision *usbvision) * if it looks like USBVISION video device * */ -static int __devinit usbvision_probe(struct usb_interface *intf, const struct usb_device_id *devid) +static int __devinit usbvision_probe(struct usb_interface *intf, + const struct usb_device_id *devid) { struct usb_device *dev = usb_get_dev(interface_to_usbdev(intf)); struct usb_interface *uif; @@ -1786,25 +1784,17 @@ static int __devinit usbvision_probe(struct usb_interface *intf, const struct us int model,i; PDEBUG(DBG_PROBE, "VID=%#04x, PID=%#04x, ifnum=%u", - dev->descriptor.idVendor, dev->descriptor.idProduct, ifnum); + dev->descriptor.idVendor, + dev->descriptor.idProduct, ifnum); - /* Is it an USBVISION video dev? */ - model = 0; - for(model = 0; usbvision_device_data[model].idVendor; model++) { - if (le16_to_cpu(dev->descriptor.idVendor) != usbvision_device_data[model].idVendor) { - continue; - } - if (le16_to_cpu(dev->descriptor.idProduct) != usbvision_device_data[model].idProduct) { - continue; - } - - printk(KERN_INFO "%s: %s found\n", __FUNCTION__, usbvision_device_data[model].ModelString); - break; + model = devid->driver_info; + if ( (model<0) || (model>=usbvision_device_data_size) ) { + PDEBUG(DBG_PROBE, "model out of bounds %d",model); + return -ENODEV; } + printk(KERN_INFO "%s: %s found\n", __FUNCTION__, + usbvision_device_data[model].ModelString); - if (usbvision_device_data[model].idVendor == 0) { - return -ENODEV; //no matching device - } if (usbvision_device_data[model].Interface >= 0) { interface = &dev->actconfig->interface[usbvision_device_data[model].Interface]->altsetting[0]; } @@ -1822,16 +1812,15 @@ static int __devinit usbvision_probe(struct usb_interface *intf, const struct us return -ENODEV; } - usb_get_dev(dev); - if ((usbvision = usbvision_alloc(dev)) == NULL) { err("%s: couldn't allocate USBVision struct", __FUNCTION__); return -ENOMEM; } + if (dev->descriptor.bNumConfigurations > 1) { usbvision->bridgeType = BRIDGE_NT1004; } - else if (usbvision_device_data[model].ModelString == "Dazzle Fusion Model DVC-90 Rev 1 (SECAM)") { + else if (model == DAZZLE_DVC_90_REV_1_SECAM) { usbvision->bridgeType = BRIDGE_NT1005; } else { @@ -1920,7 +1909,7 @@ static void __devexit usbvision_disconnect(struct usb_interface *intf) usbvision_stop_isoc(usbvision); if (usbvision->power) { - usbvision_i2c_usb_del_bus(&usbvision->i2c_adap); + usbvision_i2c_unregister(usbvision); usbvision_power_off(usbvision); } usbvision->remove_pending = 1; // Now all ISO data will be ignored @@ -1951,124 +1940,6 @@ static struct usb_driver usbvision_driver = { }; /* - * customdevice_process() - * - * This procedure preprocesses CustomDevice parameter if any - * - */ -static void customdevice_process(void) -{ - usbvision_device_data[0]=usbvision_device_data[1]; - usbvision_table[0]=usbvision_table[1]; - - if(CustomDevice) - { - char *parse=CustomDevice; - - PDEBUG(DBG_PROBE, "CustomDevide=%s", CustomDevice); - - /*format is CustomDevice="0x0573 0x4D31 0 7113 3 PAL 1 1 1 5 -1 -1 -1 -1 -1" - usbvision_device_data[0].idVendor; - usbvision_device_data[0].idProduct; - usbvision_device_data[0].Interface; - usbvision_device_data[0].Codec; - usbvision_device_data[0].VideoChannels; - usbvision_device_data[0].VideoNorm; - usbvision_device_data[0].AudioChannels; - usbvision_device_data[0].Radio; - usbvision_device_data[0].Tuner; - usbvision_device_data[0].TunerType; - usbvision_device_data[0].Vin_Reg1; - usbvision_device_data[0].Vin_Reg2; - usbvision_device_data[0].X_Offset; - usbvision_device_data[0].Y_Offset; - usbvision_device_data[0].Dvi_yuv; - usbvision_device_data[0].ModelString; - */ - - rmspace(parse); - usbvision_device_data[0].ModelString="USBVISION Custom Device"; - - parse+=2; - sscanf(parse,"%x",&usbvision_device_data[0].idVendor); - goto2next(parse); - PDEBUG(DBG_PROBE, "idVendor=0x%.4X", usbvision_device_data[0].idVendor); - parse+=2; - sscanf(parse,"%x",&usbvision_device_data[0].idProduct); - goto2next(parse); - PDEBUG(DBG_PROBE, "idProduct=0x%.4X", usbvision_device_data[0].idProduct); - sscanf(parse,"%d",&usbvision_device_data[0].Interface); - goto2next(parse); - PDEBUG(DBG_PROBE, "Interface=%d", usbvision_device_data[0].Interface); - sscanf(parse,"%d",&usbvision_device_data[0].Codec); - goto2next(parse); - PDEBUG(DBG_PROBE, "Codec=%d", usbvision_device_data[0].Codec); - sscanf(parse,"%d",&usbvision_device_data[0].VideoChannels); - goto2next(parse); - PDEBUG(DBG_PROBE, "VideoChannels=%d", usbvision_device_data[0].VideoChannels); - - switch(*parse) - { - case 'P': - PDEBUG(DBG_PROBE, "VideoNorm=PAL"); - usbvision_device_data[0].VideoNorm=V4L2_STD_PAL; - break; - - case 'S': - PDEBUG(DBG_PROBE, "VideoNorm=SECAM"); - usbvision_device_data[0].VideoNorm=V4L2_STD_SECAM; - break; - - case 'N': - PDEBUG(DBG_PROBE, "VideoNorm=NTSC"); - usbvision_device_data[0].VideoNorm=V4L2_STD_NTSC; - break; - - default: - PDEBUG(DBG_PROBE, "VideoNorm=PAL (by default)"); - usbvision_device_data[0].VideoNorm=V4L2_STD_PAL; - break; - } - goto2next(parse); - - sscanf(parse,"%d",&usbvision_device_data[0].AudioChannels); - goto2next(parse); - PDEBUG(DBG_PROBE, "AudioChannels=%d", usbvision_device_data[0].AudioChannels); - sscanf(parse,"%d",&usbvision_device_data[0].Radio); - goto2next(parse); - PDEBUG(DBG_PROBE, "Radio=%d", usbvision_device_data[0].Radio); - sscanf(parse,"%d",&usbvision_device_data[0].Tuner); - goto2next(parse); - PDEBUG(DBG_PROBE, "Tuner=%d", usbvision_device_data[0].Tuner); - sscanf(parse,"%d",&usbvision_device_data[0].TunerType); - goto2next(parse); - PDEBUG(DBG_PROBE, "TunerType=%d", usbvision_device_data[0].TunerType); - sscanf(parse,"%d",&usbvision_device_data[0].Vin_Reg1); - goto2next(parse); - PDEBUG(DBG_PROBE, "Vin_Reg1=%d", usbvision_device_data[0].Vin_Reg1); - sscanf(parse,"%d",&usbvision_device_data[0].Vin_Reg2); - goto2next(parse); - PDEBUG(DBG_PROBE, "Vin_Reg2=%d", usbvision_device_data[0].Vin_Reg2); - sscanf(parse,"%d",&usbvision_device_data[0].X_Offset); - goto2next(parse); - PDEBUG(DBG_PROBE, "X_Offset=%d", usbvision_device_data[0].X_Offset); - sscanf(parse,"%d",&usbvision_device_data[0].Y_Offset); - goto2next(parse); - PDEBUG(DBG_PROBE, "Y_Offset=%d", usbvision_device_data[0].Y_Offset); - sscanf(parse,"%d",&usbvision_device_data[0].Dvi_yuv); - PDEBUG(DBG_PROBE, "Dvi_yuv=%d", usbvision_device_data[0].Dvi_yuv); - - //add to usbvision_table also - usbvision_table[0].match_flags=USB_DEVICE_ID_MATCH_DEVICE; - usbvision_table[0].idVendor=usbvision_device_data[0].idVendor; - usbvision_table[0].idProduct=usbvision_device_data[0].idProduct; - - } -} - - - -/* * usbvision_init() * * This code is run to initialize the driver. @@ -2092,8 +1963,6 @@ static int __init usbvision_init(void) usbvision_v4l2_format[7].supported = 0; // V4L2_PIX_FMT_YUV422P } - customdevice_process(); - errCode = usb_register(&usbvision_driver); if (errCode == 0) { diff --git a/drivers/media/video/usbvision/usbvision.h b/drivers/media/video/usbvision/usbvision.h index ad6afd3e42a..bd6f6422ed5 100644 --- a/drivers/media/video/usbvision/usbvision.h +++ b/drivers/media/video/usbvision/usbvision.h @@ -342,23 +342,24 @@ struct usbvision_frame { #define BRIDGE_NT1005 1005 struct usbvision_device_data_st { - int idVendor; - int idProduct; - int Interface; /* to handle special interface number like BELKIN and Hauppauge WinTV-USB II */ - int Codec; - int VideoChannels; __u64 VideoNorm; - int AudioChannels; - int Radio; - int vbi; - int Tuner; - int TunerType; - int Vin_Reg1; - int Vin_Reg2; - int X_Offset; - int Y_Offset; - int Dvi_yuv; - char *ModelString; + const char *ModelString; + int Interface; /* to handle special interface number like BELKIN and Hauppauge WinTV-USB II */ + __u16 Codec; + unsigned VideoChannels:3; + unsigned AudioChannels:2; + unsigned Radio:1; + unsigned vbi:1; + unsigned Tuner:1; + unsigned Vin_Reg1_override:1; /* Override default value with */ + unsigned Vin_Reg2_override:1; /* Vin_Reg1, Vin_Reg2, etc. */ + unsigned Dvi_yuv_override:1; + __u8 Vin_Reg1; + __u8 Vin_Reg2; + __u8 Dvi_yuv; + __u8 TunerType; + __s16 X_Offset; + __s16 Y_Offset; }; /* Declared on usbvision-cards.c */ @@ -481,13 +482,11 @@ struct usb_usbvision { /* i2c-algo-usb declaration */ /* --------------------------------------------------------------- */ -int usbvision_i2c_usb_del_bus(struct i2c_adapter *); - - /* ----------------------------------------------------------------------- */ /* usbvision specific I2C functions */ /* ----------------------------------------------------------------------- */ -int usbvision_init_i2c(struct usb_usbvision *usbvision); +int usbvision_i2c_register(struct usb_usbvision *usbvision); +int usbvision_i2c_unregister(struct usb_usbvision *usbvision); void call_i2c_clients(struct usb_usbvision *usbvision, unsigned int cmd,void *arg); /* defined in usbvision-core.c */ diff --git a/drivers/media/video/v4l2-common.c b/drivers/media/video/v4l2-common.c index 54747606eae..49f1df74aa2 100644 --- a/drivers/media/video/v4l2-common.c +++ b/drivers/media/video/v4l2-common.c @@ -60,6 +60,7 @@ #include <linux/video_decoder.h> #define __OLD_VIDIOC_ /* To allow fixing old calls*/ #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> #ifdef CONFIG_KMOD #include <linux/kmod.h> @@ -260,6 +261,8 @@ char *v4l2_field_names[] = { [V4L2_FIELD_SEQ_TB] = "seq-tb", [V4L2_FIELD_SEQ_BT] = "seq-bt", [V4L2_FIELD_ALTERNATE] = "alternate", + [V4L2_FIELD_INTERLACED_TB] = "interlaced-tb", + [V4L2_FIELD_INTERLACED_BT] = "interlaced-bt", }; char *v4l2_type_names[] = { @@ -269,7 +272,8 @@ char *v4l2_type_names[] = { [V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap", [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out", [V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap", - [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "slicec-vbi-out", + [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "sliced-vbi-out", + [V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "video-out-over", }; @@ -380,6 +384,8 @@ static const char *v4l2_ioctls[] = { [_IOC_NR(VIDIOC_DBG_S_REGISTER)] = "VIDIOC_DBG_S_REGISTER", [_IOC_NR(VIDIOC_DBG_G_REGISTER)] = "VIDIOC_DBG_G_REGISTER", + + [_IOC_NR(VIDIOC_G_CHIP_IDENT)] = "VIDIOC_G_CHIP_IDENT", #endif }; #define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) @@ -410,14 +416,16 @@ static const char *v4l2_int_ioctls[] = { [_IOC_NR(VIDIOC_INT_DECODE_VBI_LINE)] = "VIDIOC_INT_DECODE_VBI_LINE", [_IOC_NR(VIDIOC_INT_S_VBI_DATA)] = "VIDIOC_INT_S_VBI_DATA", [_IOC_NR(VIDIOC_INT_G_VBI_DATA)] = "VIDIOC_INT_G_VBI_DATA", - [_IOC_NR(VIDIOC_INT_G_CHIP_IDENT)] = "VIDIOC_INT_G_CHIP_IDENT", [_IOC_NR(VIDIOC_INT_I2S_CLOCK_FREQ)] = "VIDIOC_INT_I2S_CLOCK_FREQ", [_IOC_NR(VIDIOC_INT_S_STANDBY)] = "VIDIOC_INT_S_STANDBY", [_IOC_NR(VIDIOC_INT_S_AUDIO_ROUTING)] = "VIDIOC_INT_S_AUDIO_ROUTING", [_IOC_NR(VIDIOC_INT_G_AUDIO_ROUTING)] = "VIDIOC_INT_G_AUDIO_ROUTING", [_IOC_NR(VIDIOC_INT_S_VIDEO_ROUTING)] = "VIDIOC_INT_S_VIDEO_ROUTING", [_IOC_NR(VIDIOC_INT_G_VIDEO_ROUTING)] = "VIDIOC_INT_G_VIDEO_ROUTING", - [_IOC_NR(VIDIOC_INT_S_CRYSTAL_FREQ)] = "VIDIOC_INT_S_CRYSTAL_FREQ" + [_IOC_NR(VIDIOC_INT_S_CRYSTAL_FREQ)] = "VIDIOC_INT_S_CRYSTAL_FREQ", + [_IOC_NR(VIDIOC_INT_INIT)] = "VIDIOC_INT_INIT", + [_IOC_NR(VIDIOC_INT_G_STD_OUTPUT)] = "VIDIOC_INT_G_STD_OUTPUT", + [_IOC_NR(VIDIOC_INT_S_STD_OUTPUT)] = "VIDIOC_INT_S_STD_OUTPUT", }; #define V4L2_INT_IOCTLS ARRAY_SIZE(v4l2_int_ioctls) @@ -680,6 +688,7 @@ int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 max, s32 ste case V4L2_CID_MPEG_AUDIO_MODE_EXTENSION: name = "Audio Stereo Mode Extension"; break; case V4L2_CID_MPEG_AUDIO_EMPHASIS: name = "Audio Emphasis"; break; case V4L2_CID_MPEG_AUDIO_CRC: name = "Audio CRC"; break; + case V4L2_CID_MPEG_AUDIO_MUTE: name = "Audio Mute"; break; case V4L2_CID_MPEG_VIDEO_ENCODING: name = "Video Encoding"; break; case V4L2_CID_MPEG_VIDEO_ASPECT: name = "Video Aspect"; break; case V4L2_CID_MPEG_VIDEO_B_FRAMES: name = "Video B Frames"; break; @@ -690,6 +699,8 @@ int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 max, s32 ste case V4L2_CID_MPEG_VIDEO_BITRATE: name = "Video Bitrate"; break; case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: name = "Video Peak Bitrate"; break; case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: name = "Video Temporal Decimation"; break; + case V4L2_CID_MPEG_VIDEO_MUTE: name = "Video Mute"; break; + case V4L2_CID_MPEG_VIDEO_MUTE_YUV: name = "Video Mute YUV"; break; case V4L2_CID_MPEG_STREAM_TYPE: name = "Stream Type"; break; case V4L2_CID_MPEG_STREAM_PID_PMT: name = "Stream PMT Program ID"; break; case V4L2_CID_MPEG_STREAM_PID_AUDIO: name = "Stream Audio Program ID"; break; @@ -705,6 +716,7 @@ int v4l2_ctrl_query_fill(struct v4l2_queryctrl *qctrl, s32 min, s32 max, s32 ste switch (qctrl->id) { case V4L2_CID_AUDIO_MUTE: case V4L2_CID_AUDIO_LOUDNESS: + case V4L2_CID_MPEG_AUDIO_MUTE: case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: case V4L2_CID_MPEG_VIDEO_PULLDOWN: qctrl->type = V4L2_CTRL_TYPE_BOOLEAN; @@ -838,6 +850,8 @@ int v4l2_ctrl_query_fill_std(struct v4l2_queryctrl *qctrl) V4L2_MPEG_AUDIO_CRC_NONE, V4L2_MPEG_AUDIO_CRC_CRC16, 1, V4L2_MPEG_AUDIO_CRC_NONE); + case V4L2_CID_MPEG_AUDIO_MUTE: + return v4l2_ctrl_query_fill(qctrl, 0, 1, 1, 0); case V4L2_CID_MPEG_VIDEO_ENCODING: return v4l2_ctrl_query_fill(qctrl, V4L2_MPEG_VIDEO_ENCODING_MPEG_1, @@ -867,6 +881,10 @@ int v4l2_ctrl_query_fill_std(struct v4l2_queryctrl *qctrl) return v4l2_ctrl_query_fill(qctrl, 0, 27000000, 1, 8000000); case V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION: return v4l2_ctrl_query_fill(qctrl, 0, 255, 1, 0); + case V4L2_CID_MPEG_VIDEO_MUTE: + return v4l2_ctrl_query_fill(qctrl, 0, 1, 1, 0); + case V4L2_CID_MPEG_VIDEO_MUTE_YUV: /* Init YUV (really YCbCr) to black */ + return v4l2_ctrl_query_fill(qctrl, 0, 0xffffff, 1, 0x008080); case V4L2_CID_MPEG_STREAM_TYPE: return v4l2_ctrl_query_fill(qctrl, V4L2_MPEG_STREAM_TYPE_MPEG2_PS, @@ -965,6 +983,22 @@ int v4l2_chip_match_i2c_client(struct i2c_client *c, u32 match_type, u32 match_c } } +int v4l2_chip_ident_i2c_client(struct i2c_client *c, struct v4l2_chip_ident *chip, + u32 ident, u32 revision) +{ + if (!v4l2_chip_match_i2c_client(c, chip->match_type, chip->match_chip)) + return 0; + if (chip->ident == V4L2_IDENT_NONE) { + chip->ident = ident; + chip->revision = revision; + } + else { + chip->ident = V4L2_IDENT_AMBIGUOUS; + chip->revision = 0; + } + return 0; +} + int v4l2_chip_match_host(u32 match_type, u32 match_chip) { switch (match_type) { @@ -999,6 +1033,7 @@ EXPORT_SYMBOL(v4l2_ctrl_query_fill); EXPORT_SYMBOL(v4l2_ctrl_query_fill_std); EXPORT_SYMBOL(v4l2_chip_match_i2c_client); +EXPORT_SYMBOL(v4l2_chip_ident_i2c_client); EXPORT_SYMBOL(v4l2_chip_match_host); /* diff --git a/drivers/media/video/videocodec.c b/drivers/media/video/videocodec.c index 290e6413565..f2bbd7a4d56 100644 --- a/drivers/media/video/videocodec.c +++ b/drivers/media/video/videocodec.c @@ -348,6 +348,9 @@ videocodec_build_table (void) kfree(videocodec_buf); videocodec_buf = kmalloc(size, GFP_KERNEL); + if (!videocodec_buf) + return 0; + i = 0; i += scnprintf(videocodec_buf + i, size - 1, "<S>lave or attached <M>aster name type flags magic "); diff --git a/drivers/media/video/videodev.c b/drivers/media/video/videodev.c index 011938fb7e0..80ac5f86d9e 100644 --- a/drivers/media/video/videodev.c +++ b/drivers/media/video/videodev.c @@ -318,6 +318,7 @@ static char *v4l2_type_names_FIXME[] = { [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out", [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "sliced-vbi-out", [V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-capture", + [V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "video-out-over", [V4L2_BUF_TYPE_PRIVATE] = "private", }; @@ -330,6 +331,8 @@ static char *v4l2_field_names_FIXME[] = { [V4L2_FIELD_SEQ_TB] = "seq-tb", [V4L2_FIELD_SEQ_BT] = "seq-bt", [V4L2_FIELD_ALTERNATE] = "alternate", + [V4L2_FIELD_INTERLACED_TB] = "interlaced-tb", + [V4L2_FIELD_INTERLACED_BT] = "interlaced-bt", }; #define prt_names(a,arr) (((a)>=0)&&((a)<ARRAY_SIZE(arr)))?arr[a]:"unknown" @@ -411,6 +414,10 @@ static int check_fmt (struct video_device *vfd, enum v4l2_buf_type type) if (vfd->vidioc_try_fmt_vbi_output) return (0); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (vfd->vidioc_try_fmt_output_overlay) + return (0); + break; case V4L2_BUF_TYPE_PRIVATE: if (vfd->vidioc_try_fmt_type_private) return (0); @@ -525,6 +532,10 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, ret=vfd->vidioc_enum_fmt_vbi_output(file, fh, f); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (vfd->vidioc_enum_fmt_output_overlay) + ret=vfd->vidioc_enum_fmt_output_overlay(file, fh, f); + break; case V4L2_BUF_TYPE_PRIVATE: if (vfd->vidioc_enum_fmt_type_private) ret=vfd->vidioc_enum_fmt_type_private(file, @@ -582,6 +593,10 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, ret=vfd->vidioc_g_fmt_video_output(file, fh, f); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (vfd->vidioc_g_fmt_output_overlay) + ret=vfd->vidioc_g_fmt_output_overlay(file, fh, f); + break; case V4L2_BUF_TYPE_VBI_OUTPUT: if (vfd->vidioc_g_fmt_vbi_output) ret=vfd->vidioc_g_fmt_vbi_output(file, fh, f); @@ -630,6 +645,10 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, ret=vfd->vidioc_s_fmt_video_output(file, fh, f); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (vfd->vidioc_s_fmt_output_overlay) + ret=vfd->vidioc_s_fmt_output_overlay(file, fh, f); + break; case V4L2_BUF_TYPE_VBI_OUTPUT: if (vfd->vidioc_s_fmt_vbi_output) ret=vfd->vidioc_s_fmt_vbi_output(file, @@ -680,6 +699,10 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, ret=vfd->vidioc_try_fmt_video_output(file, fh, f); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: + if (vfd->vidioc_try_fmt_output_overlay) + ret=vfd->vidioc_try_fmt_output_overlay(file, fh, f); + break; case V4L2_BUF_TYPE_VBI_OUTPUT: if (vfd->vidioc_try_fmt_vbi_output) ret=vfd->vidioc_try_fmt_vbi_output(file, @@ -1381,6 +1404,11 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, case VIDIOC_G_PARM: { struct v4l2_streamparm *p=arg; + __u32 type=p->type; + + memset(p,0,sizeof(*p)); + p->type=type; + if (vfd->vidioc_g_parm) { ret=vfd->vidioc_g_parm(file, fh, p); } else { @@ -1392,8 +1420,6 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, v4l2_video_std_construct(&s, vfd->current_norm, v4l2_norm_to_name(vfd->current_norm)); - memset(p,0,sizeof(*p)); - p->parm.capture.timeperframe = s.frameperiod; ret=0; } @@ -1509,6 +1535,16 @@ static int __video_do_ioctl(struct inode *inode, struct file *file, break; } #endif + case VIDIOC_G_CHIP_IDENT: + { + struct v4l2_chip_ident *p=arg; + if (!vfd->vidioc_g_chip_ident) + break; + ret=vfd->vidioc_g_chip_ident(file, fh, p); + if (!ret) + dbgarg (cmd, "chip_ident=%u, revision=0x%x\n", p->ident, p->revision); + break; + } } /* switch */ if (vfd->debug & V4L2_DEBUG_IOCTL_ARG) { diff --git a/drivers/media/video/wm8739.c b/drivers/media/video/wm8739.c index a9b59c35cd6..8f6741a28a4 100644 --- a/drivers/media/video/wm8739.c +++ b/drivers/media/video/wm8739.c @@ -29,6 +29,7 @@ #include <linux/i2c-id.h> #include <linux/videodev.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> MODULE_DESCRIPTION("wm8739 driver"); MODULE_AUTHOR("T. Adachi, Hans Verkuil"); @@ -236,6 +237,9 @@ static int wm8739_command(struct i2c_client *client, unsigned int cmd, void *arg return -EINVAL; } + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_WM8739, 0); + case VIDIOC_LOG_STATUS: v4l_info(client, "Frequency: %u Hz\n", state->clock_freq); v4l_info(client, "Volume L: %02x%s\n", state->vol_l & 0x1f, diff --git a/drivers/media/video/wm8775.c b/drivers/media/video/wm8775.c index d81a88bbe43..4df5d30d4d0 100644 --- a/drivers/media/video/wm8775.c +++ b/drivers/media/video/wm8775.c @@ -33,6 +33,7 @@ #include <linux/i2c-id.h> #include <linux/videodev.h> #include <media/v4l2-common.h> +#include <media/v4l2-chip-ident.h> MODULE_DESCRIPTION("wm8775 driver"); MODULE_AUTHOR("Ulf Eklund, Hans Verkuil"); @@ -124,6 +125,9 @@ static int wm8775_command(struct i2c_client *client, unsigned int cmd, wm8775_write(client, R21, 0x100 + state->input); break; + case VIDIOC_G_CHIP_IDENT: + return v4l2_chip_ident_i2c_client(client, arg, V4L2_IDENT_WM8775, 0); + case VIDIOC_LOG_STATUS: v4l_info(client, "Input: %d%s\n", state->input, state->muted ? " (muted)" : ""); diff --git a/drivers/media/video/zr364xx.c b/drivers/media/video/zr364xx.c new file mode 100644 index 00000000000..b5d3364c94c --- /dev/null +++ b/drivers/media/video/zr364xx.c @@ -0,0 +1,929 @@ +/* + * Zoran 364xx based USB webcam module version 0.72 + * + * Allows you to use your USB webcam with V4L2 applications + * This is still in heavy developpement ! + * + * Copyright (C) 2004 Antoine Jacquet <royale@zerezo.com> + * http://royale.zerezo.com/zr364xx/ + * + * Heavily inspired by usb-skeleton.c, vicam.c, cpia.c and spca50x.c drivers + * V4L2 version inspired by meye.c driver + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/highmem.h> +#include <media/v4l2-common.h> + + +/* Version Information */ +#define DRIVER_VERSION "v0.72" +#define DRIVER_AUTHOR "Antoine Jacquet, http://royale.zerezo.com/" +#define DRIVER_DESC "Zoran 364xx" + + +/* Camera */ +#define FRAMES 2 +#define MAX_FRAME_SIZE 100000 +#define BUFFER_SIZE 0x1000 +#define CTRL_TIMEOUT 500 + + +/* Debug macro */ +#define DBG(x...) if (debug) info(x) + + +/* Init methods, need to find nicer names for these + * the exact names of the chipsets would be the best if someone finds it */ +#define METHOD0 0 +#define METHOD1 1 +#define METHOD2 2 + + +/* Module parameters */ +static int debug = 0; +static int mode = 0; + + +/* Module parameters interface */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level"); +module_param(mode, int, 0644); +MODULE_PARM_DESC(mode, "0 = 320x240, 1 = 160x120, 2 = 640x480"); + + +/* Devices supported by this driver + * .driver_info contains the init method used by the camera */ +static struct usb_device_id device_table[] = { + {USB_DEVICE(0x08ca, 0x0109), .driver_info = METHOD0 }, + {USB_DEVICE(0x041e, 0x4024), .driver_info = METHOD0 }, + {USB_DEVICE(0x0d64, 0x0108), .driver_info = METHOD0 }, + {USB_DEVICE(0x0546, 0x3187), .driver_info = METHOD0 }, + {USB_DEVICE(0x0d64, 0x3108), .driver_info = METHOD0 }, + {USB_DEVICE(0x0595, 0x4343), .driver_info = METHOD0 }, + {USB_DEVICE(0x0bb0, 0x500d), .driver_info = METHOD0 }, + {USB_DEVICE(0x0feb, 0x2004), .driver_info = METHOD0 }, + {USB_DEVICE(0x055f, 0xb500), .driver_info = METHOD0 }, + {USB_DEVICE(0x08ca, 0x2062), .driver_info = METHOD2 }, + {USB_DEVICE(0x052b, 0x1a18), .driver_info = METHOD1 }, + {USB_DEVICE(0x04c8, 0x0729), .driver_info = METHOD0 }, + {USB_DEVICE(0x04f2, 0xa208), .driver_info = METHOD0 }, + {USB_DEVICE(0x0784, 0x0040), .driver_info = METHOD1 }, + {USB_DEVICE(0x06d6, 0x0034), .driver_info = METHOD0 }, + {USB_DEVICE(0x0a17, 0x0062), .driver_info = METHOD2 }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, device_table); + + +/* Camera stuff */ +struct zr364xx_camera { + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface;/* the interface for this device */ + struct video_device *vdev; /* v4l video device */ + u8 *framebuf; + int nb; + unsigned char *buffer; + int skip; + int brightness; + int width; + int height; + int method; + struct mutex lock; +}; + + +/* function used to send initialisation commands to the camera */ +static int send_control_msg(struct usb_device *udev, u8 request, u16 value, + u16 index, unsigned char *cp, u16 size) +{ + int status; + + unsigned char *transfer_buffer = kmalloc(size, GFP_KERNEL); + if (!transfer_buffer) { + info("kmalloc(%d) failed", size); + return -ENOMEM; + } + + memcpy(transfer_buffer, cp, size); + + status = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + request, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, + transfer_buffer, size, CTRL_TIMEOUT); + + kfree(transfer_buffer); + + if (status < 0) + info("Failed sending control message, error %d.", status); + + return status; +} + + +/* Control messages sent to the camera to initialize it + * and launch the capture */ +typedef struct { + unsigned int value; + unsigned int size; + unsigned char *bytes; +} message; + +/* method 0 */ +static unsigned char m0d1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static unsigned char m0d2[] = { 0, 0, 0, 0, 0, 0 }; +static unsigned char m0d3[] = { 0, 0 }; +static message m0[] = { + {0x1f30, 0, NULL}, + {0xd000, 0, NULL}, + {0x3370, sizeof(m0d1), m0d1}, + {0x2000, 0, NULL}, + {0x2f0f, 0, NULL}, + {0x2610, sizeof(m0d2), m0d2}, + {0xe107, 0, NULL}, + {0x2502, 0, NULL}, + {0x1f70, 0, NULL}, + {0xd000, 0, NULL}, + {0x9a01, sizeof(m0d3), m0d3}, + {-1, -1, NULL} +}; + +/* method 1 */ +static unsigned char m1d1[] = { 0xff, 0xff }; +static unsigned char m1d2[] = { 0x00, 0x00 }; +static message m1[] = { + {0x1f30, 0, NULL}, + {0xd000, 0, NULL}, + {0xf000, 0, NULL}, + {0x2000, 0, NULL}, + {0x2f0f, 0, NULL}, + {0x2650, 0, NULL}, + {0xe107, 0, NULL}, + {0x2502, sizeof(m1d1), m1d1}, + {0x1f70, 0, NULL}, + {0xd000, 0, NULL}, + {0xd000, 0, NULL}, + {0xd000, 0, NULL}, + {0x9a01, sizeof(m1d2), m1d2}, + {-1, -1, NULL} +}; + +/* method 2 */ +static unsigned char m2d1[] = { 0xff, 0xff }; +static message m2[] = { + {0x1f30, 0, NULL}, + {0xf000, 0, NULL}, + {0x2000, 0, NULL}, + {0x2f0f, 0, NULL}, + {0x2650, 0, NULL}, + {0xe107, 0, NULL}, + {0x2502, sizeof(m2d1), m2d1}, + {0x1f70, 0, NULL}, + {-1, -1, NULL} +}; + +/* init table */ +static message *init[3] = { m0, m1, m2 }; + + +/* JPEG static data in header (Huffman table, etc) */ +static unsigned char header1[] = { + 0xFF, 0xD8, + /* + 0xFF, 0xE0, 0x00, 0x10, 'J', 'F', 'I', 'F', + 0x00, 0x01, 0x01, 0x00, 0x33, 0x8A, 0x00, 0x00, 0x33, 0x88, + */ + 0xFF, 0xDB, 0x00, 0x84 +}; +static unsigned char header2[] = { + 0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, + 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, + 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, + 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, + 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, + 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, + 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, + 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xC4, 0x00, 0x1F, + 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, + 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, + 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, + 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, + 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, + 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, + 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, + 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, + 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x01, + 0x40, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, + 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, + 0x00, 0x3F, 0x00 +}; +static unsigned char header3; + + + +/********************/ +/* V4L2 integration */ +/********************/ + +/* this function reads a full JPEG picture synchronously + * TODO: do it asynchronously... */ +static int read_frame(struct zr364xx_camera *cam, int framenum) +{ + int i, n, temp, head, size, actual_length; + unsigned char *ptr = NULL, *jpeg; + + redo: + /* hardware brightness */ + n = send_control_msg(cam->udev, 1, 0x2001, 0, NULL, 0); + temp = (0x60 << 8) + 127 - cam->brightness; + n = send_control_msg(cam->udev, 1, temp, 0, NULL, 0); + + /* during the first loop we are going to insert JPEG header */ + head = 0; + /* this is the place in memory where we are going to build + * the JPEG image */ + jpeg = cam->framebuf + framenum * MAX_FRAME_SIZE; + /* read data... */ + do { + n = usb_bulk_msg(cam->udev, + usb_rcvbulkpipe(cam->udev, 0x81), + cam->buffer, BUFFER_SIZE, &actual_length, + CTRL_TIMEOUT); + DBG("buffer : %d %d", cam->buffer[0], cam->buffer[1]); + DBG("bulk : n=%d size=%d", n, actual_length); + if (n < 0) { + info("error reading bulk msg"); + return 0; + } + if (actual_length < 0 || actual_length > BUFFER_SIZE) { + info("wrong number of bytes"); + return 0; + } + + /* swap bytes if camera needs it */ + if (cam->method == METHOD0) { + u16 *buf = (u16*)cam->buffer; + for (i = 0; i < BUFFER_SIZE/2; i++) + swab16s(buf + i); + } + + /* write the JPEG header */ + if (!head) { + DBG("jpeg header"); + ptr = jpeg; + memcpy(ptr, header1, sizeof(header1)); + ptr += sizeof(header1); + header3 = 0; + memcpy(ptr, &header3, 1); + ptr++; + memcpy(ptr, cam->buffer, 64); + ptr += 64; + header3 = 1; + memcpy(ptr, &header3, 1); + ptr++; + memcpy(ptr, cam->buffer + 64, 64); + ptr += 64; + memcpy(ptr, header2, sizeof(header2)); + ptr += sizeof(header2); + memcpy(ptr, cam->buffer + 128, + actual_length - 128); + ptr += actual_length - 128; + head = 1; + DBG("header : %d %d %d %d %d %d %d %d %d", + cam->buffer[0], cam->buffer[1], cam->buffer[2], + cam->buffer[3], cam->buffer[4], cam->buffer[5], + cam->buffer[6], cam->buffer[7], cam->buffer[8]); + } else { + memcpy(ptr, cam->buffer, actual_length); + ptr += actual_length; + } + } + /* ... until there is no more */ + while (actual_length == BUFFER_SIZE); + + /* we skip the 2 first frames which are usually buggy */ + if (cam->skip) { + cam->skip--; + goto redo; + } + + /* go back to find the JPEG EOI marker */ + size = ptr - jpeg; + ptr -= 2; + while (ptr > jpeg) { + if (*ptr == 0xFF && *(ptr + 1) == 0xD9 + && *(ptr + 2) == 0xFF) + break; + ptr--; + } + if (ptr == jpeg) + DBG("No EOI marker"); + + /* Sometimes there is junk data in the middle of the picture, + * we want to skip this bogus frames */ + while (ptr > jpeg) { + if (*ptr == 0xFF && *(ptr + 1) == 0xFF + && *(ptr + 2) == 0xFF) + break; + ptr--; + } + if (ptr != jpeg) { + DBG("Bogus frame ? %d", cam->nb); + goto redo; + } + + DBG("jpeg : %d %d %d %d %d %d %d %d", + jpeg[0], jpeg[1], jpeg[2], jpeg[3], + jpeg[4], jpeg[5], jpeg[6], jpeg[7]); + + return size; +} + + +static ssize_t zr364xx_read(struct file *file, char *buf, size_t cnt, + loff_t * ppos) +{ + unsigned long count = cnt; + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + DBG("zr364xx_read: read %d bytes.", (int) count); + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + if (!buf) + return -EINVAL; + + if (!count) + return -EINVAL; + + /* NoMan Sux ! */ + count = read_frame(cam, 0); + + if (copy_to_user(buf, cam->framebuf, count)) + return -EFAULT; + + return count; +} + + +static int zr364xx_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, DRIVER_DESC); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; + return 0; +} + +static int zr364xx_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + memset(i, 0, sizeof(*i)); + i->index = 0; + strcpy(i->name, DRIVER_DESC " Camera"); + i->type = V4L2_INPUT_TYPE_CAMERA; + return 0; +} + +static int zr364xx_vidioc_g_input(struct file *file, void *priv, + unsigned int *i) +{ + *i = 0; + return 0; +} + +static int zr364xx_vidioc_s_input(struct file *file, void *priv, + unsigned int i) +{ + if (i != 0) + return -EINVAL; + return 0; +} + +static int zr364xx_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Brightness"); + c->minimum = 0; + c->maximum = 127; + c->step = 1; + c->default_value = cam->brightness; + c->flags = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static int zr364xx_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *c) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + cam->brightness = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +static int zr364xx_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *c) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = cam->brightness; + break; + default: + return -EINVAL; + } + return 0; +} + +static int zr364xx_vidioc_enum_fmt_cap(struct file *file, + void *priv, struct v4l2_fmtdesc *f) +{ + if (f->index > 0) + return -EINVAL; + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + memset(f, 0, sizeof(*f)); + f->index = 0; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + strcpy(f->description, "JPEG"); + f->pixelformat = V4L2_PIX_FMT_JPEG; + return 0; +} + +static int zr364xx_vidioc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_JPEG) + return -EINVAL; + if (f->fmt.pix.field != V4L2_FIELD_ANY && + f->fmt.pix.field != V4L2_FIELD_NONE) + return -EINVAL; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = cam->width; + f->fmt.pix.height = cam->height; + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + return 0; +} + +static int zr364xx_vidioc_g_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = cam->width; + f->fmt.pix.height = cam->height; + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + return 0; +} + +static int zr364xx_vidioc_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_JPEG) + return -EINVAL; + if (f->fmt.pix.field != V4L2_FIELD_ANY && + f->fmt.pix.field != V4L2_FIELD_NONE) + return -EINVAL; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = cam->width; + f->fmt.pix.height = cam->height; + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + DBG("ok!"); + return 0; +} + +static int zr364xx_vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + return 0; +} + +static int zr364xx_vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + return 0; +} + + +/* open the camera */ +static int zr364xx_open(struct inode *inode, struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam = video_get_drvdata(vdev); + struct usb_device *udev = cam->udev; + int i, err; + + DBG("zr364xx_open"); + + cam->skip = 2; + + err = video_exclusive_open(inode, file); + if (err < 0) + return err; + + if (!cam->framebuf) { + cam->framebuf = vmalloc_32(MAX_FRAME_SIZE * FRAMES); + if (!cam->framebuf) { + info("vmalloc_32 failed!"); + return -ENOMEM; + } + } + + mutex_lock(&cam->lock); + for (i = 0; init[cam->method][i].size != -1; i++) { + err = + send_control_msg(udev, 1, init[cam->method][i].value, + 0, init[cam->method][i].bytes, + init[cam->method][i].size); + if (err < 0) { + info("error during open sequence: %d", i); + mutex_unlock(&cam->lock); + return err; + } + } + + file->private_data = vdev; + + /* Added some delay here, since opening/closing the camera quickly, + * like Ekiga does during its startup, can crash the webcam + */ + mdelay(100); + + mutex_unlock(&cam->lock); + return 0; +} + + +/* release the camera */ +static int zr364xx_release(struct inode *inode, struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + struct usb_device *udev; + int i, err; + + DBG("zr364xx_release"); + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + udev = cam->udev; + + mutex_lock(&cam->lock); + for (i = 0; i < 2; i++) { + err = + send_control_msg(udev, 1, init[cam->method][i].value, + 0, init[i][cam->method].bytes, + init[cam->method][i].size); + if (err < 0) { + info("error during release sequence"); + mutex_unlock(&cam->lock); + return err; + } + } + + file->private_data = NULL; + video_exclusive_release(inode, file); + + /* Added some delay here, since opening/closing the camera quickly, + * like Ekiga does during its startup, can crash the webcam + */ + mdelay(100); + + mutex_unlock(&cam->lock); + return 0; +} + + +static int zr364xx_mmap(struct file *file, struct vm_area_struct *vma) +{ + void *pos; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + struct video_device *vdev = video_devdata(file); + struct zr364xx_camera *cam; + + DBG("zr364xx_mmap: %ld\n", size); + + if (vdev == NULL) + return -ENODEV; + cam = video_get_drvdata(vdev); + + pos = cam->framebuf; + while (size > 0) { + if (vm_insert_page(vma, start, vmalloc_to_page(pos))) + return -EAGAIN; + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return 0; +} + + +static struct file_operations zr364xx_fops = { + .owner = THIS_MODULE, + .open = zr364xx_open, + .release = zr364xx_release, + .read = zr364xx_read, + .mmap = zr364xx_mmap, + .ioctl = video_ioctl2, + .llseek = no_llseek, +}; + +static struct video_device zr364xx_template = { + .owner = THIS_MODULE, + .name = DRIVER_DESC, + .type = VID_TYPE_CAPTURE, + .fops = &zr364xx_fops, + .release = video_device_release, + .minor = -1, + + .vidioc_querycap = zr364xx_vidioc_querycap, + .vidioc_enum_fmt_cap = zr364xx_vidioc_enum_fmt_cap, + .vidioc_try_fmt_cap = zr364xx_vidioc_try_fmt_cap, + .vidioc_s_fmt_cap = zr364xx_vidioc_s_fmt_cap, + .vidioc_g_fmt_cap = zr364xx_vidioc_g_fmt_cap, + .vidioc_enum_input = zr364xx_vidioc_enum_input, + .vidioc_g_input = zr364xx_vidioc_g_input, + .vidioc_s_input = zr364xx_vidioc_s_input, + .vidioc_streamon = zr364xx_vidioc_streamon, + .vidioc_streamoff = zr364xx_vidioc_streamoff, + .vidioc_queryctrl = zr364xx_vidioc_queryctrl, + .vidioc_g_ctrl = zr364xx_vidioc_g_ctrl, + .vidioc_s_ctrl = zr364xx_vidioc_s_ctrl, +}; + + + +/*******************/ +/* USB integration */ +/*******************/ + +static int zr364xx_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct zr364xx_camera *cam = NULL; + + DBG("probing..."); + + info(DRIVER_DESC " compatible webcam plugged"); + info("model %04x:%04x detected", udev->descriptor.idVendor, + udev->descriptor.idProduct); + + if ((cam = + kmalloc(sizeof(struct zr364xx_camera), GFP_KERNEL)) == NULL) { + info("cam: out of memory !"); + return -ENODEV; + } + memset(cam, 0x00, sizeof(struct zr364xx_camera)); + /* save the init method used by this camera */ + cam->method = id->driver_info; + + cam->vdev = video_device_alloc(); + if (cam->vdev == NULL) { + info("cam->vdev: out of memory !"); + kfree(cam); + return -ENODEV; + } + memcpy(cam->vdev, &zr364xx_template, sizeof(zr364xx_template)); + video_set_drvdata(cam->vdev, cam); + if (debug) + cam->vdev->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG; + + cam->udev = udev; + + if ((cam->buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL)) == NULL) { + info("cam->buffer: out of memory !"); + video_device_release(cam->vdev); + kfree(cam); + return -ENODEV; + } + + switch (mode) { + case 1: + info("160x120 mode selected"); + cam->width = 160; + cam->height = 120; + break; + case 2: + info("640x480 mode selected"); + cam->width = 640; + cam->height = 480; + break; + default: + info("320x240 mode selected"); + cam->width = 320; + cam->height = 240; + break; + } + + m0d1[0] = mode; + m1[2].value = 0xf000 + mode; + m2[1].value = 0xf000 + mode; + header2[437] = cam->height / 256; + header2[438] = cam->height % 256; + header2[439] = cam->width / 256; + header2[440] = cam->width % 256; + + cam->nb = 0; + cam->brightness = 64; + mutex_init(&cam->lock); + + if (video_register_device(cam->vdev, VFL_TYPE_GRABBER, -1) == -1) { + info("video_register_device failed"); + video_device_release(cam->vdev); + kfree(cam->buffer); + kfree(cam); + return -ENODEV; + } + + usb_set_intfdata(intf, cam); + + info(DRIVER_DESC " controlling video device %d", cam->vdev->minor); + return 0; +} + + +static void zr364xx_disconnect(struct usb_interface *intf) +{ + struct zr364xx_camera *cam = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + dev_set_drvdata(&intf->dev, NULL); + info(DRIVER_DESC " webcam unplugged"); + if (cam->vdev) + video_unregister_device(cam->vdev); + cam->vdev = NULL; + kfree(cam->buffer); + if (cam->framebuf) + vfree(cam->framebuf); + kfree(cam); +} + + + +/**********************/ +/* Module integration */ +/**********************/ + +static struct usb_driver zr364xx_driver = { + .name = "zr364xx", + .probe = zr364xx_probe, + .disconnect = zr364xx_disconnect, + .id_table = device_table +}; + + +static int __init zr364xx_init(void) +{ + int retval; + retval = usb_register(&zr364xx_driver) < 0; + if (retval) + info("usb_register failed!"); + else + info(DRIVER_DESC " module loaded"); + return retval; +} + + +static void __exit zr364xx_exit(void) +{ + info(DRIVER_DESC " module unloaded"); + usb_deregister(&zr364xx_driver); +} + + +module_init(zr364xx_init); +module_exit(zr364xx_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); |