diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/media/video | |
download | linux-stable-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz linux-stable-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 linux-stable-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/media/video')
141 files changed, 94536 insertions, 0 deletions
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig new file mode 100644 index 000000000000..c1b3542dad88 --- /dev/null +++ b/drivers/media/video/Kconfig @@ -0,0 +1,360 @@ +# +# Multimedia Video device configuration +# + +menu "Video For Linux" + depends on VIDEO_DEV + +comment "Video Adapters" + +config VIDEO_BT848 + tristate "BT848 Video For Linux" + depends on VIDEO_DEV && PCI && I2C + select I2C_ALGOBIT + select FW_LOADER + select VIDEO_BTCX + select VIDEO_BUF + select VIDEO_IR + select VIDEO_TUNER + select VIDEO_TVEEPROM + ---help--- + Support for BT848 based frame grabber/overlay boards. This includes + the Miro, Hauppauge and STB boards. Please read the material in + <file:Documentation/video4linux/bttv/> for more information. + + If you say Y or M here, you need to say Y or M to "I2C support" and + "I2C bit-banging interfaces" in the device drivers section. + + To compile this driver as a module, choose M here: the + module will be called bttv. + +config VIDEO_PMS + tristate "Mediavision Pro Movie Studio Video For Linux" + depends on VIDEO_DEV && ISA + help + Say Y if you have such a thing. + + To compile this driver as a module, choose M here: the + module will be called pms. + +config VIDEO_PLANB + tristate "PlanB Video-In on PowerMac" + depends on PPC_PMAC && VIDEO_DEV && BROKEN + help + PlanB is the V4L driver for the PowerMac 7x00/8x00 series video + input hardware. If you want to experiment with this, say Y. + Otherwise, or if you don't understand a word, say N. See + <http://www.cpu.lu/~mlan/linux/dev/planb.html> for more info. + + Saying M will compile this driver as a module (planb). + +config VIDEO_BWQCAM + tristate "Quickcam BW Video For Linux" + depends on VIDEO_DEV && PARPORT + help + Say Y have if you the black and white version of the QuickCam + camera. See the next option for the color version. + + To compile this driver as a module, choose M here: the + module will be called bw-qcam. + +config VIDEO_CQCAM + tristate "QuickCam Colour Video For Linux (EXPERIMENTAL)" + depends on EXPERIMENTAL && VIDEO_DEV && PARPORT + help + This is the video4linux driver for the colour version of the + Connectix QuickCam. If you have one of these cameras, say Y here, + otherwise say N. This driver does not work with the original + monochrome QuickCam, QuickCam VC or QuickClip. It is also available + as a module (c-qcam). + Read <file:Documentation/video4linux/CQcam.txt> for more information. + +config VIDEO_W9966 + tristate "W9966CF Webcam (FlyCam Supra and others) Video For Linux" + depends on PARPORT_1284 && VIDEO_DEV && PARPORT + help + Video4linux driver for Winbond's w9966 based Webcams. + Currently tested with the LifeView FlyCam Supra. + If you have one of these cameras, say Y here + otherwise say N. + This driver is also available as a module (w9966). + + Check out <file:Documentation/video4linux/w9966.txt> for more + information. + +config VIDEO_CPIA + tristate "CPiA Video For Linux" + depends on VIDEO_DEV + ---help--- + This is the video4linux driver for cameras based on Vision's CPiA + (Colour Processor Interface ASIC), such as the Creative Labs Video + Blaster Webcam II. If you have one of these cameras, say Y here + and select parallel port and/or USB lowlevel support below, + otherwise say N. This will not work with the Creative Webcam III. + + Please read <file:Documentation/video4linux/README.cpia> for more + information. + + This driver is also available as a module (cpia). + +config VIDEO_CPIA_PP + tristate "CPiA Parallel Port Lowlevel Support" + depends on PARPORT_1284 && VIDEO_CPIA && PARPORT + help + This is the lowlevel parallel port support for cameras based on + Vision's CPiA (Colour Processor Interface ASIC), such as the + Creative Webcam II. If you have the parallel port version of one + of these cameras, say Y here, otherwise say N. It is also available + as a module (cpia_pp). + +config VIDEO_CPIA_USB + tristate "CPiA USB Lowlevel Support" + depends on VIDEO_CPIA && USB + help + This is the lowlevel USB support for cameras based on Vision's CPiA + (Colour Processor Interface ASIC), such as the Creative Webcam II. + If you have the USB version of one of these cameras, say Y here, + otherwise say N. This will not work with the Creative Webcam III. + It is also available as a module (cpia_usb). + +config VIDEO_SAA5246A + tristate "SAA5246A, SAA5281 Teletext processor" + depends on VIDEO_DEV && I2C + help + Support for I2C bus based teletext using the SAA5246A or SAA5281 + chip. Useful only if you live in Europe. + + To compile this driver as a module, choose M here: the + module will be called saa5246a. + +config VIDEO_SAA5249 + tristate "SAA5249 Teletext processor" + depends on VIDEO_DEV && I2C + help + Support for I2C bus based teletext using the SAA5249 chip. At the + moment this is only useful on some European WinTV cards. + + To compile this driver as a module, choose M here: the + module will be called saa5249. + +config TUNER_3036 + tristate "SAB3036 tuner" + depends on VIDEO_DEV && I2C + help + Say Y here to include support for Philips SAB3036 compatible tuners. + If in doubt, say N. + +config VIDEO_VINO + tristate "SGI Vino Video For Linux (EXPERIMENTAL)" + depends on VIDEO_DEV && I2C && SGI_IP22 && EXPERIMENTAL + select I2C_ALGO_SGI + help + Say Y here to build in support for the Vino video input system found + on SGI Indy machines. + +config VIDEO_STRADIS + tristate "Stradis 4:2:2 MPEG-2 video driver (EXPERIMENTAL)" + depends on EXPERIMENTAL && VIDEO_DEV && PCI + help + Say Y here to enable support for the Stradis 4:2:2 MPEG-2 video + driver for PCI. There is a product page at + <http://www.stradis.com/decoder.html>. + +config VIDEO_ZORAN + tristate "Zoran ZR36057/36067 Video For Linux" + depends on VIDEO_DEV && PCI && I2C_ALGOBIT + help + Say Y for support for MJPEG capture cards based on the Zoran + 36057/36067 PCI controller chipset. This includes the Iomega + Buz, Pinnacle DC10+ and the Linux Media Labs LML33. There is + a driver homepage at <http://mjpeg.sf.net/driver-zoran/>. For + more information, check <file:Documentation/video4linux/Zoran>. + + To compile this driver as a module, choose M here: the + module will be called zr36067. + +config VIDEO_ZORAN_BUZ + tristate "Iomega Buz support" + depends on VIDEO_ZORAN + help + Support for the Iomega Buz MJPEG capture/playback card. + +config VIDEO_ZORAN_DC10 + tristate "Pinnacle/Miro DC10(+) support" + depends on VIDEO_ZORAN + help + Support for the Pinnacle/Miro DC10(+) MJPEG capture/playback + card. + +config VIDEO_ZORAN_DC30 + tristate "Pinnacle/Miro DC30(+) support" + depends on VIDEO_ZORAN + help + Support for the Pinnacle/Miro DC30(+) MJPEG capture/playback + card. This also supports really old DC10 cards based on the + zr36050 MJPEG codec and zr36016 VFE. + +config VIDEO_ZORAN_LML33 + tristate "Linux Media Labs LML33 support" + depends on VIDEO_ZORAN + help + Support for the Linux Media Labs LML33 MJPEG capture/playback + card. + +config VIDEO_ZORAN_LML33R10 + tristate "Linux Media Labs LML33R10 support" + depends on VIDEO_ZORAN + help + support for the Linux Media Labs LML33R10 MJPEG capture/playback + card. + +config VIDEO_ZR36120 + tristate "Zoran ZR36120/36125 Video For Linux" + depends on VIDEO_DEV && PCI && I2C && BROKEN + help + Support for ZR36120/ZR36125 based frame grabber/overlay boards. + This includes the Victor II, WaveWatcher, Video Wonder, Maxi-TV, + and Buster boards. Please read the material in + <file:Documentation/video4linux/zr36120.txt> for more information. + + To compile this driver as a module, choose M here: the + module will be called zr36120. + +config VIDEO_MEYE + tristate "Sony Vaio Picturebook Motion Eye Video For Linux" + depends on VIDEO_DEV && PCI && SONYPI + ---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. + + To compile this driver as a module, choose M here: the + module will be called meye. + +config VIDEO_SAA7134 + tristate "Philips SAA7134 support" + depends on VIDEO_DEV && PCI && I2C + select VIDEO_BUF + select VIDEO_IR + select VIDEO_TUNER + ---help--- + This is a video4linux driver for Philips SAA7130/7134 based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called saa7134. + +config VIDEO_SAA7134_DVB + tristate "DVB Support for saa7134 based TV cards" + depends on VIDEO_SAA7134 && DVB_CORE + select VIDEO_BUF_DVB + select DVB_MT352 + ---help--- + This adds support for DVB cards based on the + Philips saa7134 chip. + +config VIDEO_MXB + tristate "Siemens-Nixdorf 'Multimedia eXtension Board'" + depends on VIDEO_DEV && PCI + select VIDEO_SAA7146_VV + select VIDEO_TUNER + ---help--- + This is a video4linux driver for the 'Multimedia eXtension Board' + TV card by Siemens-Nixdorf. + + To compile this driver as a module, choose M here: the + module will be called mxb. + +config VIDEO_DPC + tristate "Philips-Semiconductors 'dpc7146 demonstration board'" + depends on VIDEO_DEV && PCI + select VIDEO_SAA7146_VV + ---help--- + This is a video4linux driver for the 'dpc7146 demonstration + board' by Philips-Semiconductors. It's the reference design + for SAA7146 bases boards, so if you have some unsupported + saa7146 based, analog video card, chances are good that it + will work with this skeleton driver. + + To compile this driver as a module, choose M here: the + module will be called dpc7146. + +config VIDEO_HEXIUM_ORION + tristate "Hexium HV-PCI6 and Orion frame grabber" + depends on VIDEO_DEV && PCI + select VIDEO_SAA7146_VV + ---help--- + This is a video4linux driver for the Hexium HV-PCI6 and + Orion frame grabber cards by Hexium. + + To compile this driver as a module, choose M here: the + module will be called hexium_orion. + +config VIDEO_HEXIUM_GEMINI + tristate "Hexium Gemini frame grabber" + depends on VIDEO_DEV && PCI + select VIDEO_SAA7146_VV + ---help--- + This is a video4linux driver for the Hexium Gemini frame + grabber card by Hexium. Please note that the Gemini Dual + card is *not* fully supported. + + To compile this driver as a module, choose M here: the + module will be called hexium_gemini. + +config VIDEO_CX88 + tristate "Conexant 2388x (bt878 successor) support" + depends on VIDEO_DEV && PCI && I2C && EXPERIMENTAL + select I2C_ALGOBIT + select FW_LOADER + select VIDEO_BTCX + select VIDEO_BUF + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_IR + ---help--- + This is a video4linux driver for Conexant 2388x based + TV cards. + + To compile this driver as a module, choose M here: the + module will be called cx8800 + +config VIDEO_CX88_DVB + tristate "DVB Support for cx2388x based TV cards" + depends on VIDEO_CX88 && DVB_CORE + select VIDEO_BUF_DVB + select DVB_MT352 + select DVB_OR51132 + ---help--- + This adds support for DVB/ATSC cards based on the + Connexant 2388x chip. + +config VIDEO_OVCAMCHIP + tristate "OmniVision Camera Chip support" + depends on VIDEO_DEV && I2C + ---help--- + Support for the OmniVision OV6xxx and OV7xxx series of camera chips. + This driver is intended to be used with the ov511 and w9968cf USB + camera drivers. + + To compile this driver as a module, choose M here: the + module will be called ovcamchip + +config VIDEO_M32R_AR + tristate "AR devices" + depends on M32R + ---help--- + This is a video4linux driver for the Renesas AR (Artificial Retina) + camera module. + +config VIDEO_M32R_AR_M64278 + tristate "Use Colour AR module M64278(VGA)" + depends on VIDEO_M32R_AR + ---help--- + Say Y here to use the Renesas M64278E-800 camera module, + which supports VGA(640x480 pixcels) size of images. + +endmenu diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile new file mode 100644 index 000000000000..2dc906fdfa55 --- /dev/null +++ b/drivers/media/video/Makefile @@ -0,0 +1,56 @@ +# +# Makefile for the video capture/playback device drivers. +# + +bttv-objs := bttv-driver.o bttv-cards.o bttv-if.o \ + bttv-risc.o bttv-vbi.o bttv-i2c.o bttv-gpio.o +zoran-objs := zr36120.o zr36120_i2c.o zr36120_mem.o +zr36067-objs := zoran_procfs.o zoran_device.o \ + zoran_driver.o zoran_card.o +tuner-objs := tuner-core.o tuner-simple.o mt20xx.o tda8290.o + +obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-common.o v4l1-compat.o + +obj-$(CONFIG_VIDEO_BT848) += bttv.o msp3400.o tvaudio.o \ + tda7432.o tda9875.o ir-kbd-i2c.o ir-kbd-gpio.o +obj-$(CONFIG_SOUND_TVMIXER) += tvmixer.o + +obj-$(CONFIG_VIDEO_ZR36120) += zoran.o +obj-$(CONFIG_VIDEO_SAA5246A) += saa5246a.o +obj-$(CONFIG_VIDEO_SAA5249) += saa5249.o +obj-$(CONFIG_VIDEO_CQCAM) += c-qcam.o +obj-$(CONFIG_VIDEO_BWQCAM) += bw-qcam.o +obj-$(CONFIG_VIDEO_W9966) += w9966.o +obj-$(CONFIG_VIDEO_ZORAN_BUZ) += saa7111.o saa7185.o zr36060.o +obj-$(CONFIG_VIDEO_ZORAN_DC10) += saa7110.o adv7175.o zr36060.o +obj-$(CONFIG_VIDEO_ZORAN_DC30) += adv7175.o vpx3220.o zr36050.o \ + zr36016.o +obj-$(CONFIG_VIDEO_ZORAN_LML33) += bt819.o bt856.o zr36060.o +obj-$(CONFIG_VIDEO_ZORAN_LML33R10) += saa7114.o adv7170.o zr36060.o +obj-$(CONFIG_VIDEO_ZORAN) += zr36067.o videocodec.o +obj-$(CONFIG_VIDEO_PMS) += pms.o +obj-$(CONFIG_VIDEO_PLANB) += planb.o +obj-$(CONFIG_VIDEO_VINO) += vino.o +obj-$(CONFIG_VIDEO_STRADIS) += stradis.o +obj-$(CONFIG_VIDEO_CPIA) += cpia.o +obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o +obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o +obj-$(CONFIG_VIDEO_MEYE) += meye.o +obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ +obj-$(CONFIG_VIDEO_CX88) += cx88/ +obj-$(CONFIG_VIDEO_OVCAMCHIP) += ovcamchip/ +obj-$(CONFIG_VIDEO_MXB) += saa7111.o tuner.o tda9840.o tea6415c.o tea6420.o mxb.o +obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o +obj-$(CONFIG_VIDEO_HEXIUM_GEMINI) += hexium_gemini.o +obj-$(CONFIG_VIDEO_DPC) += saa7111.o dpc7146.o +obj-$(CONFIG_TUNER_3036) += tuner-3036.o + +obj-$(CONFIG_VIDEO_TUNER) += tuner.o tda9887.o +obj-$(CONFIG_VIDEO_BUF) += video-buf.o +obj-$(CONFIG_VIDEO_BUF_DVB) += video-buf-dvb.o +obj-$(CONFIG_VIDEO_BTCX) += btcx-risc.o +obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o + +obj-$(CONFIG_VIDEO_M32R_AR_M64278) += arv.o + +EXTRA_CFLAGS += -I$(srctree)/drivers/media/dvb/dvb-core diff --git a/drivers/media/video/adv7170.c b/drivers/media/video/adv7170.c new file mode 100644 index 000000000000..80254caa444c --- /dev/null +++ b/drivers/media/video/adv7170.c @@ -0,0 +1,535 @@ +/* + * adv7170 - adv7170, adv7171 video encoder driver version 0.0.1 + * + * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Based on adv7176 driver by: + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Analog Devices ADV7170 video encoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(x) (x)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct adv7170 { + unsigned char reg[128]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_ADV7170 0xd4 +#define I2C_ADV7171 0x54 + +static char adv7170_name[] = "adv7170"; +static char adv7171_name[] = "adv7171"; + +static char *inputs[] = { "pass_through", "play_back" }; +static char *norms[] = { "PAL", "NTSC" }; + +/* ----------------------------------------------------------------------- */ + +static inline int +adv7170_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct adv7170 *encoder = i2c_get_clientdata(client); + + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +adv7170_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +adv7170_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the adv7170 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct adv7170 *encoder = i2c_get_clientdata(client); + struct i2c_msg msg; + u8 block_data[32]; + + msg.addr = client->addr; + msg.flags = 0; + while (len >= 2) { + msg.buf = (char *) block_data; + msg.len = 0; + block_data[msg.len++] = reg = data[0]; + do { + block_data[msg.len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + msg.len < 32); + if ((ret = i2c_transfer(client->adapter, + &msg, 1)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = adv7170_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------- */ +// Output filter: S-Video Composite + +#define MR050 0x11 //0x09 +#define MR060 0x14 //0x0c + +//--------------------------------------------------------------------------- + +#define TR0MODE 0x4c +#define TR0RST 0x80 + +#define TR1CAPT 0x00 +#define TR1PLAY 0x00 + + +static const unsigned char init_NTSC[] = { + 0x00, 0x10, // MR0 + 0x01, 0x20, // MR1 + 0x02, 0x0e, // MR2 RTC control: bits 2 and 1 + 0x03, 0x80, // MR3 + 0x04, 0x30, // MR4 + 0x05, 0x00, // Reserved + 0x06, 0x00, // Reserved + 0x07, TR0MODE, // TM0 + 0x08, TR1CAPT, // TM1 + 0x09, 0x16, // Fsc0 + 0x0a, 0x7c, // Fsc1 + 0x0b, 0xf0, // Fsc2 + 0x0c, 0x21, // Fsc3 + 0x0d, 0x00, // Subcarrier Phase + 0x0e, 0x00, // Closed Capt. Ext 0 + 0x0f, 0x00, // Closed Capt. Ext 1 + 0x10, 0x00, // Closed Capt. 0 + 0x11, 0x00, // Closed Capt. 1 + 0x12, 0x00, // Pedestal Ctl 0 + 0x13, 0x00, // Pedestal Ctl 1 + 0x14, 0x00, // Pedestal Ctl 2 + 0x15, 0x00, // Pedestal Ctl 3 + 0x16, 0x00, // CGMS_WSS_0 + 0x17, 0x00, // CGMS_WSS_1 + 0x18, 0x00, // CGMS_WSS_2 + 0x19, 0x00, // Teletext Ctl +}; + +static const unsigned char init_PAL[] = { + 0x00, 0x71, // MR0 + 0x01, 0x20, // MR1 + 0x02, 0x0e, // MR2 RTC control: bits 2 and 1 + 0x03, 0x80, // MR3 + 0x04, 0x30, // MR4 + 0x05, 0x00, // Reserved + 0x06, 0x00, // Reserved + 0x07, TR0MODE, // TM0 + 0x08, TR1CAPT, // TM1 + 0x09, 0xcb, // Fsc0 + 0x0a, 0x8a, // Fsc1 + 0x0b, 0x09, // Fsc2 + 0x0c, 0x2a, // Fsc3 + 0x0d, 0x00, // Subcarrier Phase + 0x0e, 0x00, // Closed Capt. Ext 0 + 0x0f, 0x00, // Closed Capt. Ext 1 + 0x10, 0x00, // Closed Capt. 0 + 0x11, 0x00, // Closed Capt. 1 + 0x12, 0x00, // Pedestal Ctl 0 + 0x13, 0x00, // Pedestal Ctl 1 + 0x14, 0x00, // Pedestal Ctl 2 + 0x15, 0x00, // Pedestal Ctl 3 + 0x16, 0x00, // CGMS_WSS_0 + 0x17, 0x00, // CGMS_WSS_1 + 0x18, 0x00, // CGMS_WSS_2 + 0x19, 0x00, // Teletext Ctl +}; + + +static int +adv7170_command (struct i2c_client *client, + unsigned int cmd, + void * arg) +{ + struct adv7170 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: +#if 0 + /* This is just for testing!!! */ + adv7170_write_block(client, init_common, + sizeof(init_common)); + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); +#endif + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + cap->flags = VIDEO_ENCODER_PAL | + VIDEO_ENCODER_NTSC; + cap->inputs = 2; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int iarg = *(int *) arg; + + dprintk(1, KERN_DEBUG "%s_command: set norm %d", + I2C_NAME(client), iarg); + + switch (iarg) { + + case VIDEO_MODE_NTSC: + adv7170_write_block(client, init_NTSC, + sizeof(init_NTSC)); + if (encoder->input == 0) + adv7170_write(client, 0x02, 0x0e); // Enable genlock + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + break; + + case VIDEO_MODE_PAL: + adv7170_write_block(client, init_PAL, + sizeof(init_PAL)); + if (encoder->input == 0) + adv7170_write(client, 0x02, 0x0e); // Enable genlock + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + break; + + default: + dprintk(1, KERN_ERR "%s: illegal norm: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_DEBUG "%s: switched to %s\n", I2C_NAME(client), + norms[iarg]); + encoder->norm = iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int iarg = *(int *) arg; + + /* RJ: *iarg = 0: input is from decoder + *iarg = 1: input is from ZR36060 + *iarg = 2: color bar */ + + dprintk(1, KERN_DEBUG "%s_command: set input from %s\n", + I2C_NAME(client), + iarg == 0 ? "decoder" : "ZR36060"); + + switch (iarg) { + + case 0: + adv7170_write(client, 0x01, 0x20); + adv7170_write(client, 0x08, TR1CAPT); /* TR1 */ + adv7170_write(client, 0x02, 0x0e); // Enable genlock + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + case 1: + adv7170_write(client, 0x01, 0x00); + adv7170_write(client, 0x08, TR1PLAY); /* TR1 */ + adv7170_write(client, 0x02, 0x08); + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + default: + dprintk(1, KERN_ERR "%s: illegal input: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_DEBUG "%s: switched to %s\n", I2C_NAME(client), + inputs[iarg]); + encoder->input = iarg; + } + break; + + case ENCODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case ENCODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + encoder->enable = !!*iarg; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_ADV7170 >> 1, (I2C_ADV7170 >> 1) + 1, + I2C_ADV7171 >> 1, (I2C_ADV7171 >> 1) + 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_adv7170; + +static int +adv7170_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct adv7170 *encoder; + char *dname; + + dprintk(1, + KERN_INFO + "adv7170.c: detecting adv7170 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_adv7170; + client->flags = I2C_CLIENT_ALLOW_USE; + if ((client->addr == I2C_ADV7170 >> 1) || + (client->addr == (I2C_ADV7170 >> 1) + 1)) { + dname = adv7170_name; + } else if ((client->addr == I2C_ADV7171 >> 1) || + (client->addr == (I2C_ADV7171 >> 1) + 1)) { + dname = adv7171_name; + } else { + /* We should never get here!!! */ + kfree(client); + return 0; + } + strlcpy(I2C_NAME(client), dname, sizeof(I2C_NAME(client))); + + encoder = kmalloc(sizeof(struct adv7170), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(encoder, 0, sizeof(struct adv7170)); + encoder->norm = VIDEO_MODE_NTSC; + encoder->input = 0; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + i = adv7170_write_block(client, init_NTSC, sizeof(init_NTSC)); + if (i >= 0) { + i = adv7170_write(client, 0x07, TR0MODE | TR0RST); + i = adv7170_write(client, 0x07, TR0MODE); + i = adv7170_read(client, 0x12); + dprintk(1, KERN_INFO "%s_attach: rev. %d at 0x%02x\n", + I2C_NAME(client), i & 1, client->addr << 1); + } + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init error 0x%x\n", + I2C_NAME(client), i); + } + + return 0; +} + +static int +adv7170_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "adv7170.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &adv7170_detect_client); +} + +static int +adv7170_detach_client (struct i2c_client *client) +{ + struct adv7170 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_adv7170 = { + .owner = THIS_MODULE, + .name = "adv7170", /* name */ + + .id = I2C_DRIVERID_ADV7170, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = adv7170_attach_adapter, + .detach_client = adv7170_detach_client, + .command = adv7170_command, +}; + +static int __init +adv7170_init (void) +{ + return i2c_add_driver(&i2c_driver_adv7170); +} + +static void __exit +adv7170_exit (void) +{ + i2c_del_driver(&i2c_driver_adv7170); +} + +module_init(adv7170_init); +module_exit(adv7170_exit); diff --git a/drivers/media/video/adv7175.c b/drivers/media/video/adv7175.c new file mode 100644 index 000000000000..95d0974b0ab5 --- /dev/null +++ b/drivers/media/video/adv7175.c @@ -0,0 +1,585 @@ +/* + * adv7175 - adv7175a video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Analog Devices ADV7175 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct adv7175 { + unsigned char reg[128]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_ADV7175 0xd4 +#define I2C_ADV7176 0x54 + +static char adv7175_name[] = "adv7175"; +static char adv7176_name[] = "adv7176"; + +static char *inputs[] = { "pass_through", "play_back", "color_bar" }; +static char *norms[] = { "PAL", "NTSC", "SECAM->PAL (may not work!)" }; + +/* ----------------------------------------------------------------------- */ + +static inline int +adv7175_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct adv7175 *encoder = i2c_get_clientdata(client); + + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +adv7175_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +adv7175_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct adv7175 *encoder = i2c_get_clientdata(client); + struct i2c_msg msg; + u8 block_data[32]; + + msg.addr = client->addr; + msg.flags = 0; + while (len >= 2) { + msg.buf = (char *) block_data; + msg.len = 0; + block_data[msg.len++] = reg = data[0]; + do { + block_data[msg.len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + msg.len < 32); + if ((ret = i2c_transfer(client->adapter, + &msg, 1)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = adv7175_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static void +set_subcarrier_freq (struct i2c_client *client, + int pass_through) +{ + /* for some reason pass_through NTSC needs + * a different sub-carrier freq to remain stable. */ + if(pass_through) + adv7175_write(client, 0x02, 0x00); + else + adv7175_write(client, 0x02, 0x55); + + adv7175_write(client, 0x03, 0x55); + adv7175_write(client, 0x04, 0x55); + adv7175_write(client, 0x05, 0x25); +} + +#ifdef ENCODER_DUMP +static void +dump (struct i2c_client *client) +{ + struct adv7175 *encoder = i2c_get_clientdata(client); + int i, j; + + printk(KERN_INFO "%s: registry dump\n", I2C_NAME(client)); + for (i = 0; i < 182 / 8; i++) { + printk("%s: 0x%02x -", I2C_NAME(client), i * 8); + for (j = 0; j < 8; j++) { + printk(" 0x%02x", encoder->reg[i * 8 + j]); + } + printk("\n"); + } +} +#endif + +/* ----------------------------------------------------------------------- */ +// Output filter: S-Video Composite + +#define MR050 0x11 //0x09 +#define MR060 0x14 //0x0c + +//--------------------------------------------------------------------------- + +#define TR0MODE 0x46 +#define TR0RST 0x80 + +#define TR1CAPT 0x80 +#define TR1PLAY 0x00 + +static const unsigned char init_common[] = { + + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ + + 0x07, TR0MODE, /* TR0, 16bit */ + 0x08, 0x21, /* */ + 0x09, 0x00, /* */ + 0x0a, 0x00, /* */ + 0x0b, 0x00, /* */ + 0x0c, TR1CAPT, /* TR1 */ + 0x0d, 0x4f, /* MR2 */ + 0x0e, 0x00, /* */ + 0x0f, 0x00, /* */ + 0x10, 0x00, /* */ + 0x11, 0x00, /* */ +}; + +static const unsigned char init_pal[] = { + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ +}; + +static const unsigned char init_ntsc[] = { + 0x00, MR060, /* MR0, NTSC enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x55, /* subc. freq. */ + 0x03, 0x55, /* subc. freq. */ + 0x04, 0x55, /* subc. freq. */ + 0x05, 0x25, /* subc. freq. */ + 0x06, 0x1a, /* subc. phase */ +}; + +static int +adv7175_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct adv7175 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + /* This is just for testing!!! */ + adv7175_write_block(client, init_common, + sizeof(init_common)); + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + cap->flags = VIDEO_ENCODER_PAL | + VIDEO_ENCODER_NTSC | + VIDEO_ENCODER_SECAM; /* well, hacky */ + cap->inputs = 2; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int iarg = *(int *) arg; + + switch (iarg) { + + case VIDEO_MODE_NTSC: + adv7175_write_block(client, init_ntsc, + sizeof(init_ntsc)); + if (encoder->input == 0) + adv7175_write(client, 0x0d, 0x4f); // Enable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + + case VIDEO_MODE_PAL: + adv7175_write_block(client, init_pal, + sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(client, 0x0d, 0x4f); // Enable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + + case VIDEO_MODE_SECAM: // WARNING! ADV7176 does not support SECAM. + /* This is an attempt to convert + * SECAM->PAL (typically it does not work + * due to genlock: when decoder is in SECAM + * and encoder in in PAL the subcarrier can + * not be syncronized with horizontal + * quency) */ + adv7175_write_block(client, init_pal, + sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(client, 0x0d, 0x49); // Disable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + default: + dprintk(1, KERN_ERR "%s: illegal norm: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_INFO "%s: switched to %s\n", I2C_NAME(client), + norms[iarg]); + encoder->norm = iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int iarg = *(int *) arg; + + /* RJ: *iarg = 0: input is from SAA7110 + *iarg = 1: input is from ZR36060 + *iarg = 2: color bar */ + + switch (iarg) { + + case 0: + adv7175_write(client, 0x01, 0x00); + + if (encoder->norm == VIDEO_MODE_NTSC) + set_subcarrier_freq(client, 1); + + adv7175_write(client, 0x0c, TR1CAPT); /* TR1 */ + if (encoder->norm == VIDEO_MODE_SECAM) + adv7175_write(client, 0x0d, 0x49); // Disable genlock + else + adv7175_write(client, 0x0d, 0x4f); // Enable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + case 1: + adv7175_write(client, 0x01, 0x00); + + if (encoder->norm == VIDEO_MODE_NTSC) + set_subcarrier_freq(client, 0); + + adv7175_write(client, 0x0c, TR1PLAY); /* TR1 */ + adv7175_write(client, 0x0d, 0x49); + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + case 2: + adv7175_write(client, 0x01, 0x80); + + if (encoder->norm == VIDEO_MODE_NTSC) + set_subcarrier_freq(client, 0); + + adv7175_write(client, 0x0d, 0x49); + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + default: + dprintk(1, KERN_ERR "%s: illegal input: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_INFO "%s: switched to %s\n", I2C_NAME(client), + inputs[iarg]); + encoder->input = iarg; + } + break; + + case ENCODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case ENCODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + encoder->enable = !!*iarg; + } + break; + +#ifdef ENCODER_DUMP + case ENCODER_DUMP: + { + dump(client); + } + break; +#endif + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_ADV7175 >> 1, (I2C_ADV7175 >> 1) + 1, + I2C_ADV7176 >> 1, (I2C_ADV7176 >> 1) + 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_adv7175; + +static int +adv7175_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct adv7175 *encoder; + char *dname; + + dprintk(1, + KERN_INFO + "adv7175.c: detecting adv7175 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_adv7175; + client->flags = I2C_CLIENT_ALLOW_USE; + if ((client->addr == I2C_ADV7175 >> 1) || + (client->addr == (I2C_ADV7175 >> 1) + 1)) { + dname = adv7175_name; + } else if ((client->addr == I2C_ADV7176 >> 1) || + (client->addr == (I2C_ADV7176 >> 1) + 1)) { + dname = adv7176_name; + } else { + /* We should never get here!!! */ + kfree(client); + return 0; + } + strlcpy(I2C_NAME(client), dname, sizeof(I2C_NAME(client))); + + encoder = kmalloc(sizeof(struct adv7175), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(encoder, 0, sizeof(struct adv7175)); + encoder->norm = VIDEO_MODE_PAL; + encoder->input = 0; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + i = adv7175_write_block(client, init_common, sizeof(init_common)); + if (i >= 0) { + i = adv7175_write(client, 0x07, TR0MODE | TR0RST); + i = adv7175_write(client, 0x07, TR0MODE); + i = adv7175_read(client, 0x12); + dprintk(1, KERN_INFO "%s_attach: rev. %d at 0x%x\n", + I2C_NAME(client), i & 1, client->addr << 1); + } + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init error 0x%x\n", + I2C_NAME(client), i); + } + + return 0; +} + +static int +adv7175_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "adv7175.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &adv7175_detect_client); +} + +static int +adv7175_detach_client (struct i2c_client *client) +{ + struct adv7175 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_adv7175 = { + .owner = THIS_MODULE, + .name = "adv7175", /* name */ + + .id = I2C_DRIVERID_ADV7175, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = adv7175_attach_adapter, + .detach_client = adv7175_detach_client, + .command = adv7175_command, +}; + +static int __init +adv7175_init (void) +{ + return i2c_add_driver(&i2c_driver_adv7175); +} + +static void __exit +adv7175_exit (void) +{ + i2c_del_driver(&i2c_driver_adv7175); +} + +module_init(adv7175_init); +module_exit(adv7175_exit); diff --git a/drivers/media/video/arv.c b/drivers/media/video/arv.c new file mode 100644 index 000000000000..87fd3a7bb392 --- /dev/null +++ b/drivers/media/video/arv.c @@ -0,0 +1,916 @@ +/* + * Colour AR M64278(VGA) driver for Video4Linux + * + * Copyright (C) 2003 Takeo Takahashi <takahashi.takeo@renesas.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Some code is taken from AR driver sample program for M3T-M32700UT. + * + * AR driver sample (M32R SDK): + * Copyright (c) 2003 RENESAS TECHNOROGY CORPORATION + * AND RENESAS SOLUTIONS CORPORATION + * All Rights Reserved. + * + * 2003-09-01: Support w3cam by Takeo Takahashi + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/videodev.h> + +#include <asm/semaphore.h> +#include <asm/uaccess.h> +#include <asm/m32r.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#if 0 +#define DEBUG(n, args...) printk(args) +#define CHECK_LOST 1 +#else +#define DEBUG(n, args...) +#define CHECK_LOST 0 +#endif + +/* + * USE_INT is always 0, interrupt mode is not available + * on linux due to lack of speed + */ +#define USE_INT 0 /* Don't modify */ + +#define VERSION "0.03" + +#define ar_inl(addr) inl((unsigned long)(addr)) +#define ar_outl(val, addr) outl((unsigned long)(val),(unsigned long)(addr)) + +extern struct cpuinfo_m32r boot_cpu_data; + +/* + * CCD pixel size + * Note that M32700UT does not support CIF mode, but QVGA is + * supported by M32700UT hardware using VGA mode of AR LSI. + * + * Supported: VGA (Normal mode, Interlace mode) + * QVGA (Always Interlace mode of VGA) + * + */ +#define AR_WIDTH_VGA 640 +#define AR_HEIGHT_VGA 480 +#define AR_WIDTH_QVGA 320 +#define AR_HEIGHT_QVGA 240 +#define MIN_AR_WIDTH AR_WIDTH_QVGA +#define MIN_AR_HEIGHT AR_HEIGHT_QVGA +#define MAX_AR_WIDTH AR_WIDTH_VGA +#define MAX_AR_HEIGHT AR_HEIGHT_VGA + +/* bits & bytes per pixel */ +#define AR_BITS_PER_PIXEL 16 +#define AR_BYTES_PER_PIXEL (AR_BITS_PER_PIXEL/8) + +/* line buffer size */ +#define AR_LINE_BYTES_VGA (AR_WIDTH_VGA * AR_BYTES_PER_PIXEL) +#define AR_LINE_BYTES_QVGA (AR_WIDTH_QVGA * AR_BYTES_PER_PIXEL) +#define MAX_AR_LINE_BYTES AR_LINE_BYTES_VGA + +/* frame size & type */ +#define AR_FRAME_BYTES_VGA \ + (AR_WIDTH_VGA * AR_HEIGHT_VGA * AR_BYTES_PER_PIXEL) +#define AR_FRAME_BYTES_QVGA \ + (AR_WIDTH_QVGA * AR_HEIGHT_QVGA * AR_BYTES_PER_PIXEL) +#define MAX_AR_FRAME_BYTES \ + (MAX_AR_WIDTH * MAX_AR_HEIGHT * AR_BYTES_PER_PIXEL) + +#define AR_MAX_FRAME 15 + +/* capture size */ +#define AR_SIZE_VGA 0 +#define AR_SIZE_QVGA 1 + +/* capture mode */ +#define AR_MODE_INTERLACE 0 +#define AR_MODE_NORMAL 1 + +struct ar_device { + struct video_device *vdev; + unsigned int start_capture; /* duaring capture in INT. mode. */ +#if USE_INT + unsigned char *line_buff; /* DMA line buffer */ +#endif + unsigned char *frame[MAX_AR_HEIGHT]; /* frame data */ + short size; /* capture size */ + short mode; /* capture mode */ + int width, height; + int frame_bytes, line_bytes; + wait_queue_head_t wait; + struct semaphore lock; +}; + +static int video_nr = -1; /* video device number (first free) */ +static unsigned char yuv[MAX_AR_FRAME_BYTES]; + +/* module parameters */ +/* default frequency */ +#define DEFAULT_FREQ 50 /* 50 or 75 (MHz) is available as BCLK */ +static int freq = DEFAULT_FREQ; /* BCLK: available 50 or 70 (MHz) */ +static int vga = 0; /* default mode(0:QVGA mode, other:VGA mode) */ +static int vga_interlace = 0; /* 0 is normal mode for, else interlace mode */ +MODULE_PARM(freq, "i"); +MODULE_PARM(vga, "i"); +MODULE_PARM(vga_interlace, "i"); + +static int ar_initialize(struct video_device *dev); + +static inline void wait_for_vsync(void) +{ + while (ar_inl(ARVCR0) & ARVCR0_VDS) /* wait for VSYNC */ + cpu_relax(); + while (!(ar_inl(ARVCR0) & ARVCR0_VDS)) /* wait for VSYNC */ + cpu_relax(); +} + +static inline void wait_acknowledge(void) +{ + int i; + + for (i = 0; i < 1000; i++) + cpu_relax(); + while (ar_inl(PLDI2CSTS) & PLDI2CSTS_NOACK) + cpu_relax(); +} + +/******************************************************************* + * I2C functions + *******************************************************************/ +void iic(int n, unsigned long addr, unsigned long data1, unsigned long data2, + unsigned long data3) +{ + int i; + + /* Slave Address */ + ar_outl(addr, PLDI2CDATA); + wait_for_vsync(); + + /* Start */ + ar_outl(1, PLDI2CCND); + wait_acknowledge(); + + /* Transfer data 1 */ + ar_outl(data1, PLDI2CDATA); + wait_for_vsync(); + ar_outl(PLDI2CSTEN_STEN, PLDI2CSTEN); + wait_acknowledge(); + + /* Transfer data 2 */ + ar_outl(data2, PLDI2CDATA); + wait_for_vsync(); + ar_outl(PLDI2CSTEN_STEN, PLDI2CSTEN); + wait_acknowledge(); + + if (n == 3) { + /* Transfer data 3 */ + ar_outl(data3, PLDI2CDATA); + wait_for_vsync(); + ar_outl(PLDI2CSTEN_STEN, PLDI2CSTEN); + wait_acknowledge(); + } + + /* Stop */ + for (i = 0; i < 100; i++) + cpu_relax(); + ar_outl(2, PLDI2CCND); + ar_outl(2, PLDI2CCND); + + while (ar_inl(PLDI2CSTS) & PLDI2CSTS_BB) + cpu_relax(); +} + + +void init_iic(void) +{ + DEBUG(1, "init_iic:\n"); + + /* + * ICU Setting (iic) + */ + /* I2C Setting */ + ar_outl(0x0, PLDI2CCR); /* I2CCR Disable */ + ar_outl(0x0300, PLDI2CMOD); /* I2CMOD ACK/8b-data/7b-addr/auto */ + ar_outl(0x1, PLDI2CACK); /* I2CACK ACK */ + + /* I2C CLK */ + /* 50MH-100k */ + if (freq == 75) { + ar_outl(369, PLDI2CFREQ); /* BCLK = 75MHz */ + } else if (freq == 50) { + ar_outl(244, PLDI2CFREQ); /* BCLK = 50MHz */ + } else { + ar_outl(244, PLDI2CFREQ); /* default: BCLK = 50MHz */ + } + ar_outl(0x1, PLDI2CCR); /* I2CCR Enable */ +} + +/************************************************************************** + * + * Video4Linux Interface functions + * + **************************************************************************/ + +static inline void disable_dma(void) +{ + ar_outl(0x8000, M32R_DMAEN_PORTL); /* disable DMA0 */ +} + +static inline void enable_dma(void) +{ + ar_outl(0x8080, M32R_DMAEN_PORTL); /* enable DMA0 */ +} + +static inline void clear_dma_status(void) +{ + ar_outl(0x8000, M32R_DMAEDET_PORTL); /* clear status */ +} + +static inline void wait_for_vertical_sync(int exp_line) +{ +#if CHECK_LOST + int tmout = 10000; /* FIXME */ + int l; + + /* + * check HCOUNT because we cannot check vertical sync. + */ + for (; tmout >= 0; tmout--) { + l = ar_inl(ARVHCOUNT); + if (l == exp_line) + break; + } + if (tmout < 0) + printk("arv: lost %d -> %d\n", exp_line, l); +#else + while (ar_inl(ARVHCOUNT) != exp_line) + cpu_relax(); +#endif +} + +static ssize_t ar_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct ar_device *ar = v->priv; + long ret = ar->frame_bytes; /* return read bytes */ + unsigned long arvcr1 = 0; + unsigned long flags; + unsigned char *p; + int h, w; + unsigned char *py, *pu, *pv; +#if ! USE_INT + int l; +#endif + + DEBUG(1, "ar_read()\n"); + + if (ar->size == AR_SIZE_QVGA) + arvcr1 |= ARVCR1_QVGA; + if (ar->mode == AR_MODE_NORMAL) + arvcr1 |= ARVCR1_NORMAL; + + down(&ar->lock); + +#if USE_INT + local_irq_save(flags); + disable_dma(); + ar_outl(0xa1871300, M32R_DMA0CR0_PORTL); + ar_outl(0x01000000, M32R_DMA0CR1_PORTL); + + /* set AR FIFO address as source(BSEL5) */ + ar_outl(ARDATA32, M32R_DMA0CSA_PORTL); + ar_outl(ARDATA32, M32R_DMA0RSA_PORTL); + ar_outl(ar->line_buff, M32R_DMA0CDA_PORTL); /* destination addr. */ + ar_outl(ar->line_buff, M32R_DMA0RDA_PORTL); /* reload address */ + ar_outl(ar->line_bytes, M32R_DMA0CBCUT_PORTL); /* byte count (bytes) */ + ar_outl(ar->line_bytes, M32R_DMA0RBCUT_PORTL); /* reload count (bytes) */ + + /* + * Okey , kicks AR LSI to invoke an interrupt + */ + ar->start_capture = 0; + ar_outl(arvcr1 | ARVCR1_HIEN, ARVCR1); + local_irq_restore(flags); + /* .... AR interrupts .... */ + interruptible_sleep_on(&ar->wait); + if (signal_pending(current)) { + printk("arv: interrupted while get frame data.\n"); + ret = -EINTR; + goto out_up; + } +#else /* ! USE_INT */ + /* polling */ + ar_outl(arvcr1, ARVCR1); + disable_dma(); + ar_outl(0x8000, M32R_DMAEDET_PORTL); + ar_outl(0xa0861300, M32R_DMA0CR0_PORTL); + ar_outl(0x01000000, M32R_DMA0CR1_PORTL); + ar_outl(ARDATA32, M32R_DMA0CSA_PORTL); + ar_outl(ARDATA32, M32R_DMA0RSA_PORTL); + ar_outl(ar->line_bytes, M32R_DMA0CBCUT_PORTL); + ar_outl(ar->line_bytes, M32R_DMA0RBCUT_PORTL); + + local_irq_save(flags); + while (ar_inl(ARVHCOUNT) != 0) /* wait for 0 */ + cpu_relax(); + if (ar->mode == AR_MODE_INTERLACE && ar->size == AR_SIZE_VGA) { + for (h = 0; h < ar->height; h++) { + wait_for_vertical_sync(h); + if (h < (AR_HEIGHT_VGA/2)) + l = h << 1; + else + l = (((h - (AR_HEIGHT_VGA/2)) << 1) + 1); + ar_outl(virt_to_phys(ar->frame[l]), M32R_DMA0CDA_PORTL); + enable_dma(); + while (!(ar_inl(M32R_DMAEDET_PORTL) & 0x8000)) + cpu_relax(); + disable_dma(); + clear_dma_status(); + ar_outl(0xa0861300, M32R_DMA0CR0_PORTL); + } + } else { + for (h = 0; h < ar->height; h++) { + wait_for_vertical_sync(h); + ar_outl(virt_to_phys(ar->frame[h]), M32R_DMA0CDA_PORTL); + enable_dma(); + while (!(ar_inl(M32R_DMAEDET_PORTL) & 0x8000)) + cpu_relax(); + disable_dma(); + clear_dma_status(); + ar_outl(0xa0861300, M32R_DMA0CR0_PORTL); + } + } + local_irq_restore(flags); +#endif /* ! USE_INT */ + + /* + * convert YUV422 to YUV422P + * +--------------------+ + * | Y0,Y1,... | + * | ..............Yn | + * +--------------------+ + * | U0,U1,........Un | + * +--------------------+ + * | V0,V1,........Vn | + * +--------------------+ + */ + py = yuv; + pu = py + (ar->frame_bytes / 2); + pv = pu + (ar->frame_bytes / 4); + for (h = 0; h < ar->height; h++) { + p = ar->frame[h]; + for (w = 0; w < ar->line_bytes; w += 4) { + *py++ = *p++; + *pu++ = *p++; + *py++ = *p++; + *pv++ = *p++; + } + } + if (copy_to_user(buf, yuv, ar->frame_bytes)) { + printk("arv: failed while copy_to_user yuv.\n"); + ret = -EFAULT; + goto out_up; + } + DEBUG(1, "ret = %d\n", ret); +out_up: + up(&ar->lock); + return ret; +} + +static int ar_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct ar_device *ar = dev->priv; + + DEBUG(1, "ar_ioctl()\n"); + switch(cmd) { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + DEBUG(1, "VIDIOCGCAP:\n"); + strcpy(b->name, ar->vdev->name); + b->type = VID_TYPE_CAPTURE; + b->channels = 0; + b->audios = 0; + b->maxwidth = MAX_AR_WIDTH; + b->maxheight = MAX_AR_HEIGHT; + b->minwidth = MIN_AR_WIDTH; + b->minheight = MIN_AR_HEIGHT; + return 0; + } + case VIDIOCGCHAN: + DEBUG(1, "VIDIOCGCHAN:\n"); + return 0; + case VIDIOCSCHAN: + DEBUG(1, "VIDIOCSCHAN:\n"); + return 0; + case VIDIOCGTUNER: + DEBUG(1, "VIDIOCGTUNER:\n"); + return 0; + case VIDIOCSTUNER: + DEBUG(1, "VIDIOCSTUNER:\n"); + return 0; + case VIDIOCGPICT: + DEBUG(1, "VIDIOCGPICT:\n"); + return 0; + case VIDIOCSPICT: + DEBUG(1, "VIDIOCSPICT:\n"); + return 0; + case VIDIOCCAPTURE: + DEBUG(1, "VIDIOCCAPTURE:\n"); + return -EINVAL; + case VIDIOCGWIN: + { + struct video_window *w = arg; + DEBUG(1, "VIDIOCGWIN:\n"); + memset(w, 0, sizeof(w)); + w->width = ar->width; + w->height = ar->height; + return 0; + } + case VIDIOCSWIN: + { + struct video_window *w = arg; + DEBUG(1, "VIDIOCSWIN:\n"); + if ((w->width != AR_WIDTH_VGA || w->height != AR_HEIGHT_VGA) && + (w->width != AR_WIDTH_QVGA || w->height != AR_HEIGHT_QVGA)) + return -EINVAL; + + down(&ar->lock); + ar->width = w->width; + ar->height = w->height; + if (ar->width == AR_WIDTH_VGA) { + ar->size = AR_SIZE_VGA; + ar->frame_bytes = AR_FRAME_BYTES_VGA; + ar->line_bytes = AR_LINE_BYTES_VGA; + if (vga_interlace) + ar->mode = AR_MODE_INTERLACE; + else + ar->mode = AR_MODE_NORMAL; + } else { + ar->size = AR_SIZE_QVGA; + ar->frame_bytes = AR_FRAME_BYTES_QVGA; + ar->line_bytes = AR_LINE_BYTES_QVGA; + ar->mode = AR_MODE_INTERLACE; + } + up(&ar->lock); + return 0; + } + case VIDIOCGFBUF: + DEBUG(1, "VIDIOCGFBUF:\n"); + return -EINVAL; + case VIDIOCSFBUF: + DEBUG(1, "VIDIOCSFBUF:\n"); + return -EINVAL; + case VIDIOCKEY: + DEBUG(1, "VIDIOCKEY:\n"); + return 0; + case VIDIOCGFREQ: + DEBUG(1, "VIDIOCGFREQ:\n"); + return -EINVAL; + case VIDIOCSFREQ: + DEBUG(1, "VIDIOCSFREQ:\n"); + return -EINVAL; + case VIDIOCGAUDIO: + DEBUG(1, "VIDIOCGAUDIO:\n"); + return -EINVAL; + case VIDIOCSAUDIO: + DEBUG(1, "VIDIOCSAUDIO:\n"); + return -EINVAL; + case VIDIOCSYNC: + DEBUG(1, "VIDIOCSYNC:\n"); + return -EINVAL; + case VIDIOCMCAPTURE: + DEBUG(1, "VIDIOCMCAPTURE:\n"); + return -EINVAL; + case VIDIOCGMBUF: + DEBUG(1, "VIDIOCGMBUF:\n"); + return -EINVAL; + case VIDIOCGUNIT: + DEBUG(1, "VIDIOCGUNIT:\n"); + return -EINVAL; + case VIDIOCGCAPTURE: + DEBUG(1, "VIDIOCGCAPTURE:\n"); + return -EINVAL; + case VIDIOCSCAPTURE: + DEBUG(1, "VIDIOCSCAPTURE:\n"); + return -EINVAL; + case VIDIOCSPLAYMODE: + DEBUG(1, "VIDIOCSPLAYMODE:\n"); + return -EINVAL; + case VIDIOCSWRITEMODE: + DEBUG(1, "VIDIOCSWRITEMODE:\n"); + return -EINVAL; + case VIDIOCGPLAYINFO: + DEBUG(1, "VIDIOCGPLAYINFO:\n"); + return -EINVAL; + case VIDIOCSMICROCODE: + DEBUG(1, "VIDIOCSMICROCODE:\n"); + return -EINVAL; + case VIDIOCGVBIFMT: + DEBUG(1, "VIDIOCGVBIFMT:\n"); + return -EINVAL; + case VIDIOCSVBIFMT: + DEBUG(1, "VIDIOCSVBIFMT:\n"); + return -EINVAL; + default: + DEBUG(1, "Unknown ioctl(0x%08x)\n", cmd); + return -ENOIOCTLCMD; + } + return 0; +} + +static int ar_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, ar_do_ioctl); +} + +#if USE_INT +/* + * Interrupt handler + */ +static void ar_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + struct ar_device *ar = dev; + unsigned int line_count; + unsigned int line_number; + unsigned int arvcr1; + + line_count = ar_inl(ARVHCOUNT); /* line number */ + if (ar->mode == AR_MODE_INTERLACE && ar->size == AR_SIZE_VGA) { + /* operations for interlace mode */ + if ( line_count < (AR_HEIGHT_VGA/2) ) /* even line */ + line_number = (line_count << 1); + else /* odd line */ + line_number = + (((line_count - (AR_HEIGHT_VGA/2)) << 1) + 1); + } else { + line_number = line_count; + } + + if (line_number == 0) { + /* + * It is an interrupt for line 0. + * we have to start capture. + */ + disable_dma(); +#if 0 + ar_outl(ar->line_buff, M32R_DMA0CDA_PORTL); /* needless? */ +#endif + memcpy(ar->frame[0], ar->line_buff, ar->line_bytes); +#if 0 + ar_outl(0xa1861300, M32R_DMA0CR0_PORTL); +#endif + enable_dma(); + ar->start_capture = 1; /* during capture */ + return; + } + + if (ar->start_capture == 1 && line_number <= (ar->height - 1)) { + disable_dma(); + memcpy(ar->frame[line_number], ar->line_buff, ar->line_bytes); + + /* + * if captured all line of a frame, disable AR interrupt + * and wake a process up. + */ + if (line_number == (ar->height - 1)) { /* end of line */ + + ar->start_capture = 0; + + /* disable AR interrupt request */ + arvcr1 = ar_inl(ARVCR1); + arvcr1 &= ~ARVCR1_HIEN; /* clear int. flag */ + ar_outl(arvcr1, ARVCR1); /* disable */ + wake_up_interruptible(&ar->wait); + } else { +#if 0 + ar_outl(ar->line_buff, M32R_DMA0CDA_PORTL); + ar_outl(0xa1861300, M32R_DMA0CR0_PORTL); +#endif + enable_dma(); + } + } +} +#endif + +/* + * ar_initialize() + * ar_initialize() is called by video_register_device() and + * initializes AR LSI and peripherals. + * + * -1 is returned in all failures. + * 0 is returned in success. + * + */ +static int ar_initialize(struct video_device *dev) +{ + struct ar_device *ar = dev->priv; + unsigned long cr = 0; + int i,found=0; + + DEBUG(1, "ar_initialize:\n"); + + /* + * initialize AR LSI + */ + ar_outl(0, ARVCR0); /* assert reset of AR LSI */ + for (i = 0; i < 0x18; i++) /* wait for over 10 cycles @ 27MHz */ + cpu_relax(); + ar_outl(ARVCR0_RST, ARVCR0); /* negate reset of AR LSI (enable) */ + for (i = 0; i < 0x40d; i++) /* wait for over 420 cycles @ 27MHz */ + cpu_relax(); + + /* AR uses INT3 of CPU as interrupt pin. */ + ar_outl(ARINTSEL_INT3, ARINTSEL); + + if (ar->size == AR_SIZE_QVGA) + cr |= ARVCR1_QVGA; + if (ar->mode == AR_MODE_NORMAL) + cr |= ARVCR1_NORMAL; + ar_outl(cr, ARVCR1); + + /* + * Initialize IIC so that CPU can communicate with AR LSI, + * and send boot commands to AR LSI. + */ + init_iic(); + + for (i = 0; i < 0x100000; i++) { /* > 0xa1d10, 56ms */ + if ((ar_inl(ARVCR0) & ARVCR0_VDS)) { /* VSYNC */ + found = 1; + break; + } + } + + if (found == 0) + return -ENODEV; + + printk("arv: Initializing "); + + iic(2,0x78,0x11,0x01,0x00); /* start */ + iic(3,0x78,0x12,0x00,0x06); + iic(3,0x78,0x12,0x12,0x30); + iic(3,0x78,0x12,0x15,0x58); + iic(3,0x78,0x12,0x17,0x30); + printk("."); + iic(3,0x78,0x12,0x1a,0x97); + iic(3,0x78,0x12,0x1b,0xff); + iic(3,0x78,0x12,0x1c,0xff); + iic(3,0x78,0x12,0x26,0x10); + iic(3,0x78,0x12,0x27,0x00); + printk("."); + iic(2,0x78,0x34,0x02,0x00); + iic(2,0x78,0x7a,0x10,0x00); + iic(2,0x78,0x80,0x39,0x00); + iic(2,0x78,0x81,0xe6,0x00); + iic(2,0x78,0x8d,0x00,0x00); + printk("."); + iic(2,0x78,0x8e,0x0c,0x00); + iic(2,0x78,0x8f,0x00,0x00); +#if 0 + iic(2,0x78,0x90,0x00,0x00); /* AWB on=1 off=0 */ +#endif + iic(2,0x78,0x93,0x01,0x00); + iic(2,0x78,0x94,0xcd,0x00); + iic(2,0x78,0x95,0x00,0x00); + printk("."); + iic(2,0x78,0x96,0xa0,0x00); + iic(2,0x78,0x97,0x00,0x00); + iic(2,0x78,0x98,0x60,0x00); + iic(2,0x78,0x99,0x01,0x00); + iic(2,0x78,0x9a,0x19,0x00); + printk("."); + iic(2,0x78,0x9b,0x02,0x00); + iic(2,0x78,0x9c,0xe8,0x00); + iic(2,0x78,0x9d,0x02,0x00); + iic(2,0x78,0x9e,0x2e,0x00); + iic(2,0x78,0xb8,0x78,0x00); + iic(2,0x78,0xba,0x05,0x00); +#if 0 + iic(2,0x78,0x83,0x8c,0x00); /* brightness */ +#endif + printk("."); + + /* color correction */ + iic(3,0x78,0x49,0x00,0x95); /* a */ + iic(3,0x78,0x49,0x01,0x96); /* b */ + iic(3,0x78,0x49,0x03,0x85); /* c */ + iic(3,0x78,0x49,0x04,0x97); /* d */ + iic(3,0x78,0x49,0x02,0x7e); /* e(Lo) */ + iic(3,0x78,0x49,0x05,0xa4); /* f(Lo) */ + iic(3,0x78,0x49,0x06,0x04); /* e(Hi) */ + iic(3,0x78,0x49,0x07,0x04); /* e(Hi) */ + iic(2,0x78,0x48,0x01,0x00); /* on=1 off=0 */ + + printk("."); + iic(2,0x78,0x11,0x00,0x00); /* end */ + printk(" done\n"); + return 0; +} + + +void ar_release(struct video_device *vfd) +{ + struct ar_device *ar = vfd->priv; + down(&ar->lock); + video_device_release(vfd); +} + +/**************************************************************************** + * + * Video4Linux Module functions + * + ****************************************************************************/ +static struct file_operations ar_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .read = ar_read, + .ioctl = ar_ioctl, + .llseek = no_llseek, +}; + +static struct video_device ar_template = { + .owner = THIS_MODULE, + .name = "Colour AR VGA", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_ARV, + .fops = &ar_fops, + .release = ar_release, + .minor = -1, +}; + +#define ALIGN4(x) ((((int)(x)) & 0x3) == 0) +static struct ar_device ardev; + +static int __init ar_init(void) +{ + struct ar_device *ar; + int ret; + int i; + + DEBUG(1, "ar_init:\n"); + ret = -EIO; + printk(KERN_INFO "arv: Colour AR VGA driver %s\n", VERSION); + + ar = &ardev; + memset(ar, 0, sizeof(struct ar_device)); + +#if USE_INT + /* allocate a DMA buffer for 1 line. */ + ar->line_buff = kmalloc(MAX_AR_LINE_BYTES, GFP_KERNEL | GFP_DMA); + if (ar->line_buff == NULL || ! ALIGN4(ar->line_buff)) { + printk("arv: buffer allocation failed for DMA.\n"); + ret = -ENOMEM; + goto out_end; + } +#endif + /* allocate buffers for a frame */ + for (i = 0; i < MAX_AR_HEIGHT; i++) { + ar->frame[i] = kmalloc(MAX_AR_LINE_BYTES, GFP_KERNEL); + if (ar->frame[i] == NULL || ! ALIGN4(ar->frame[i])) { + printk("arv: buffer allocation failed for frame.\n"); + ret = -ENOMEM; + goto out_line_buff; + } + } + + ar->vdev = video_device_alloc(); + if (!ar->vdev) { + printk(KERN_ERR "arv: video_device_alloc() failed\n"); + return -ENOMEM; + } + memcpy(ar->vdev, &ar_template, sizeof(ar_template)); + ar->vdev->priv = ar; + + if (vga) { + ar->width = AR_WIDTH_VGA; + ar->height = AR_HEIGHT_VGA; + ar->size = AR_SIZE_VGA; + ar->frame_bytes = AR_FRAME_BYTES_VGA; + ar->line_bytes = AR_LINE_BYTES_VGA; + if (vga_interlace) + ar->mode = AR_MODE_INTERLACE; + else + ar->mode = AR_MODE_NORMAL; + } else { + ar->width = AR_WIDTH_QVGA; + ar->height = AR_HEIGHT_QVGA; + ar->size = AR_SIZE_QVGA; + ar->frame_bytes = AR_FRAME_BYTES_QVGA; + ar->line_bytes = AR_LINE_BYTES_QVGA; + ar->mode = AR_MODE_INTERLACE; + } + init_MUTEX(&ar->lock); + init_waitqueue_head(&ar->wait); + +#if USE_INT + if (request_irq(M32R_IRQ_INT3, ar_interrupt, 0, "arv", ar)) { + printk("arv: request_irq(%d) failed.\n", M32R_IRQ_INT3); + ret = -EIO; + goto out_irq; + } +#endif + + if (ar_initialize(ar->vdev) != 0) { + printk("arv: M64278 not found.\n"); + ret = -ENODEV; + goto out_dev; + } + + /* + * ok, we can initialize h/w according to parameters, + * so register video device as a frame grabber type. + * device is named "video[0-64]". + * video_register_device() initializes h/w using ar_initialize(). + */ + if (video_register_device(ar->vdev, VFL_TYPE_GRABBER, video_nr) != 0) { + /* return -1, -ENFILE(full) or others */ + printk("arv: register video (Colour AR) failed.\n"); + ret = -ENODEV; + goto out_dev; + } + + printk("video%d: Found M64278 VGA (IRQ %d, Freq %dMHz).\n", + ar->vdev->minor, M32R_IRQ_INT3, freq); + + return 0; + +out_dev: +#if USE_INT + free_irq(M32R_IRQ_INT3, ar); + +out_irq: +#endif + for (i = 0; i < MAX_AR_HEIGHT; i++) { + if (ar->frame[i]) + kfree(ar->frame[i]); + } + +out_line_buff: +#if USE_INT + kfree(ar->line_buff); + +out_end: +#endif + return ret; +} + + +static int __init ar_init_module(void) +{ + freq = (boot_cpu_data.bus_clock / 1000000); + printk("arv: Bus clock %d\n", freq); + if (freq != 50 && freq != 75) + freq = DEFAULT_FREQ; + return ar_init(); +} + +static void __exit ar_cleanup_module(void) +{ + struct ar_device *ar; + int i; + + ar = &ardev; + video_unregister_device(ar->vdev); +#if USE_INT + free_irq(M32R_IRQ_INT3, ar); +#endif + for (i = 0; i < MAX_AR_HEIGHT; i++) { + if (ar->frame[i]) + kfree(ar->frame[i]); + } +#if USE_INT + kfree(ar->line_buff); +#endif +} + +module_init(ar_init_module); +module_exit(ar_cleanup_module); + +MODULE_AUTHOR("Takeo Takahashi <takahashi.takeo@renesas.com>"); +MODULE_DESCRIPTION("Colour AR M64278(VGA) for Video4Linux"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/bt819.c b/drivers/media/video/bt819.c new file mode 100644 index 000000000000..cf0db2554a80 --- /dev/null +++ b/drivers/media/video/bt819.c @@ -0,0 +1,660 @@ +/* + * bt819 - BT819A VideoStream Decoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson <mike@mlb.org> + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Brooktree-819 video decoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct bt819 { + unsigned char reg[32]; + + int initialized; + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +struct timing { + int hactive; + int hdelay; + int vactive; + int vdelay; + int hscale; + int vscale; +}; + +/* for values, see the bt819 datasheet */ +static struct timing timing_data[] = { + {864 - 24, 20, 625 - 2, 1, 0x0504, 0x0000}, + {858 - 24, 20, 525 - 2, 1, 0x00f8, 0x0000}, +}; + +#define I2C_BT819 0x8a + +/* ----------------------------------------------------------------------- */ + +static inline int +bt819_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +bt819_setbit (struct i2c_client *client, + u8 reg, + u8 bit, + u8 value) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + + return bt819_write(client, reg, + (decoder-> + reg[reg] & ~(1 << bit)) | + (value ? (1 << bit) : 0)); +} + +static int +bt819_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the bt819 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct bt819 *decoder = i2c_get_clientdata(client); + struct i2c_msg msg; + u8 block_data[32]; + + msg.addr = client->addr; + msg.flags = 0; + while (len >= 2) { + msg.buf = (char *) block_data; + msg.len = 0; + block_data[msg.len++] = reg = data[0]; + do { + block_data[msg.len++] = + decoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + msg.len < 32); + if ((ret = i2c_transfer(client->adapter, + &msg, 1)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = bt819_write(client, reg, *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static inline int +bt819_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +bt819_init (struct i2c_client *client) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + + static unsigned char init[] = { + //0x1f, 0x00, /* Reset */ + 0x01, 0x59, /* 0x01 input format */ + 0x02, 0x00, /* 0x02 temporal decimation */ + 0x03, 0x12, /* 0x03 Cropping msb */ + 0x04, 0x16, /* 0x04 Vertical Delay, lsb */ + 0x05, 0xe0, /* 0x05 Vertical Active lsb */ + 0x06, 0x80, /* 0x06 Horizontal Delay lsb */ + 0x07, 0xd0, /* 0x07 Horizontal Active lsb */ + 0x08, 0x00, /* 0x08 Horizontal Scaling msb */ + 0x09, 0xf8, /* 0x09 Horizontal Scaling lsb */ + 0x0a, 0x00, /* 0x0a Brightness control */ + 0x0b, 0x30, /* 0x0b Miscellaneous control */ + 0x0c, 0xd8, /* 0x0c Luma Gain lsb */ + 0x0d, 0xfe, /* 0x0d Chroma Gain (U) lsb */ + 0x0e, 0xb4, /* 0x0e Chroma Gain (V) msb */ + 0x0f, 0x00, /* 0x0f Hue control */ + 0x12, 0x04, /* 0x12 Output Format */ + 0x13, 0x20, /* 0x13 Vertial Scaling msb 0x00 + chroma comb OFF, line drop scaling, interlace scaling + BUG? Why does turning the chroma comb on fuck up color? + Bug in the bt819 stepping on my board? + */ + 0x14, 0x00, /* 0x14 Vertial Scaling lsb */ + 0x16, 0x07, /* 0x16 Video Timing Polarity + ACTIVE=active low + FIELD: high=odd, + vreset=active high, + hreset=active high */ + 0x18, 0x68, /* 0x18 AGC Delay */ + 0x19, 0x5d, /* 0x19 Burst Gate Delay */ + 0x1a, 0x80, /* 0x1a ADC Interface */ + }; + + struct timing *timing = &timing_data[decoder->norm]; + + init[0x03 * 2 - 1] = + (((timing->vdelay >> 8) & 0x03) << 6) | (((timing-> + vactive >> 8) & + 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | ((timing-> + hactive >> 8) & + 0x03); + init[0x04 * 2 - 1] = timing->vdelay & 0xff; + init[0x05 * 2 - 1] = timing->vactive & 0xff; + init[0x06 * 2 - 1] = timing->hdelay & 0xff; + init[0x07 * 2 - 1] = timing->hactive & 0xff; + init[0x08 * 2 - 1] = timing->hscale >> 8; + init[0x09 * 2 - 1] = timing->hscale & 0xff; + /* 0x15 in array is address 0x19 */ + init[0x15 * 2 - 1] = (decoder->norm == 0) ? 115 : 93; /* Chroma burst delay */ + /* reset */ + bt819_write(client, 0x1f, 0x00); + mdelay(1); + + /* init */ + return bt819_write_block(client, init, sizeof(init)); + +} + +/* ----------------------------------------------------------------------- */ + +static int +bt819_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + int temp; + + struct bt819 *decoder = i2c_get_clientdata(client); + + if (!decoder->initialized) { // First call to bt819_init could be + bt819_init(client); // without #FRST = 0 + decoder->initialized = 1; + } + + switch (cmd) { + + case 0: + /* This is just for testing!!! */ + bt819_init(client); + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + cap->flags = VIDEO_DECODER_PAL | + VIDEO_DECODER_NTSC | + VIDEO_DECODER_AUTO | + VIDEO_DECODER_CCIR; + cap->inputs = 8; + cap->outputs = 1; + } + break; + + case DECODER_GET_STATUS: + { + int *iarg = arg; + int status; + int res; + + status = bt819_read(client, 0x00); + res = 0; + if ((status & 0x80)) { + res |= DECODER_STATUS_GOOD; + } + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + default: + case VIDEO_MODE_AUTO: + if ((status & 0x10)) { + res |= DECODER_STATUS_PAL; + } else { + res |= DECODER_STATUS_NTSC; + } + break; + } + res |= DECODER_STATUS_COLOR; + *iarg = res; + + dprintk(1, KERN_INFO "%s: get status %x\n", I2C_NAME(client), + *iarg); + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg; + struct timing *timing = NULL; + + dprintk(1, KERN_INFO "%s: set norm %x\n", I2C_NAME(client), + *iarg); + + switch (*iarg) { + case VIDEO_MODE_NTSC: + bt819_setbit(client, 0x01, 0, 1); + bt819_setbit(client, 0x01, 1, 0); + bt819_setbit(client, 0x01, 5, 0); + bt819_write(client, 0x18, 0x68); + bt819_write(client, 0x19, 0x5d); + //bt819_setbit(client, 0x1a, 5, 1); + timing = &timing_data[VIDEO_MODE_NTSC]; + break; + case VIDEO_MODE_PAL: + bt819_setbit(client, 0x01, 0, 1); + bt819_setbit(client, 0x01, 1, 1); + bt819_setbit(client, 0x01, 5, 1); + bt819_write(client, 0x18, 0x7f); + bt819_write(client, 0x19, 0x72); + //bt819_setbit(client, 0x1a, 5, 0); + timing = &timing_data[VIDEO_MODE_PAL]; + break; + case VIDEO_MODE_AUTO: + bt819_setbit(client, 0x01, 0, 0); + bt819_setbit(client, 0x01, 1, 0); + break; + default: + dprintk(1, + KERN_ERR + "%s: unsupported norm %d\n", + I2C_NAME(client), *iarg); + return -EINVAL; + } + + if (timing) { + bt819_write(client, 0x03, + (((timing->vdelay >> 8) & 0x03) << 6) | + (((timing->vactive >> 8) & 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | + ((timing->hactive >> 8) & 0x03) ); + bt819_write(client, 0x04, timing->vdelay & 0xff); + bt819_write(client, 0x05, timing->vactive & 0xff); + bt819_write(client, 0x06, timing->hdelay & 0xff); + bt819_write(client, 0x07, timing->hactive & 0xff); + bt819_write(client, 0x08, (timing->hscale >> 8) & 0xff); + bt819_write(client, 0x09, timing->hscale & 0xff); + } + + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set input %x\n", I2C_NAME(client), + *iarg); + + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + + if (decoder->input != *iarg) { + decoder->input = *iarg; + /* select mode */ + if (decoder->input == 0) { + bt819_setbit(client, 0x0b, 6, 0); + bt819_setbit(client, 0x1a, 1, 1); + } else { + bt819_setbit(client, 0x0b, 6, 1); + bt819_setbit(client, 0x1a, 1, 0); + } + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set output %x\n", I2C_NAME(client), + *iarg); + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + dprintk(1, KERN_INFO "%s: enable output %x\n", + I2C_NAME(client), *iarg); + + if (decoder->enable != enable) { + decoder->enable = enable; + + if (decoder->enable) { + bt819_setbit(client, 0x16, 7, 0); + } else { + bt819_setbit(client, 0x16, 7, 1); + } + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + dprintk(1, + KERN_INFO + "%s: set picture brightness %d contrast %d colour %d\n", + I2C_NAME(client), pic->brightness, pic->contrast, + pic->colour); + + + if (decoder->bright != pic->brightness) { + /* We want -128 to 127 we get 0-65535 */ + decoder->bright = pic->brightness; + bt819_write(client, 0x0a, + (decoder->bright >> 8) - 128); + } + + if (decoder->contrast != pic->contrast) { + /* We want 0 to 511 we get 0-65535 */ + decoder->contrast = pic->contrast; + bt819_write(client, 0x0c, + (decoder->contrast >> 7) & 0xff); + bt819_setbit(client, 0x0b, 2, + ((decoder->contrast >> 15) & 0x01)); + } + + if (decoder->sat != pic->colour) { + /* We want 0 to 511 we get 0-65535 */ + decoder->sat = pic->colour; + bt819_write(client, 0x0d, + (decoder->sat >> 7) & 0xff); + bt819_setbit(client, 0x0b, 1, + ((decoder->sat >> 15) & 0x01)); + + temp = (decoder->sat * 201) / 237; + bt819_write(client, 0x0e, (temp >> 7) & 0xff); + bt819_setbit(client, 0x0b, 0, (temp >> 15) & 0x01); + } + + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + bt819_write(client, 0x0f, + 128 - (decoder->hue >> 8)); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { + I2C_BT819 >> 1, + I2C_CLIENT_END, +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_bt819; + +static int +bt819_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i, id; + struct bt819 *decoder; + struct i2c_client *client; + + dprintk(1, + KERN_INFO + "saa7111.c: detecting bt819 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_bt819; + client->flags = I2C_CLIENT_ALLOW_USE; + + decoder = kmalloc(sizeof(struct bt819), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + + memset(decoder, 0, sizeof(struct bt819)); + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + decoder->initialized = 0; + i2c_set_clientdata(client, decoder); + + id = bt819_read(client, 0x17); + switch (id & 0xf0) { + case 0x70: + strlcpy(I2C_NAME(client), "bt819a", sizeof(I2C_NAME(client))); + break; + case 0x60: + strlcpy(I2C_NAME(client), "bt817a", sizeof(I2C_NAME(client))); + break; + case 0x20: + strlcpy(I2C_NAME(client), "bt815a", sizeof(I2C_NAME(client))); + break; + default: + dprintk(1, + KERN_ERR + "bt819: unknown chip version 0x%x (ver 0x%x)\n", + id & 0xf0, id & 0x0f); + kfree(decoder); + kfree(client); + return 0; + } + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(decoder); + return i; + } + + i = bt819_init(client); + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init status %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version 0x%x at address 0x%x\n", + I2C_NAME(client), id & 0x0f, + client->addr << 1); + } + + return 0; +} + +static int +bt819_attach_adapter (struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, &bt819_detect_client); +} + +static int +bt819_detach_client (struct i2c_client *client) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_bt819 = { + .owner = THIS_MODULE, + .name = "bt819", + + .id = I2C_DRIVERID_BT819, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = bt819_attach_adapter, + .detach_client = bt819_detach_client, + .command = bt819_command, +}; + +static int __init +bt819_init_module (void) +{ + return i2c_add_driver(&i2c_driver_bt819); +} + +static void __exit +bt819_exit (void) +{ + i2c_del_driver(&i2c_driver_bt819); +} + +module_init(bt819_init_module); +module_exit(bt819_exit); diff --git a/drivers/media/video/bt832.c b/drivers/media/video/bt832.c new file mode 100644 index 000000000000..efe605a113a1 --- /dev/null +++ b/drivers/media/video/bt832.c @@ -0,0 +1,271 @@ +/* Driver for Bt832 CMOS Camera Video Processor + i2c-addresses: 0x88 or 0x8a + + The BT832 interfaces to a Quartzsight Digital Camera (352x288, 25 or 30 fps) + via a 9 pin connector ( 4-wire SDATA, 2-wire i2c, SCLK, VCC, GND). + It outputs an 8-bit 4:2:2 YUV or YCrCb video signal which can be directly + connected to bt848/bt878 GPIO pins on this purpose. + (see: VLSI Vision Ltd. www.vvl.co.uk for camera datasheets) + + Supported Cards: + - Pixelview Rev.4E: 0x8a + GPIO 0x400000 toggles Bt832 RESET, and the chip changes to i2c 0x88 ! + + (c) Gunther Mayer, 2002 + + STATUS: + - detect chip and hexdump + - reset chip and leave low power mode + - detect camera present + + TODO: + - make it work (find correct setup for Bt832 and Bt878) +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> + +#include "id.h" +#include "audiochip.h" +#include "bttv.h" +#include "bt832.h" + +MODULE_LICENSE("GPL"); + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_BT832_ALT1>>1,I2C_BT832_ALT2>>1,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug) printk + +static int bt832_detach(struct i2c_client *client); + + +static struct i2c_driver driver; +static struct i2c_client client_template; + +struct bt832 { + struct i2c_client client; +}; + +int bt832_hexdump(struct i2c_client *i2c_client_s, unsigned char *buf) +{ + int i,rc; + buf[0]=0x80; // start at register 0 with auto-increment + if (1 != (rc = i2c_master_send(i2c_client_s,buf,1))) + printk("bt832: i2c i/o error: rc == %d (should be 1)\n",rc); + + for(i=0;i<65;i++) + buf[i]=0; + if (65 != (rc=i2c_master_recv(i2c_client_s,buf,65))) + printk("bt832: i2c i/o error: rc == %d (should be 65)\n",rc); + + // Note: On READ the first byte is the current index + // (e.g. 0x80, what we just wrote) + + if(1) { + int i; + printk("BT832 hexdump:\n"); + for(i=1;i<65;i++) { + if(i!=1) { + if(((i-1)%8)==0) printk(" "); + if(((i-1)%16)==0) printk("\n"); + } + printk(" %02x",buf[i]); + } + printk("\n"); + } + return 0; +} + +// Return: 1 (is a bt832), 0 (No bt832 here) +int bt832_init(struct i2c_client *i2c_client_s) +{ + unsigned char *buf; + int rc; + + buf=kmalloc(65,GFP_KERNEL); + bt832_hexdump(i2c_client_s,buf); + + if(buf[0x40] != 0x31) { + printk("bt832: this i2c chip is no bt832 (id=%02x). Detaching.\n",buf[0x40]); + kfree(buf); + return 0; + } + + printk("Write 0 tp VPSTATUS\n"); + buf[0]=BT832_VP_STATUS; // Reg.52 + buf[1]= 0x00; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error VPS: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + + + // Leave low power mode: + printk("Bt832: leave low power mode.\n"); + buf[0]=BT832_CAM_SETUP0; //0x39 57 + buf[1]=0x08; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error LLPM: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + + printk("Write 0 tp VPSTATUS\n"); + buf[0]=BT832_VP_STATUS; // Reg.52 + buf[1]= 0x00; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error VPS: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + + + // Enable Output + printk("Enable Output\n"); + buf[0]=BT832_VP_CONTROL1; // Reg.40 + buf[1]= 0x27 & (~0x01); // Default | !skip + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error EO: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); + +#if 0 + // Full 30/25 Frame rate + printk("Full 30/25 Frame rate\n"); + buf[0]=BT832_VP_CONTROL0; // Reg.39 + buf[1]= 0x00; + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error FFR: rc == %d (should be 2)\n",rc); + + bt832_hexdump(i2c_client_s,buf); +#endif + +#if 1 + // for testing (even works when no camera attached) + printk("bt832: *** Generate NTSC M Bars *****\n"); + buf[0]=BT832_VP_TESTCONTROL0; // Reg. 42 + buf[1]=3; // Generate NTSC System M bars, Generate Frame timing internally + if (2 != (rc = i2c_master_send(i2c_client_s,buf,2))) + printk("bt832: i2c i/o error MBAR: rc == %d (should be 2)\n",rc); +#endif + + printk("Bt832: Camera Present: %s\n", + (buf[1+BT832_CAM_STATUS] & BT832_56_CAMERA_PRESENT) ? "yes":"no"); + + bt832_hexdump(i2c_client_s,buf); + kfree(buf); + return 1; +} + + + +static int bt832_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct bt832 *t; + + printk("bt832_attach\n"); + + client_template.adapter = adap; + client_template.addr = addr; + + printk("bt832: chip found @ 0x%x\n", addr<<1); + + if (NULL == (t = kmalloc(sizeof(*t), GFP_KERNEL))) + return -ENOMEM; + memset(t,0,sizeof(*t)); + t->client = client_template; + t->client.data = t; + i2c_attach_client(&t->client); + + if(! bt832_init(&t->client)) { + bt832_detach(&t->client); + return -1; + } + + return 0; +} + +static int bt832_probe(struct i2c_adapter *adap) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, bt832_attach); + return 0; +} + +static int bt832_detach(struct i2c_client *client) +{ + struct bt832 *t = (struct bt832*)client->data; + + printk("bt832: detach.\n"); + i2c_detach_client(client); + kfree(t); + return 0; +} + +static int +bt832_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct bt832 *t = (struct bt832*)client->data; + + printk("bt832: command %x\n",cmd); + + switch (cmd) { + case BT832_HEXDUMP: { + unsigned char *buf; + buf=kmalloc(65,GFP_KERNEL); + bt832_hexdump(&t->client,buf); + kfree(buf); + } + break; + case BT832_REATTACH: + printk("bt832: re-attach\n"); + i2c_del_driver(&driver); + i2c_add_driver(&driver); + break; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "i2c bt832 driver", + .id = -1, /* FIXME */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = bt832_probe, + .detach_client = bt832_detach, + .command = bt832_command, +}; +static struct i2c_client client_template = +{ + .name = "bt832", + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + + +int bt832_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void bt832_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(bt832_init_module); +module_exit(bt832_cleanup_module); + diff --git a/drivers/media/video/bt832.h b/drivers/media/video/bt832.h new file mode 100644 index 000000000000..7a98c06e0e34 --- /dev/null +++ b/drivers/media/video/bt832.h @@ -0,0 +1,305 @@ +/* Bt832 CMOS Camera Video Processor (VP) + + The Bt832 CMOS Camera Video Processor chip connects a Quartsight CMOS + color digital camera directly to video capture devices via an 8-bit, + 4:2:2 YUV or YCrCb video interface. + + i2c addresses: 0x88 or 0x8a + */ + +/* The 64 registers: */ + +// Input Processor +#define BT832_OFFSET 0 +#define BT832_RCOMP 1 +#define BT832_G1COMP 2 +#define BT832_G2COMP 3 +#define BT832_BCOMP 4 +// Exposures: +#define BT832_FINEH 5 +#define BT832_FINEL 6 +#define BT832_COARSEH 7 +#define BT832_COARSEL 8 +#define BT832_CAMGAIN 9 +// Main Processor: +#define BT832_M00 10 +#define BT832_M01 11 +#define BT832_M02 12 +#define BT832_M10 13 +#define BT832_M11 14 +#define BT832_M12 15 +#define BT832_M20 16 +#define BT832_M21 17 +#define BT832_M22 18 +#define BT832_APCOR 19 +#define BT832_GAMCOR 20 +// Level Accumulator Inputs +#define BT832_VPCONTROL2 21 +#define BT832_ZONECODE0 22 +#define BT832_ZONECODE1 23 +#define BT832_ZONECODE2 24 +#define BT832_ZONECODE3 25 +// Level Accumulator Outputs: +#define BT832_RACC 26 +#define BT832_GACC 27 +#define BT832_BACC 28 +#define BT832_BLACKACC 29 +#define BT832_EXP_AGC 30 +#define BT832_LACC0 31 +#define BT832_LACC1 32 +#define BT832_LACC2 33 +#define BT832_LACC3 34 +#define BT832_LACC4 35 +#define BT832_LACC5 36 +#define BT832_LACC6 37 +#define BT832_LACC7 38 +// System: +#define BT832_VP_CONTROL0 39 +#define BT832_VP_CONTROL1 40 +#define BT832_THRESH 41 +#define BT832_VP_TESTCONTROL0 42 +#define BT832_VP_DMCODE 43 +#define BT832_ACB_CONFIG 44 +#define BT832_ACB_GNBASE 45 +#define BT832_ACB_MU 46 +#define BT832_CAM_TEST0 47 +#define BT832_AEC_CONFIG 48 +#define BT832_AEC_TL 49 +#define BT832_AEC_TC 50 +#define BT832_AEC_TH 51 +// Status: +#define BT832_VP_STATUS 52 +#define BT832_VP_LINECOUNT 53 +#define BT832_CAM_DEVICEL 54 // e.g. 0x19 +#define BT832_CAM_DEVICEH 55 // e.g. 0x40 == 0x194 Mask0, 0x194 = 404 decimal (VVL-404 camera) +#define BT832_CAM_STATUS 56 + #define BT832_56_CAMERA_PRESENT 0x20 +//Camera Setups: +#define BT832_CAM_SETUP0 57 +#define BT832_CAM_SETUP1 58 +#define BT832_CAM_SETUP2 59 +#define BT832_CAM_SETUP3 60 +// System: +#define BT832_DEFCOR 61 +#define BT832_VP_TESTCONTROL1 62 +#define BT832_DEVICE_ID 63 +# define BT832_DEVICE_ID__31 0x31 // Bt832 has ID 0x31 + +/* STMicroelectronivcs VV5404 camera module + i2c: 0x20: sensor address + i2c: 0xa0: eeprom for ccd defect map + */ +#define VV5404_device_h 0x00 // 0x19 +#define VV5404_device_l 0x01 // 0x40 +#define VV5404_status0 0x02 +#define VV5404_linecountc 0x03 // current line counter +#define VV5404_linecountl 0x04 +#define VV5404_setup0 0x10 +#define VV5404_setup1 0x11 +#define VV5404_setup2 0x12 +#define VV5404_setup4 0x14 +#define VV5404_setup5 0x15 +#define VV5404_fine_h 0x20 // fine exposure +#define VV5404_fine_l 0x21 +#define VV5404_coarse_h 0x22 //coarse exposure +#define VV5404_coarse_l 0x23 +#define VV5404_gain 0x24 // ADC pre-amp gain setting +#define VV5404_clk_div 0x25 +#define VV5404_cr 0x76 // control register +#define VV5404_as0 0x77 // ADC setup register + + +// IOCTL +#define BT832_HEXDUMP _IOR('b',1,int) +#define BT832_REATTACH _IOR('b',2,int) + +/* from BT8x8VXD/capdrv/dialogs.cpp */ + +/* +typedef enum { SVI, Logitech, Rockwell } CAMERA; + +static COMBOBOX_ENTRY gwCameraOptions[] = +{ + { SVI, "Silicon Vision 512N" }, + { Logitech, "Logitech VideoMan 1.3" }, + { Rockwell, "Rockwell QuartzSight PCI 1.0" } +}; + +// SRAM table values +//=========================================================================== +typedef enum { TGB_NTSC624, TGB_NTSC780, TGB_NTSC858, TGB_NTSC392 } TimeGenByte; + +BYTE SRAMTable[][ 60 ] = +{ + // TGB_NTSC624 + { + 0x33, // size of table = 51 + 0x0E, 0xC0, 0x00, 0x00, 0x90, 0x02, 0x03, 0x10, 0x03, 0x06, + 0x10, 0x04, 0x12, 0x12, 0x05, 0x02, 0x13, 0x04, 0x19, 0x00, + 0x04, 0x39, 0x00, 0x06, 0x59, 0x08, 0x03, 0x85, 0x08, 0x07, + 0x03, 0x50, 0x00, 0x91, 0x40, 0x00, 0x11, 0x01, 0x01, 0x4D, + 0x0D, 0x02, 0x03, 0x11, 0x01, 0x05, 0x37, 0x00, 0x37, 0x21, 0x00 + }, + // TGB_NTSC780 + { + 0x33, // size of table = 51 + 0x0e, 0xc0, 0x00, 0x00, 0x90, 0xe2, 0x03, 0x10, 0x03, 0x06, + 0x10, 0x34, 0x12, 0x12, 0x65, 0x02, 0x13, 0x24, 0x19, 0x00, + 0x24, 0x39, 0x00, 0x96, 0x59, 0x08, 0x93, 0x85, 0x08, 0x97, + 0x03, 0x50, 0x50, 0xaf, 0x40, 0x30, 0x5f, 0x01, 0xf1, 0x7f, + 0x0d, 0xf2, 0x03, 0x11, 0xf1, 0x05, 0x37, 0x30, 0x85, 0x21, 0x50 + }, + // TGB_NTSC858 + { + 0x33, // size of table = 51 + 0x0c, 0xc0, 0x00, 0x00, 0x90, 0xc2, 0x03, 0x10, 0x03, 0x06, + 0x10, 0x34, 0x12, 0x12, 0x65, 0x02, 0x13, 0x24, 0x19, 0x00, + 0x24, 0x39, 0x00, 0x96, 0x59, 0x08, 0x93, 0x83, 0x08, 0x97, + 0x03, 0x50, 0x30, 0xc0, 0x40, 0x30, 0x86, 0x01, 0x01, 0xa6, + 0x0d, 0x62, 0x03, 0x11, 0x61, 0x05, 0x37, 0x30, 0xac, 0x21, 0x50 + }, + // TGB_NTSC392 + // This table has been modified to be used for Fusion Rev D + { + 0x2A, // size of table = 42 + 0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24, + 0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10, + 0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00, + 0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3, + 0x20, 0x00 + } +}; + +//=========================================================================== +// This is the structure of the camera specifications +//=========================================================================== +typedef struct tag_cameraSpec +{ + SignalFormat signal; // which digital signal format the camera has + VideoFormat vidFormat; // video standard + SyncVideoRef syncRef; // which sync video reference is used + State syncOutput; // enable sync output for sync video input? + DecInputClk iClk; // which input clock is used + TimeGenByte tgb; // which timing generator byte does the camera use + int HReset; // select 64, 48, 32, or 16 CLKx1 for HReset + PLLFreq pllFreq; // what synthesized frequency to set PLL to + VSIZEPARMS vSize; // video size the camera produces + int lineCount; // expected total number of half-line per frame - 1 + BOOL interlace; // interlace signal? +} CameraSpec; + +//=========================================================================== +// <UPDATE REQUIRED> +// Camera specifications database. Update this table whenever camera spec +// has been changed or added/deleted supported camera models +//=========================================================================== +static CameraSpec dbCameraSpec[ N_CAMERAOPTIONS ] = +{ // Silicon Vision 512N + { Signal_CCIR656, VFormat_NTSC, VRef_alignedCb, Off, DecClk_GPCLK, TGB_NTSC624, 64, KHz19636, + // Clkx1_HACTIVE, Clkx1_HDELAY, VActive, VDelay, linesPerField; lineCount, Interlace + { 512, 0x64, 480, 0x13, 240 }, 0, TRUE + }, + // Logitech VideoMan 1.3 + { Signal_CCIR656, VFormat_NTSC, VRef_alignedCb, Off, DecClk_GPCLK, TGB_NTSC780, 64, KHz24545, + // Clkx1_HACTIVE, Clkx1_HDELAY, VActive, VDelay, linesPerField; lineCount, Interlace + { 640, 0x80, 480, 0x1A, 240 }, 0, TRUE + }, + // Rockwell QuartzSight + // Note: Fusion Rev D (rev ID 0x02) and later supports 16 pixels for HReset which is preferable. + // Use 32 for earlier version of hardware. Clkx1_HDELAY also changed from 0x27 to 0x20. + { Signal_CCIR656, VFormat_NTSC, VRef_alignedCb, Off, DecClk_GPCLK, TGB_NTSC392, 16, KHz28636, + // Clkx1_HACTIVE, Clkx1_HDELAY, VActive, VDelay, linesPerField; lineCount, Interlace + { 352, 0x20, 576, 0x08, 288 }, 607, FALSE + } +}; +*/ + +/* +The corresponding APIs required to be invoked are: +SetConnector( ConCamera, TRUE/FALSE ); +SetSignalFormat( spec.signal ); +SetVideoFormat( spec.vidFormat ); +SetSyncVideoRef( spec.syncRef ); +SetEnableSyncOutput( spec.syncOutput ); +SetTimGenByte( SRAMTable[ spec.tgb ], SRAMTableSize[ spec.tgb ] ); +SetHReset( spec.HReset ); +SetPLL( spec.pllFreq ); +SetDecInputClock( spec.iClk ); +SetVideoInfo( spec.vSize ); +SetTotalLineCount( spec.lineCount ); +SetInterlaceMode( spec.interlace ); +*/ + +/* from web: + Video Sampling +Digital video is a sampled form of analog video. The most common sampling schemes in use today are: + Pixel Clock Horiz Horiz Vert + Rate Total Active +NTSC square pixel 12.27 MHz 780 640 525 +NTSC CCIR-601 13.5 MHz 858 720 525 +NTSC 4FSc 14.32 MHz 910 768 525 +PAL square pixel 14.75 MHz 944 768 625 +PAL CCIR-601 13.5 MHz 864 720 625 +PAL 4FSc 17.72 MHz 1135 948 625 + +For the CCIR-601 standards, the sampling is based on a static orthogonal sampling grid. The luminance component (Y) is sampled at 13.5 MHz, while the two color difference signals, Cr and Cb are sampled at half that, or 6.75 MHz. The Cr and Cb samples are colocated with alternate Y samples, and they are taken at the same position on each line, such that one sample is coincident with the 50% point of the falling edge of analog sync. The samples are coded to either 8 or 10 bits per component. +*/ + +/* from DScaler:*/ +/* +//=========================================================================== +// CCIR656 Digital Input Support: The tables were taken from DScaler proyect +// +// 13 Dec 2000 - Michael Eskin, Conexant Systems - Initial version +// + +//=========================================================================== +// Timing generator SRAM table values for CCIR601 720x480 NTSC +//=========================================================================== +// For NTSC CCIR656 +BYTE BtCard::SRAMTable_NTSC[] = +{ + // SRAM Timing Table for NTSC + 0x0c, 0xc0, 0x00, + 0x00, 0x90, 0xc2, + 0x03, 0x10, 0x03, + 0x06, 0x10, 0x34, + 0x12, 0x12, 0x65, + 0x02, 0x13, 0x24, + 0x19, 0x00, 0x24, + 0x39, 0x00, 0x96, + 0x59, 0x08, 0x93, + 0x83, 0x08, 0x97, + 0x03, 0x50, 0x30, + 0xc0, 0x40, 0x30, + 0x86, 0x01, 0x01, + 0xa6, 0x0d, 0x62, + 0x03, 0x11, 0x61, + 0x05, 0x37, 0x30, + 0xac, 0x21, 0x50 +}; + +//=========================================================================== +// Timing generator SRAM table values for CCIR601 720x576 NTSC +//=========================================================================== +// For PAL CCIR656 +BYTE BtCard::SRAMTable_PAL[] = +{ + // SRAM Timing Table for PAL + 0x36, 0x11, 0x01, + 0x00, 0x90, 0x02, + 0x05, 0x10, 0x04, + 0x16, 0x14, 0x05, + 0x11, 0x00, 0x04, + 0x12, 0xc0, 0x00, + 0x31, 0x00, 0x06, + 0x51, 0x08, 0x03, + 0x89, 0x08, 0x07, + 0xc0, 0x44, 0x00, + 0x81, 0x01, 0x01, + 0xa9, 0x0d, 0x02, + 0x02, 0x50, 0x03, + 0x37, 0x3d, 0x00, + 0xaf, 0x21, 0x00, +}; +*/ diff --git a/drivers/media/video/bt848.h b/drivers/media/video/bt848.h new file mode 100644 index 000000000000..0bcd95303bb0 --- /dev/null +++ b/drivers/media/video/bt848.h @@ -0,0 +1,366 @@ +/* + bt848.h - Bt848 register offsets + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _BT848_H_ +#define _BT848_H_ + +#ifndef PCI_VENDOR_ID_BROOKTREE +#define PCI_VENDOR_ID_BROOKTREE 0x109e +#endif +#ifndef PCI_DEVICE_ID_BT848 +#define PCI_DEVICE_ID_BT848 0x350 +#endif +#ifndef PCI_DEVICE_ID_BT849 +#define PCI_DEVICE_ID_BT849 0x351 +#endif +#ifndef PCI_DEVICE_ID_BT878 +#define PCI_DEVICE_ID_BT878 0x36e +#endif +#ifndef PCI_DEVICE_ID_BT879 +#define PCI_DEVICE_ID_BT879 0x36f +#endif + + +/* Brooktree 848 registers */ + +#define BT848_DSTATUS 0x000 +#define BT848_DSTATUS_PRES (1<<7) +#define BT848_DSTATUS_HLOC (1<<6) +#define BT848_DSTATUS_FIELD (1<<5) +#define BT848_DSTATUS_NUML (1<<4) +#define BT848_DSTATUS_CSEL (1<<3) +#define BT848_DSTATUS_PLOCK (1<<2) +#define BT848_DSTATUS_LOF (1<<1) +#define BT848_DSTATUS_COF (1<<0) + +#define BT848_IFORM 0x004 +#define BT848_IFORM_HACTIVE (1<<7) +#define BT848_IFORM_MUXSEL (3<<5) +#define BT848_IFORM_MUX0 (2<<5) +#define BT848_IFORM_MUX1 (3<<5) +#define BT848_IFORM_MUX2 (1<<5) +#define BT848_IFORM_XTSEL (3<<3) +#define BT848_IFORM_XT0 (1<<3) +#define BT848_IFORM_XT1 (2<<3) +#define BT848_IFORM_XTAUTO (3<<3) +#define BT848_IFORM_XTBOTH (3<<3) +#define BT848_IFORM_NTSC 1 +#define BT848_IFORM_NTSC_J 2 +#define BT848_IFORM_PAL_BDGHI 3 +#define BT848_IFORM_PAL_M 4 +#define BT848_IFORM_PAL_N 5 +#define BT848_IFORM_SECAM 6 +#define BT848_IFORM_PAL_NC 7 +#define BT848_IFORM_AUTO 0 +#define BT848_IFORM_NORM 7 + +#define BT848_TDEC 0x008 +#define BT848_TDEC_DEC_FIELD (1<<7) +#define BT848_TDEC_FLDALIGN (1<<6) +#define BT848_TDEC_DEC_RAT (0x1f) + +#define BT848_E_CROP 0x00C +#define BT848_O_CROP 0x08C + +#define BT848_E_VDELAY_LO 0x010 +#define BT848_O_VDELAY_LO 0x090 + +#define BT848_E_VACTIVE_LO 0x014 +#define BT848_O_VACTIVE_LO 0x094 + +#define BT848_E_HDELAY_LO 0x018 +#define BT848_O_HDELAY_LO 0x098 + +#define BT848_E_HACTIVE_LO 0x01C +#define BT848_O_HACTIVE_LO 0x09C + +#define BT848_E_HSCALE_HI 0x020 +#define BT848_O_HSCALE_HI 0x0A0 + +#define BT848_E_HSCALE_LO 0x024 +#define BT848_O_HSCALE_LO 0x0A4 + +#define BT848_BRIGHT 0x028 + +#define BT848_E_CONTROL 0x02C +#define BT848_O_CONTROL 0x0AC +#define BT848_CONTROL_LNOTCH (1<<7) +#define BT848_CONTROL_COMP (1<<6) +#define BT848_CONTROL_LDEC (1<<5) +#define BT848_CONTROL_CBSENSE (1<<4) +#define BT848_CONTROL_CON_MSB (1<<2) +#define BT848_CONTROL_SAT_U_MSB (1<<1) +#define BT848_CONTROL_SAT_V_MSB (1<<0) + +#define BT848_CONTRAST_LO 0x030 +#define BT848_SAT_U_LO 0x034 +#define BT848_SAT_V_LO 0x038 +#define BT848_HUE 0x03C + +#define BT848_E_SCLOOP 0x040 +#define BT848_O_SCLOOP 0x0C0 +#define BT848_SCLOOP_CAGC (1<<6) +#define BT848_SCLOOP_CKILL (1<<5) +#define BT848_SCLOOP_HFILT_AUTO (0<<3) +#define BT848_SCLOOP_HFILT_CIF (1<<3) +#define BT848_SCLOOP_HFILT_QCIF (2<<3) +#define BT848_SCLOOP_HFILT_ICON (3<<3) + +#define BT848_SCLOOP_PEAK (1<<7) +#define BT848_SCLOOP_HFILT_MINP (1<<3) +#define BT848_SCLOOP_HFILT_MEDP (2<<3) +#define BT848_SCLOOP_HFILT_MAXP (3<<3) + + +#define BT848_OFORM 0x048 +#define BT848_OFORM_RANGE (1<<7) +#define BT848_OFORM_CORE0 (0<<5) +#define BT848_OFORM_CORE8 (1<<5) +#define BT848_OFORM_CORE16 (2<<5) +#define BT848_OFORM_CORE32 (3<<5) + +#define BT848_E_VSCALE_HI 0x04C +#define BT848_O_VSCALE_HI 0x0CC +#define BT848_VSCALE_YCOMB (1<<7) +#define BT848_VSCALE_COMB (1<<6) +#define BT848_VSCALE_INT (1<<5) +#define BT848_VSCALE_HI 15 + +#define BT848_E_VSCALE_LO 0x050 +#define BT848_O_VSCALE_LO 0x0D0 +#define BT848_TEST 0x054 +#define BT848_ADELAY 0x060 +#define BT848_BDELAY 0x064 + +#define BT848_ADC 0x068 +#define BT848_ADC_RESERVED (2<<6) +#define BT848_ADC_SYNC_T (1<<5) +#define BT848_ADC_AGC_EN (1<<4) +#define BT848_ADC_CLK_SLEEP (1<<3) +#define BT848_ADC_Y_SLEEP (1<<2) +#define BT848_ADC_C_SLEEP (1<<1) +#define BT848_ADC_CRUSH (1<<0) + +#define BT848_WC_UP 0x044 +#define BT848_WC_DOWN 0x078 + +#define BT848_E_VTC 0x06C +#define BT848_O_VTC 0x0EC +#define BT848_VTC_HSFMT (1<<7) +#define BT848_VTC_VFILT_2TAP 0 +#define BT848_VTC_VFILT_3TAP 1 +#define BT848_VTC_VFILT_4TAP 2 +#define BT848_VTC_VFILT_5TAP 3 + +#define BT848_SRESET 0x07C + +#define BT848_COLOR_FMT 0x0D4 +#define BT848_COLOR_FMT_O_RGB32 (0<<4) +#define BT848_COLOR_FMT_O_RGB24 (1<<4) +#define BT848_COLOR_FMT_O_RGB16 (2<<4) +#define BT848_COLOR_FMT_O_RGB15 (3<<4) +#define BT848_COLOR_FMT_O_YUY2 (4<<4) +#define BT848_COLOR_FMT_O_BtYUV (5<<4) +#define BT848_COLOR_FMT_O_Y8 (6<<4) +#define BT848_COLOR_FMT_O_RGB8 (7<<4) +#define BT848_COLOR_FMT_O_YCrCb422 (8<<4) +#define BT848_COLOR_FMT_O_YCrCb411 (9<<4) +#define BT848_COLOR_FMT_O_RAW (14<<4) +#define BT848_COLOR_FMT_E_RGB32 0 +#define BT848_COLOR_FMT_E_RGB24 1 +#define BT848_COLOR_FMT_E_RGB16 2 +#define BT848_COLOR_FMT_E_RGB15 3 +#define BT848_COLOR_FMT_E_YUY2 4 +#define BT848_COLOR_FMT_E_BtYUV 5 +#define BT848_COLOR_FMT_E_Y8 6 +#define BT848_COLOR_FMT_E_RGB8 7 +#define BT848_COLOR_FMT_E_YCrCb422 8 +#define BT848_COLOR_FMT_E_YCrCb411 9 +#define BT848_COLOR_FMT_E_RAW 14 + +#define BT848_COLOR_FMT_RGB32 0x00 +#define BT848_COLOR_FMT_RGB24 0x11 +#define BT848_COLOR_FMT_RGB16 0x22 +#define BT848_COLOR_FMT_RGB15 0x33 +#define BT848_COLOR_FMT_YUY2 0x44 +#define BT848_COLOR_FMT_BtYUV 0x55 +#define BT848_COLOR_FMT_Y8 0x66 +#define BT848_COLOR_FMT_RGB8 0x77 +#define BT848_COLOR_FMT_YCrCb422 0x88 +#define BT848_COLOR_FMT_YCrCb411 0x99 +#define BT848_COLOR_FMT_RAW 0xee + +#define BT848_VTOTAL_LO 0xB0 +#define BT848_VTOTAL_HI 0xB4 + +#define BT848_COLOR_CTL 0x0D8 +#define BT848_COLOR_CTL_EXT_FRMRATE (1<<7) +#define BT848_COLOR_CTL_COLOR_BARS (1<<6) +#define BT848_COLOR_CTL_RGB_DED (1<<5) +#define BT848_COLOR_CTL_GAMMA (1<<4) +#define BT848_COLOR_CTL_WSWAP_ODD (1<<3) +#define BT848_COLOR_CTL_WSWAP_EVEN (1<<2) +#define BT848_COLOR_CTL_BSWAP_ODD (1<<1) +#define BT848_COLOR_CTL_BSWAP_EVEN (1<<0) + +#define BT848_CAP_CTL 0x0DC +#define BT848_CAP_CTL_DITH_FRAME (1<<4) +#define BT848_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define BT848_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define BT848_CAP_CTL_CAPTURE_ODD (1<<1) +#define BT848_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define BT848_VBI_PACK_SIZE 0x0E0 + +#define BT848_VBI_PACK_DEL 0x0E4 +#define BT848_VBI_PACK_DEL_VBI_HDELAY 0xfc +#define BT848_VBI_PACK_DEL_EXT_FRAME 2 +#define BT848_VBI_PACK_DEL_VBI_PKT_HI 1 + + +#define BT848_INT_STAT 0x100 +#define BT848_INT_MASK 0x104 + +#define BT848_INT_ETBF (1<<23) + +#define BT848_INT_RISCS (0xf<<28) +#define BT848_INT_RISC_EN (1<<27) +#define BT848_INT_RACK (1<<25) +#define BT848_INT_FIELD (1<<24) +#define BT848_INT_SCERR (1<<19) +#define BT848_INT_OCERR (1<<18) +#define BT848_INT_PABORT (1<<17) +#define BT848_INT_RIPERR (1<<16) +#define BT848_INT_PPERR (1<<15) +#define BT848_INT_FDSR (1<<14) +#define BT848_INT_FTRGT (1<<13) +#define BT848_INT_FBUS (1<<12) +#define BT848_INT_RISCI (1<<11) +#define BT848_INT_GPINT (1<<9) +#define BT848_INT_I2CDONE (1<<8) +#define BT848_INT_VPRES (1<<5) +#define BT848_INT_HLOCK (1<<4) +#define BT848_INT_OFLOW (1<<3) +#define BT848_INT_HSYNC (1<<2) +#define BT848_INT_VSYNC (1<<1) +#define BT848_INT_FMTCHG (1<<0) + + +#define BT848_GPIO_DMA_CTL 0x10C +#define BT848_GPIO_DMA_CTL_GPINTC (1<<15) +#define BT848_GPIO_DMA_CTL_GPINTI (1<<14) +#define BT848_GPIO_DMA_CTL_GPWEC (1<<13) +#define BT848_GPIO_DMA_CTL_GPIOMODE (3<<11) +#define BT848_GPIO_DMA_CTL_GPCLKMODE (1<<10) +#define BT848_GPIO_DMA_CTL_PLTP23_4 (0<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_8 (1<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_16 (2<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_32 (3<<6) +#define BT848_GPIO_DMA_CTL_PLTP1_4 (0<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_8 (1<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_16 (2<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_32 (3<<4) +#define BT848_GPIO_DMA_CTL_PKTP_4 (0<<2) +#define BT848_GPIO_DMA_CTL_PKTP_8 (1<<2) +#define BT848_GPIO_DMA_CTL_PKTP_16 (2<<2) +#define BT848_GPIO_DMA_CTL_PKTP_32 (3<<2) +#define BT848_GPIO_DMA_CTL_RISC_ENABLE (1<<1) +#define BT848_GPIO_DMA_CTL_FIFO_ENABLE (1<<0) + +#define BT848_I2C 0x110 +#define BT878_I2C_MODE (1<<7) +#define BT878_I2C_RATE (1<<6) +#define BT878_I2C_NOSTOP (1<<5) +#define BT878_I2C_NOSTART (1<<4) +#define BT848_I2C_DIV (0xf<<4) +#define BT848_I2C_SYNC (1<<3) +#define BT848_I2C_W3B (1<<2) +#define BT848_I2C_SCL (1<<1) +#define BT848_I2C_SDA (1<<0) + +#define BT848_RISC_STRT_ADD 0x114 +#define BT848_GPIO_OUT_EN 0x118 +#define BT848_GPIO_REG_INP 0x11C +#define BT848_RISC_COUNT 0x120 +#define BT848_GPIO_DATA 0x200 + + +/* Bt848 RISC commands */ + +/* only for the SYNC RISC command */ +#define BT848_FIFO_STATUS_FM1 0x06 +#define BT848_FIFO_STATUS_FM3 0x0e +#define BT848_FIFO_STATUS_SOL 0x02 +#define BT848_FIFO_STATUS_EOL4 0x01 +#define BT848_FIFO_STATUS_EOL3 0x0d +#define BT848_FIFO_STATUS_EOL2 0x09 +#define BT848_FIFO_STATUS_EOL1 0x05 +#define BT848_FIFO_STATUS_VRE 0x04 +#define BT848_FIFO_STATUS_VRO 0x0c +#define BT848_FIFO_STATUS_PXV 0x00 + +#define BT848_RISC_RESYNC (1<<15) + +/* WRITE and SKIP */ +/* disable which bytes of each DWORD */ +#define BT848_RISC_BYTE0 (1U<<12) +#define BT848_RISC_BYTE1 (1U<<13) +#define BT848_RISC_BYTE2 (1U<<14) +#define BT848_RISC_BYTE3 (1U<<15) +#define BT848_RISC_BYTE_ALL (0x0fU<<12) +#define BT848_RISC_BYTE_NONE 0 +/* cause RISCI */ +#define BT848_RISC_IRQ (1U<<24) +/* RISC command is last one in this line */ +#define BT848_RISC_EOL (1U<<26) +/* RISC command is first one in this line */ +#define BT848_RISC_SOL (1U<<27) + +#define BT848_RISC_WRITE (0x01U<<28) +#define BT848_RISC_SKIP (0x02U<<28) +#define BT848_RISC_WRITEC (0x05U<<28) +#define BT848_RISC_JUMP (0x07U<<28) +#define BT848_RISC_SYNC (0x08U<<28) + +#define BT848_RISC_WRITE123 (0x09U<<28) +#define BT848_RISC_SKIP123 (0x0aU<<28) +#define BT848_RISC_WRITE1S23 (0x0bU<<28) + + +/* Bt848A and higher only !! */ +#define BT848_TGLB 0x080 +#define BT848_TGCTRL 0x084 +#define BT848_FCAP 0x0E8 +#define BT848_PLL_F_LO 0x0F0 +#define BT848_PLL_F_HI 0x0F4 + +#define BT848_PLL_XCI 0x0F8 +#define BT848_PLL_X (1<<7) +#define BT848_PLL_C (1<<6) + +#define BT848_DVSIF 0x0FC + +/* Bt878 register */ + +#define BT878_DEVCTRL 0x40 +#define BT878_EN_TBFX 0x02 +#define BT878_EN_VSFX 0x04 + +#endif diff --git a/drivers/media/video/bt856.c b/drivers/media/video/bt856.c new file mode 100644 index 000000000000..72c7eb0f8c24 --- /dev/null +++ b/drivers/media/video/bt856.c @@ -0,0 +1,442 @@ +/* + * bt856 - BT856A Digital Video Encoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson <mike@mlb.org> + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Brooktree-856A video encoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +#define REG_OFFSET 0xCE + +struct bt856 { + unsigned char reg[32]; + + int norm; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_BT856 0x88 + +/* ----------------------------------------------------------------------- */ + +static inline int +bt856_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + + encoder->reg[reg - REG_OFFSET] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +bt856_setbit (struct i2c_client *client, + u8 reg, + u8 bit, + u8 value) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + + return bt856_write(client, reg, + (encoder-> + reg[reg - REG_OFFSET] & ~(1 << bit)) | + (value ? (1 << bit) : 0)); +} + +static void +bt856_dump (struct i2c_client *client) +{ + int i; + struct bt856 *encoder = i2c_get_clientdata(client); + + printk(KERN_INFO "%s: register dump:", I2C_NAME(client)); + for (i = 0xd6; i <= 0xde; i += 2) + printk(" %02x", encoder->reg[i - REG_OFFSET]); + printk("\n"); +} + +/* ----------------------------------------------------------------------- */ + +static int +bt856_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + /* This is just for testing!!! */ + dprintk(1, KERN_INFO "bt856: init\n"); + bt856_write(client, 0xdc, 0x18); + bt856_write(client, 0xda, 0); + bt856_write(client, 0xde, 0); + + bt856_setbit(client, 0xdc, 3, 1); + //bt856_setbit(client, 0xdc, 6, 0); + bt856_setbit(client, 0xdc, 4, 1); + + switch (encoder->norm) { + + case VIDEO_MODE_NTSC: + bt856_setbit(client, 0xdc, 2, 0); + break; + + case VIDEO_MODE_PAL: + bt856_setbit(client, 0xdc, 2, 1); + break; + } + + bt856_setbit(client, 0xdc, 1, 1); + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + if (debug != 0) + bt856_dump(client); + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + dprintk(1, KERN_INFO "%s: get capabilities\n", + I2C_NAME(client)); + + cap->flags = VIDEO_ENCODER_PAL | + VIDEO_ENCODER_NTSC | + VIDEO_ENCODER_CCIR; + cap->inputs = 2; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set norm %d\n", I2C_NAME(client), + *iarg); + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + bt856_setbit(client, 0xdc, 2, 0); + break; + + case VIDEO_MODE_PAL: + bt856_setbit(client, 0xdc, 2, 1); + bt856_setbit(client, 0xda, 0, 0); + //bt856_setbit(client, 0xda, 0, 1); + break; + + default: + return -EINVAL; + + } + encoder->norm = *iarg; + if (debug != 0) + bt856_dump(client); + } + break; + + case ENCODER_SET_INPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set input %d\n", I2C_NAME(client), + *iarg); + + /* We only have video bus. + * iarg = 0: input is from bt819 + * iarg = 1: input is from ZR36060 */ + + switch (*iarg) { + + case 0: + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + bt856_setbit(client, 0xdc, 3, 1); + bt856_setbit(client, 0xdc, 6, 0); + break; + case 1: + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + bt856_setbit(client, 0xdc, 3, 1); + bt856_setbit(client, 0xdc, 6, 1); + break; + case 2: // Color bar + bt856_setbit(client, 0xdc, 3, 0); + bt856_setbit(client, 0xde, 4, 1); + break; + default: + return -EINVAL; + + } + + if (debug != 0) + bt856_dump(client); + } + break; + + case ENCODER_SET_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set output %d\n", I2C_NAME(client), + *iarg); + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case ENCODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + encoder->enable = !!*iarg; + + dprintk(1, KERN_INFO "%s: enable output %d\n", + I2C_NAME(client), encoder->enable); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { I2C_BT856 >> 1, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_bt856; + +static int +bt856_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct bt856 *encoder; + + dprintk(1, + KERN_INFO + "bt856.c: detecting bt856 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_bt856; + client->flags = I2C_CLIENT_ALLOW_USE; + strlcpy(I2C_NAME(client), "bt856", sizeof(I2C_NAME(client))); + + encoder = kmalloc(sizeof(struct bt856), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(encoder, 0, sizeof(struct bt856)); + encoder->norm = VIDEO_MODE_NTSC; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + bt856_write(client, 0xdc, 0x18); + bt856_write(client, 0xda, 0); + bt856_write(client, 0xde, 0); + + bt856_setbit(client, 0xdc, 3, 1); + //bt856_setbit(client, 0xdc, 6, 0); + bt856_setbit(client, 0xdc, 4, 1); + + switch (encoder->norm) { + + case VIDEO_MODE_NTSC: + bt856_setbit(client, 0xdc, 2, 0); + break; + + case VIDEO_MODE_PAL: + bt856_setbit(client, 0xdc, 2, 1); + break; + } + + bt856_setbit(client, 0xdc, 1, 1); + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + + if (debug != 0) + bt856_dump(client); + + dprintk(1, KERN_INFO "%s_attach: at address 0x%x\n", I2C_NAME(client), + client->addr << 1); + + return 0; +} + +static int +bt856_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "bt856.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &bt856_detect_client); +} + +static int +bt856_detach_client (struct i2c_client *client) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_bt856 = { + .owner = THIS_MODULE, + .name = "bt856", + + .id = I2C_DRIVERID_BT856, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = bt856_attach_adapter, + .detach_client = bt856_detach_client, + .command = bt856_command, +}; + +static int __init +bt856_init (void) +{ + return i2c_add_driver(&i2c_driver_bt856); +} + +static void __exit +bt856_exit (void) +{ + i2c_del_driver(&i2c_driver_bt856); +} + +module_init(bt856_init); +module_exit(bt856_exit); diff --git a/drivers/media/video/btcx-risc.c b/drivers/media/video/btcx-risc.c new file mode 100644 index 000000000000..7f2d515d2873 --- /dev/null +++ b/drivers/media/video/btcx-risc.c @@ -0,0 +1,262 @@ +/* + $Id: btcx-risc.c,v 1.6 2005/02/21 13:57:59 kraxel Exp $ + + btcx-risc.c + + bt848/bt878/cx2388x risc code generator. + + (c) 2000-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/videodev2.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include "btcx-risc.h" + +MODULE_DESCRIPTION("some code shared by bttv and cx88xx drivers"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"debug messages, default is 0 (no)"); + +/* ---------------------------------------------------------- */ +/* allocate/free risc memory */ + +static int memcnt; + +void btcx_riscmem_free(struct pci_dev *pci, + struct btcx_riscmem *risc) +{ + if (NULL == risc->cpu) + return; + if (debug) { + memcnt--; + printk("btcx: riscmem free [%d] dma=%lx\n", + memcnt, (unsigned long)risc->dma); + } + pci_free_consistent(pci, risc->size, risc->cpu, risc->dma); + memset(risc,0,sizeof(*risc)); +} + +int btcx_riscmem_alloc(struct pci_dev *pci, + struct btcx_riscmem *risc, + unsigned int size) +{ + u32 *cpu; + dma_addr_t dma; + + if (NULL != risc->cpu && risc->size < size) + btcx_riscmem_free(pci,risc); + if (NULL == risc->cpu) { + cpu = pci_alloc_consistent(pci, size, &dma); + if (NULL == cpu) + return -ENOMEM; + risc->cpu = cpu; + risc->dma = dma; + risc->size = size; + if (debug) { + memcnt++; + printk("btcx: riscmem alloc [%d] dma=%lx cpu=%p size=%d\n", + memcnt, (unsigned long)dma, cpu, size); + } + } + memset(risc->cpu,0,risc->size); + return 0; +} + +/* ---------------------------------------------------------- */ +/* screen overlay helpers */ + +int +btcx_screen_clips(int swidth, int sheight, struct v4l2_rect *win, + struct v4l2_clip *clips, unsigned int n) +{ + if (win->left < 0) { + /* left */ + clips[n].c.left = 0; + clips[n].c.top = 0; + clips[n].c.width = -win->left; + clips[n].c.height = win->height; + n++; + } + if (win->left + win->width > swidth) { + /* right */ + clips[n].c.left = swidth - win->left; + clips[n].c.top = 0; + clips[n].c.width = win->width - clips[n].c.left; + clips[n].c.height = win->height; + n++; + } + if (win->top < 0) { + /* top */ + clips[n].c.left = 0; + clips[n].c.top = 0; + clips[n].c.width = win->width; + clips[n].c.height = -win->top; + n++; + } + if (win->top + win->height > sheight) { + /* bottom */ + clips[n].c.left = 0; + clips[n].c.top = sheight - win->top; + clips[n].c.width = win->width; + clips[n].c.height = win->height - clips[n].c.top; + n++; + } + return n; +} + +int +btcx_align(struct v4l2_rect *win, struct v4l2_clip *clips, unsigned int n, int mask) +{ + s32 nx,nw,dx; + unsigned int i; + + /* fixup window */ + nx = (win->left + mask) & ~mask; + nw = (win->width) & ~mask; + if (nx + nw > win->left + win->width) + nw -= mask+1; + dx = nx - win->left; + win->left = nx; + win->width = nw; + if (debug) + printk(KERN_DEBUG "btcx: window align %dx%d+%d+%d [dx=%d]\n", + win->width, win->height, win->left, win->top, dx); + + /* fixup clips */ + for (i = 0; i < n; i++) { + nx = (clips[i].c.left-dx) & ~mask; + nw = (clips[i].c.width) & ~mask; + if (nx + nw < clips[i].c.left-dx + clips[i].c.width) + nw += mask+1; + clips[i].c.left = nx; + clips[i].c.width = nw; + if (debug) + printk(KERN_DEBUG "btcx: clip align %dx%d+%d+%d\n", + clips[i].c.width, clips[i].c.height, + clips[i].c.left, clips[i].c.top); + } + return 0; +} + +void +btcx_sort_clips(struct v4l2_clip *clips, unsigned int nclips) +{ + struct v4l2_clip swap; + int i,j,n; + + if (nclips < 2) + return; + for (i = nclips-2; i >= 0; i--) { + for (n = 0, j = 0; j <= i; j++) { + if (clips[j].c.left > clips[j+1].c.left) { + swap = clips[j]; + clips[j] = clips[j+1]; + clips[j+1] = swap; + n++; + } + } + if (0 == n) + break; + } +} + +void +btcx_calc_skips(int line, int width, unsigned int *maxy, + struct btcx_skiplist *skips, unsigned int *nskips, + const struct v4l2_clip *clips, unsigned int nclips) +{ + unsigned int clip,skip; + int end,maxline; + + skip=0; + maxline = 9999; + for (clip = 0; clip < nclips; clip++) { + + /* sanity checks */ + if (clips[clip].c.left + clips[clip].c.width <= 0) + continue; + if (clips[clip].c.left > (signed)width) + break; + + /* vertical range */ + if (line > clips[clip].c.top+clips[clip].c.height-1) + continue; + if (line < clips[clip].c.top) { + if (maxline > clips[clip].c.top-1) + maxline = clips[clip].c.top-1; + continue; + } + if (maxline > clips[clip].c.top+clips[clip].c.height-1) + maxline = clips[clip].c.top+clips[clip].c.height-1; + + /* horizontal range */ + if (0 == skip || clips[clip].c.left > skips[skip-1].end) { + /* new one */ + skips[skip].start = clips[clip].c.left; + if (skips[skip].start < 0) + skips[skip].start = 0; + skips[skip].end = clips[clip].c.left + clips[clip].c.width; + if (skips[skip].end > width) + skips[skip].end = width; + skip++; + } else { + /* overlaps -- expand last one */ + end = clips[clip].c.left + clips[clip].c.width; + if (skips[skip-1].end < end) + skips[skip-1].end = end; + if (skips[skip-1].end > width) + skips[skip-1].end = width; + } + } + *nskips = skip; + *maxy = maxline; + + if (debug) { + printk(KERN_DEBUG "btcx: skips line %d-%d:",line,maxline); + for (skip = 0; skip < *nskips; skip++) { + printk(" %d-%d",skips[skip].start,skips[skip].end); + } + printk("\n"); + } +} + +/* ---------------------------------------------------------- */ + +EXPORT_SYMBOL(btcx_riscmem_alloc); +EXPORT_SYMBOL(btcx_riscmem_free); + +EXPORT_SYMBOL(btcx_screen_clips); +EXPORT_SYMBOL(btcx_align); +EXPORT_SYMBOL(btcx_sort_clips); +EXPORT_SYMBOL(btcx_calc_skips); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/btcx-risc.h b/drivers/media/video/btcx-risc.h new file mode 100644 index 000000000000..41f60395a520 --- /dev/null +++ b/drivers/media/video/btcx-risc.h @@ -0,0 +1,35 @@ +/* + * $Id: btcx-risc.h,v 1.2 2004/09/15 16:15:24 kraxel Exp $ + */ +struct btcx_riscmem { + unsigned int size; + u32 *cpu; + u32 *jmp; + dma_addr_t dma; +}; + +struct btcx_skiplist { + int start; + int end; +}; + +int btcx_riscmem_alloc(struct pci_dev *pci, + struct btcx_riscmem *risc, + unsigned int size); +void btcx_riscmem_free(struct pci_dev *pci, + struct btcx_riscmem *risc); + +int btcx_screen_clips(int swidth, int sheight, struct v4l2_rect *win, + struct v4l2_clip *clips, unsigned int n); +int btcx_align(struct v4l2_rect *win, struct v4l2_clip *clips, + unsigned int n, int mask); +void btcx_sort_clips(struct v4l2_clip *clips, unsigned int nclips); +void btcx_calc_skips(int line, int width, unsigned int *maxy, + struct btcx_skiplist *skips, unsigned int *nskips, + const struct v4l2_clip *clips, unsigned int nclips); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-cards.c b/drivers/media/video/bttv-cards.c new file mode 100644 index 000000000000..abce874e71cc --- /dev/null +++ b/drivers/media/video/bttv-cards.c @@ -0,0 +1,4350 @@ +/* + $Id: bttv-cards.c,v 1.47 2005/02/22 14:06:32 kraxel Exp $ + + bttv-cards.c + + this file has configuration informations - card-specific stuff + like the big tvcards array for the most part + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2001 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kmod.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/firmware.h> + +#include <asm/io.h> + +#include "bttvp.h" +#if 0 /* not working yet */ +#include "bt832.h" +#endif + +/* fwd decl */ +static void boot_msp34xx(struct bttv *btv, int pin); +static void boot_bt832(struct bttv *btv); +static void hauppauge_eeprom(struct bttv *btv); +static void avermedia_eeprom(struct bttv *btv); +static void osprey_eeprom(struct bttv *btv); +static void modtec_eeprom(struct bttv *btv); +static void init_PXC200(struct bttv *btv); + +static void winview_audio(struct bttv *btv, struct video_audio *v, int set); +static void lt9415_audio(struct bttv *btv, struct video_audio *v, int set); +static void avermedia_tvphone_audio(struct bttv *btv, struct video_audio *v, + int set); +static void avermedia_tv_stereo_audio(struct bttv *btv, struct video_audio *v, + int set); +static void terratv_audio(struct bttv *btv, struct video_audio *v, int set); +static void gvbctv3pci_audio(struct bttv *btv, struct video_audio *v, int set); +static void gvbctv5pci_audio(struct bttv *btv, struct video_audio *v, int set); +static void winfast2000_audio(struct bttv *btv, struct video_audio *v, int set); +static void pvbt878p9b_audio(struct bttv *btv, struct video_audio *v, int set); +static void fv2000s_audio(struct bttv *btv, struct video_audio *v, int set); +static void windvr_audio(struct bttv *btv, struct video_audio *v, int set); +static void adtvk503_audio(struct bttv *btv, struct video_audio *v, int set); +static void rv605_muxsel(struct bttv *btv, unsigned int input); +static void eagle_muxsel(struct bttv *btv, unsigned int input); +static void xguard_muxsel(struct bttv *btv, unsigned int input); +static void ivc120_muxsel(struct bttv *btv, unsigned int input); +static void gvc1100_muxsel(struct bttv *btv, unsigned int input); + +static void PXC200_muxsel(struct bttv *btv, unsigned int input); + +static void picolo_tetra_muxsel(struct bttv *btv, unsigned int input); +static void picolo_tetra_init(struct bttv *btv); + +static void tibetCS16_muxsel(struct bttv *btv, unsigned int input); +static void tibetCS16_init(struct bttv *btv); + +static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input); +static void kodicom4400r_init(struct bttv *btv); + +static void sigmaSLC_muxsel(struct bttv *btv, unsigned int input); +static void sigmaSQ_muxsel(struct bttv *btv, unsigned int input); + +static int terratec_active_radio_upgrade(struct bttv *btv); +static int tea5757_read(struct bttv *btv); +static int tea5757_write(struct bttv *btv, int value); +static void identify_by_eeprom(struct bttv *btv, + unsigned char eeprom_data[256]); +static int __devinit pvr_boot(struct bttv *btv); + +/* config variables */ +static unsigned int triton1=0; +static unsigned int vsfx=0; +static unsigned int latency = UNSET; +static unsigned int no_overlay=-1; + +static unsigned int card[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int pll[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int tuner[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int svhs[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static unsigned int remote[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = UNSET }; +static struct bttv *master[BTTV_MAX] = { [ 0 ... (BTTV_MAX-1) ] = NULL }; +#ifdef MODULE +static unsigned int autoload = 1; +#else +static unsigned int autoload = 0; +#endif +static unsigned int gpiomask = UNSET; +static unsigned int audioall = UNSET; +static unsigned int audiomux[5] = { [ 0 ... 4 ] = UNSET }; + +/* insmod options */ +module_param(triton1, int, 0444); +module_param(vsfx, int, 0444); +module_param(no_overlay, int, 0444); +module_param(latency, int, 0444); +module_param(gpiomask, int, 0444); +module_param(audioall, int, 0444); +module_param(autoload, int, 0444); + +module_param_array(card, int, NULL, 0444); +module_param_array(pll, int, NULL, 0444); +module_param_array(tuner, int, NULL, 0444); +module_param_array(svhs, int, NULL, 0444); +module_param_array(remote, int, NULL, 0444); +module_param_array(audiomux, int, NULL, 0444); + +MODULE_PARM_DESC(triton1,"set ETBF pci config bit " + "[enable bug compatibility for triton1 + others]"); +MODULE_PARM_DESC(vsfx,"set VSFX pci config bit " + "[yet another chipset flaw workaround]"); +MODULE_PARM_DESC(latency,"pci latency timer"); +MODULE_PARM_DESC(card,"specify TV/grabber card model, see CARDLIST file for a list"); +MODULE_PARM_DESC(pll,"specify installed crystal (0=none, 28=28 MHz, 35=35 MHz)"); +MODULE_PARM_DESC(tuner,"specify installed tuner type"); +MODULE_PARM_DESC(autoload,"automatically load i2c modules like tuner.o, default is 1 (yes)"); + +/* ----------------------------------------------------------------------- */ +/* list of card IDs for bt878+ cards */ + +static struct CARD { + unsigned id; + int cardnr; + char *name; +} cards[] __devinitdata = { + { 0x13eb0070, BTTV_HAUPPAUGE878, "Hauppauge WinTV" }, + { 0x39000070, BTTV_HAUPPAUGE878, "Hauppauge WinTV-D" }, + { 0x45000070, BTTV_HAUPPAUGEPVR, "Hauppauge WinTV/PVR" }, + { 0xff000070, BTTV_OSPREY1x0, "Osprey-100" }, + { 0xff010070, BTTV_OSPREY2x0_SVID,"Osprey-200" }, + { 0xff020070, BTTV_OSPREY500, "Osprey-500" }, + { 0xff030070, BTTV_OSPREY2000, "Osprey-2000" }, + { 0xff040070, BTTV_OSPREY540, "Osprey-540" }, + + { 0x00011002, BTTV_ATI_TVWONDER, "ATI TV Wonder" }, + { 0x00031002, BTTV_ATI_TVWONDERVE,"ATI TV Wonder/VE" }, + + { 0x6606107d, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x6607107d, BTTV_WINFASTVC100, "Leadtek WinFast VC 100" }, + { 0x6609107d, BTTV_WINFAST2000, "Leadtek TV 2000 XP" }, + { 0x263610b4, BTTV_STB2, "STB TV PCI FM, Gateway P/N 6000704" }, + { 0x264510b4, BTTV_STB2, "STB TV PCI FM, Gateway P/N 6000704" }, + { 0x402010fc, BTTV_GVBCTV3PCI, "I-O Data Co. GV-BCTV3/PCI" }, + { 0x405010fc, BTTV_GVBCTV4PCI, "I-O Data Co. GV-BCTV4/PCI" }, + { 0x407010fc, BTTV_GVBCTV5PCI, "I-O Data Co. GV-BCTV5/PCI" }, + { 0xd01810fc, BTTV_GVBCTV5PCI, "I-O Data Co. GV-BCTV5/PCI" }, + + { 0x001211bd, BTTV_PINNACLE, "Pinnacle PCTV" }, + // some cards ship with byteswapped IDs ... + { 0x1200bd11, BTTV_PINNACLE, "Pinnacle PCTV [bswap]" }, + { 0xff00bd11, BTTV_PINNACLE, "Pinnacle PCTV [bswap]" }, + // this seems to happen as well ... + { 0xff1211bd, BTTV_PINNACLE, "Pinnacle PCTV" }, + + { 0x3000121a, BTTV_VOODOOTV_FM, "3Dfx VoodooTV FM/ VoodooTV 200" }, + { 0x263710b4, BTTV_VOODOOTV_FM, "3Dfx VoodooTV FM/ VoodooTV 200" }, + { 0x3060121a, BTTV_STB2, "3Dfx VoodooTV 100/ STB OEM" }, + + { 0x3000144f, BTTV_MAGICTVIEW063, "(Askey Magic/others) TView99 CPH06x" }, + { 0xa005144f, BTTV_MAGICTVIEW063, "CPH06X TView99-Card" }, + { 0x3002144f, BTTV_MAGICTVIEW061, "(Askey Magic/others) TView99 CPH05x" }, + { 0x3005144f, BTTV_MAGICTVIEW061, "(Askey Magic/others) TView99 CPH061/06L (T1/LC)" }, + { 0x5000144f, BTTV_MAGICTVIEW061, "Askey CPH050" }, + { 0x300014ff, BTTV_MAGICTVIEW061, "TView 99 (CPH061)" }, + { 0x300214ff, BTTV_PHOEBE_TVMAS, "Phoebe TV Master (CPH060)" }, + + { 0x00011461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00021461, BTTV_AVERMEDIA98, "AVermedia TVCapture 98" }, + { 0x00031461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00041461, BTTV_AVERMEDIA98, "AVerMedia TVCapture 98" }, + { 0x03001461, BTTV_AVERMEDIA98, "VDOMATE TV TUNER CARD" }, + + { 0x1117153b, BTTV_TERRATVALUE, "Terratec TValue (Philips PAL B/G)" }, + { 0x1118153b, BTTV_TERRATVALUE, "Terratec TValue (Temic PAL B/G)" }, + { 0x1119153b, BTTV_TERRATVALUE, "Terratec TValue (Philips PAL I)" }, + { 0x111a153b, BTTV_TERRATVALUE, "Terratec TValue (Temic PAL I)" }, + + { 0x1123153b, BTTV_TERRATVRADIO, "Terratec TV Radio+" }, + { 0x1127153b, BTTV_TERRATV, "Terratec TV+ (V1.05)" }, + // clashes with FlyVideo + //{ 0x18521852, BTTV_TERRATV, "Terratec TV+ (V1.10)" }, + { 0x1134153b, BTTV_TERRATVALUE, "Terratec TValue (LR102)" }, + { 0x1135153b, BTTV_TERRATVALUER, "Terratec TValue Radio" }, // LR102 + { 0x5018153b, BTTV_TERRATVALUE, "Terratec TValue" }, // ?? + { 0xff3b153b, BTTV_TERRATVALUER, "Terratec TValue Radio" }, // ?? + + { 0x400015b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x400a15b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x400d15b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + { 0x401015b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + { 0x401615b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV / Radio" }, + + { 0x1430aa00, BTTV_PV143, "Provideo PV143A" }, + { 0x1431aa00, BTTV_PV143, "Provideo PV143B" }, + { 0x1432aa00, BTTV_PV143, "Provideo PV143C" }, + { 0x1433aa00, BTTV_PV143, "Provideo PV143D" }, + { 0x1433aa03, BTTV_PV143, "Security Eyes" }, + + { 0x1460aa00, BTTV_PV150, "Provideo PV150A-1" }, + { 0x1461aa01, BTTV_PV150, "Provideo PV150A-2" }, + { 0x1462aa02, BTTV_PV150, "Provideo PV150A-3" }, + { 0x1463aa03, BTTV_PV150, "Provideo PV150A-4" }, + + { 0x1464aa04, BTTV_PV150, "Provideo PV150B-1" }, + { 0x1465aa05, BTTV_PV150, "Provideo PV150B-2" }, + { 0x1466aa06, BTTV_PV150, "Provideo PV150B-3" }, + { 0x1467aa07, BTTV_PV150, "Provideo PV150B-4" }, + + { 0xa132ff00, BTTV_IVC100, "IVC-100" }, + { 0xa1550000, BTTV_IVC200, "IVC-200" }, + { 0xa1550001, BTTV_IVC200, "IVC-200" }, + { 0xa1550002, BTTV_IVC200, "IVC-200" }, + { 0xa1550003, BTTV_IVC200, "IVC-200" }, + { 0xa1550100, BTTV_IVC200, "IVC-200G" }, + { 0xa1550101, BTTV_IVC200, "IVC-200G" }, + { 0xa1550102, BTTV_IVC200, "IVC-200G" }, + { 0xa1550103, BTTV_IVC200, "IVC-200G" }, + { 0xa182ff00, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff01, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff02, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff03, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff04, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff05, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff06, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff07, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff08, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff09, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0a, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0b, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0c, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0d, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0e, BTTV_IVC120, "IVC-120G" }, + { 0xa182ff0f, BTTV_IVC120, "IVC-120G" }, + + { 0x41424344, BTTV_GRANDTEC, "GrandTec Multi Capture" }, + { 0x01020304, BTTV_XGUARD, "Grandtec Grand X-Guard" }, + + { 0x18501851, BTTV_CHRONOS_VS2, "FlyVideo 98 (LR50)/ Chronos Video Shuttle II" }, + { 0xa0501851, BTTV_CHRONOS_VS2, "FlyVideo 98 (LR50)/ Chronos Video Shuttle II" }, + { 0x18511851, BTTV_FLYVIDEO98EZ, "FlyVideo 98EZ (LR51)/ CyberMail AV" }, + { 0x18521852, BTTV_TYPHOON_TVIEW, "FlyVideo 98FM (LR50)/ Typhoon TView TV/FM Tuner" }, + { 0x41a0a051, BTTV_FLYVIDEO_98FM, "Lifeview FlyVideo 98 LR50 Rev Q" }, + { 0x18501f7f, BTTV_FLYVIDEO_98, "Lifeview Flyvideo 98" }, + + { 0x010115cb, BTTV_GMV1, "AG GMV1" }, + { 0x010114c7, BTTV_MODTEC_205, "Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV" }, + + { 0x10b42636, BTTV_HAUPPAUGE878, "STB ???" }, + { 0x217d6606, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0xfff6f6ff, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x03116000, BTTV_SENSORAY311, "Sensoray 311" }, + { 0x00790e11, BTTV_WINDVR, "Canopus WinDVR PCI" }, + { 0xa0fca1a0, BTTV_ZOLTRIX, "Face to Face Tvmax" }, + { 0x20007063, BTTV_PC_HDTV, "pcHDTV HD-2000 TV"}, + { 0x82b2aa6a, BTTV_SIMUS_GVC1100, "SIMUS GVC1100" }, + { 0x146caa0c, BTTV_PV951, "ituner spectra8" }, + { 0x200a1295, BTTV_PXC200, "ImageNation PXC200A" }, + + { 0x40111554, BTTV_PV_BT878P_9B, "Prolink Pixelview PV-BT" }, + { 0x17de0a01, BTTV_KWORLD, "Mecer TV/FM/Video Tuner" }, + + { 0x01051805, BTTV_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #1" }, + { 0x01061805, BTTV_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #2" }, + { 0x01071805, BTTV_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #3" }, + { 0x01081805, BTTV_PICOLO_TETRA_CHIP, "Picolo Tetra Chip #4" }, + + // likely broken, vendor id doesn't match the other magic views ... + //{ 0xa0fca04f, BTTV_MAGICTVIEW063, "Guillemot Maxi TV Video 3" }, + + // DVB cards (using pci function .1 for mpeg data xfer) + { 0x01010071, BTTV_NEBULA_DIGITV, "Nebula Electronics DigiTV" }, + { 0x07611461, BTTV_AVDVBT_761, "AverMedia AverTV DVB-T 761" }, + { 0x001c11bd, BTTV_PINNACLESAT, "Pinnacle PCTV Sat" }, + { 0x002611bd, BTTV_TWINHAN_DST, "Pinnacle PCTV SAT CI" }, + { 0x00011822, BTTV_TWINHAN_DST, "Twinhan VisionPlus DVB" }, + { 0xfc00270f, BTTV_TWINHAN_DST, "ChainTech digitop DST-1000 DVB-S" }, + { 0x07711461, BTTV_AVDVBT_771, "AVermedia AverTV DVB-T 771" }, + { 0xdb1018ac, BTTV_DVICO_DVBT_LITE, "DVICO FusionHDTV DVB-T Lite" }, + + { 0, -1, NULL } +}; + +/* ----------------------------------------------------------------------- */ +/* array with description for bt848 / bt878 tv/grabber cards */ + +struct tvcard bttv_tvcards[] = { +{ +/* ---- card 0x00 ---------------------------------- */ + .name = " *** UNKNOWN/GENERIC *** ", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 0}, + .tuner_type = -1, +},{ + .name = "MIRO PCTV", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0, 0, 0, 10}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Hauppauge (bt848)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "STB, Gateway P/N 6000699 (bt848)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 4, 0, 2, 3, 1}, + .no_msp34xx = 1, + .needs_tvaudio = 1, + .tuner_type = TUNER_PHILIPS_NTSC, + .pll = PLL_28, + .has_radio = 1, +},{ + +/* ---- card 0x04 ---------------------------------- */ + .name = "Intel Create and Share PCI/ Smart Video Recorder III", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .tuner_type = 4, +},{ + .name = "Diamond DTV2000", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 3, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 0, 1, 0, 1, 3}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "AVerMedia TVPhone", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .muxsel = { 2, 3, 1, 1}, + .gpiomask = 0x0f, + .audiomux = { 0x0c, 0x04, 0x08, 0x04, 0}, + /* 0x04 for some cards ?? */ + .needs_tvaudio = 1, + .tuner_type = -1, + .audio_hook = avermedia_tvphone_audio, + .has_remote = 1, +},{ + .name = "MATRIX-Vision MV-Delta", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = -1, + .svhs = 3, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = {0 }, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x08 ---------------------------------- */ + .name = "Lifeview FlyVideo II (Bt848) LR26 / MAXI TV Video PCI2 LR26", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xc00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0xc00, 0x800, 0x400, 0xc00, 0}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "IMS/IXmicro TurboTV", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 3, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 1, 2, 3, 0}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, +},{ + .name = "Hauppauge (bt878)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x0f, /* old: 7 */ + .muxsel = { 2, 0, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "MIRO PCTV pro", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x3014f, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20001,0x10001, 0, 0,10}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x0c ---------------------------------- */ + .name = "ADS Technologies Channel Surfer TV (bt848)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 13, 14, 11, 7, 0, 0}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "AVerMedia TVCapture 98", + .video_inputs = 3, + .audio_inputs = 4, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 13, 14, 11, 7, 0, 0}, + .needs_tvaudio = 1, + .msp34xx_alt = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .audio_hook = avermedia_tv_stereo_audio, +},{ + .name = "Aimslab Video Highway Xtreme (VHX)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 2, 1, 3, 4}, /* old: { 0, 1, 2, 3, 4} */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Zoltrix TV-Max", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {0 , 0, 1 , 0, 10}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x10 ---------------------------------- */ + .name = "Prolink Pixelview PlayTV (bt878)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x01fe00, + .muxsel = { 2, 3, 1, 1}, +#if 0 + // old + .audiomux = { 0x01c000, 0, 0x018000, 0x014000, 0x002000, 0 }, +#else + // 2003-10-20 by "Anton A. Arapov" <arapov@mail.ru> + .audiomux = { 0x001e00, 0, 0x018000, 0x014000, 0x002000, 0 }, +#endif + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Leadtek WinView 601", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x8300f8, + .muxsel = { 2, 3, 1, 1,0}, + .audiomux = { 0x4fa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007}, + .needs_tvaudio = 1, + .tuner_type = -1, + .audio_hook = winview_audio, + .has_radio = 1, +},{ + .name = "AVEC Intercapture", + .video_inputs = 3, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0, + .muxsel = {2, 3, 1, 1}, + .audiomux = {1, 0, 0, 0, 0}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo II EZ /FlyKit LR38 Bt848 (capture only)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = -1, + .svhs = -1, + .gpiomask = 0x8dff00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0 }, + .no_msp34xx = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x14 ---------------------------------- */ + .name = "CEI Raffles Card", + .video_inputs = 3, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .muxsel = {2, 3, 1, 1}, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo 98/ Lucky Star Image World ConferenceTV LR50", + .video_inputs = 4, + .audio_inputs = 2, // tuner, line in + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800}, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, +},{ + .name = "Askey CPH050/ Phoebe Tv Master + FM", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xc00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {0, 1, 0x800, 0x400, 0xc00, 0}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Modular Technology MM201/MM202/MM205/MM210/MM215 PCTV, bt878", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 7, + .muxsel = { 2, 3, -1 }, + .digital_mode = DIGITAL_MODE_CAMERA, + .audiomux = { 0, 0, 0, 0, 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ALPS_TSBB5_PAL_I, +},{ + +/* ---- card 0x18 ---------------------------------- */ + .name = "Askey CPH05X/06X (bt878) [many vendors]", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xe00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {0x400, 0x400, 0x400, 0x400, 0xc00}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_remote = 1, +},{ + .name = "Terratec TerraTV+ Version 1.0 (Bt848)/ Terra TValue Version 1.0/ Vobis TV-Boostar", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1f0fff, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20000, 0x30000, 0x10000, 0, 0x40000}, + .needs_tvaudio = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .audio_hook = terratv_audio, +},{ + .name = "Hauppauge WinCam newer (bt878)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 7, + .muxsel = { 2, 0, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .tuner_type = -1, +},{ + .name = "Lifeview FlyVideo 98/ MAXI TV Video PCI2 LR50", + .video_inputs = 4, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800}, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_SECAM, +},{ + +/* ---- card 0x1c ---------------------------------- */ + .name = "Terratec TerraTV+ Version 1.1 (bt878)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1f0fff, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20000, 0x30000, 0x10000, 0x00000, 0x40000}, + .needs_tvaudio = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .audio_hook = terratv_audio, + /* GPIO wiring: + External 20 pin connector (for Active Radio Upgrade board) + gpio00: i2c-sda + gpio01: i2c-scl + gpio02: om5610-data + gpio03: om5610-clk + gpio04: om5610-wre + gpio05: om5610-stereo + gpio06: rds6588-davn + gpio07: Pin 7 n.c. + gpio08: nIOW + gpio09+10: nIOR, nSEL ?? (bt878) + gpio09: nIOR (bt848) + gpio10: nSEL (bt848) + Sound Routing: + gpio16: u2-A0 (1st 4052bt) + gpio17: u2-A1 + gpio18: u2-nEN + gpio19: u4-A0 (2nd 4052) + gpio20: u4-A1 + u4-nEN - GND + Btspy: + 00000 : Cdrom (internal audio input) + 10000 : ext. Video audio input + 20000 : TV Mono + a0000 : TV Mono/2 + 1a0000 : TV Stereo + 30000 : Radio + 40000 : Mute + */ + +},{ + /* Jannik Fritsch <jannik@techfak.uni-bielefeld.de> */ + .name = "Imagenation PXC200", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, /* was: 4 */ + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = { 0 }, + .needs_tvaudio = 1, + .tuner_type = -1, + .muxsel_hook = PXC200_muxsel, + +},{ + .name = "Lifeview FlyVideo 98 LR50", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, //0x8dfe00 + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x0800, 0x1000, 0x1000, 0x1800, 0 }, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Formac iProTV, Formac ProTV I (bt848)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 1, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 0, 0, 0, 0 }, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, +},{ + +/* ---- card 0x20 ---------------------------------- */ + .name = "Intel Create and Share PCI/ Smart Video Recorder III", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .tuner_type = 4, +},{ + .name = "Terratec TerraTValue Version Bt878", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xffff00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x500, 0, 0x300, 0x900, 0x900}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, +},{ + .name = "Leadtek WinFast 2000/ WinFast 2000 XP", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 1, 0}, // TV, CVid, SVid, CVid over SVid connector +#if 0 + .gpiomask = 0xc33000, + .audiomux = { 0x422000,0x1000,0x0000,0x620000,0x800000 }, +#else + /* Alexander Varakin <avarakin@hotmail.com> [stereo version] */ + .gpiomask = 0xb33000, + .audiomux = { 0x122000,0x1000,0x0000,0x620000,0x800000 }, +#endif + /* Audio Routing for "WinFast 2000 XP" (no tv stereo !) + gpio23 -- hef4052:nEnable (0x800000) + gpio12 -- hef4052:A1 + gpio13 -- hef4052:A0 + 0x0000: external audio + 0x1000: FM + 0x2000: TV + 0x3000: n.c. + Note: There exists another variant "Winfast 2000" with tv stereo !? + Note: eeprom only contains FF and pci subsystem id 107d:6606 + */ + .needs_tvaudio = 0, + .pll = PLL_28, + .has_radio = 1, + .tuner_type = 5, // default for now, gpio reads BFFF06 for Pal bg+dk + .audio_hook = winfast2000_audio, + .has_remote = 1, +},{ + .name = "Lifeview FlyVideo 98 LR50 / Chronos Video Shuttle II", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800}, + .pll = PLL_28, + .tuner_type = -1, +},{ + +/* ---- card 0x24 ---------------------------------- */ + .name = "Lifeview FlyVideo 98FM LR50 / Typhoon TView TV/FM Tuner", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800, 0 }, + .pll = PLL_28, + .tuner_type = -1, + .has_radio = 1, +},{ + .name = "Prolink PixelView PlayTV pro", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xff, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0x21, 0x20, 0x24, 0x2c, 0x29, 0x29 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Askey CPH06X TView99", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x551e00, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 0x551400, 0x551200, 0, 0, 0x551c00, 0x551200 }, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = 1, + .has_remote = 1, +},{ + .name = "Pinnacle PCTV Studio/Rave", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x03000F, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0xd0001, 0, 0, 1}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = -1, +},{ + +/* ---- card 0x28 ---------------------------------- */ + .name = "STB TV PCI FM, Gateway P/N 6000704 (bt878), 3Dfx VoodooTV 100", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 4, 0, 2, 3, 1}, + .no_msp34xx = 1, + .needs_tvaudio = 1, + .tuner_type = TUNER_PHILIPS_NTSC, + .pll = PLL_28, + .has_radio = 1, +},{ + .name = "AVerMedia TVPhone 98", + .video_inputs = 3, + .audio_inputs = 4, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 13, 4, 11, 7, 0, 0}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_radio = 1, + .audio_hook = avermedia_tvphone_audio, +},{ + .name = "ProVideo PV951", /* pic16c54 */ + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 0, 0, 0}, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 1, +},{ + .name = "Little OnAir TV", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xe00b, + .muxsel = {2, 3, 1, 1}, + .audiomux = {0xff9ff6, 0xff9ff6, 0xff1ff7, 0, 0xff3ffc}, + .no_msp34xx = 1, + .tuner_type = -1, +},{ + +/* ---- card 0x2c ---------------------------------- */ + .name = "Sigma TVII-FM", + .video_inputs = 2, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 3, + .muxsel = {2, 3, 1, 1}, + .audiomux = {1, 1, 0, 2, 3}, + .no_msp34xx = 1, + .pll = PLL_NONE, + .tuner_type = -1, +},{ + .name = "MATRIX-Vision MV-Delta 2", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = -1, + .svhs = 3, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = {0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Zoltrix Genie TV/FM", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xbcf03f, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0xbc803f, 0xbc903f, 0xbcb03f, 0, 0xbcb03f}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 21, +},{ + .name = "Terratec TV/Radio+", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x70000, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x20000, 0x30000, 0x10000, 0, 0x40000, 0x20000 }, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .pll = PLL_35, + .tuner_type = 1, + .has_radio = 1, +},{ + +/* ---- card 0x30 ---------------------------------- */ + .name = "Askey CPH03x/ Dynalink Magic TView", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = {2,0,0,0,1}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "IODATA GV-BCTV3/PCI", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x010f00, + .muxsel = {2, 3, 0, 0}, + .audiomux = {0x10000, 0, 0x10000, 0, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_ALPS_TSHC6_NTSC, + .audio_hook = gvbctv3pci_audio, +},{ + .name = "Prolink PV-BT878P+4E / PixelView PlayTV PAK / Lenco MXTV-9578 CP", + .video_inputs = 5, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 0xAA0000, + .muxsel = { 2,3,1,1,-1 }, + .digital_mode = DIGITAL_MODE_CAMERA, + .audiomux = { 0x20000, 0, 0x80000, 0x80000, 0xa8000, 0x46000 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL_I, + .has_remote = 1, + /* GPIO wiring: (different from Rev.4C !) + GPIO17: U4.A0 (first hef4052bt) + GPIO19: U4.A1 + GPIO20: U5.A1 (second hef4052bt) + GPIO21: U4.nEN + GPIO22: BT832 Reset Line + GPIO23: A5,A0, U5,nEN + Note: At i2c=0x8a is a Bt832 chip, which changes to 0x88 after being reset via GPIO22 + */ +},{ + .name = "Eagle Wireless Capricorn2 (bt878A)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 0, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .pll = PLL_28, + .tuner_type = -1 /* TUNER_ALPS_TMDH2_NTSC */, +},{ + +/* ---- card 0x34 ---------------------------------- */ + /* David Härdeman <david@2gen.com> */ + .name = "Pinnacle PCTV Studio Pro", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 0x03000F, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 0xd0001, 0, 0, 10}, + /* sound path (5 sources): + MUX1 (mask 0x03), Enable Pin 0x08 (0=enable, 1=disable) + 0= ext. Audio IN + 1= from MUX2 + 2= Mono TV sound from Tuner + 3= not connected + MUX2 (mask 0x30000): + 0,2,3= from MSP34xx + 1= FM stereo Radio from Tuner */ + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Claas Langbehn <claas@bigfoot.com>, + Sven Grothklags <sven@upb.de> */ + .name = "Typhoon TView RDS + FM Stereo / KNC1 TV Station RDS", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1c, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 0x10, 8, 4 }, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_radio = 1, +},{ + /* Tim Röstermundt <rosterm@uni-muenster.de> + in de.comp.os.unix.linux.hardware: + options bttv card=0 pll=1 radio=1 gpiomask=0x18e0 + audiomux=0x44c71f,0x44d71f,0,0x44d71f,0x44dfff + options tuner type=5 */ + .name = "Lifeview FlyVideo 2000 /FlyVideo A2/ Lifetec LT 9415 TV [LR90]", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x18e0, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x0000,0x0800,0x1000,0x1000,0x18e0 }, + /* For cards with tda9820/tda9821: + 0x0000: Tuner normal stereo + 0x0080: Tuner A2 SAP (second audio program = Zweikanalton) + 0x0880: Tuner A2 stereo */ + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Miguel Angel Alvarez <maacruz@navegalia.com> + old Easy TV BT848 version (model CPH031) */ + .name = "Askey CPH031/ BESTBUY Easy TV", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xF, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 2, 0, 0, 0, 10}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_PAL, +},{ + +/* ---- card 0x38 ---------------------------------- */ + /* Gordon Heydon <gjheydon@bigfoot.com ('98) */ + .name = "Lifeview FlyVideo 98FM LR50", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1800, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0x800, 0x1000, 0x1000, 0x1800, 0 }, + .pll = PLL_28, + .tuner_type = 5, +},{ + /* This is the ultimate cheapo capture card + * just a BT848A on a small PCB! + * Steve Hosgood <steve@equiinet.com> */ + .name = "GrandTec 'Grand Video Capture' (Bt848)", + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .gpiomask = 0, + .muxsel = { 3, 1 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_35, + .tuner_type = -1, +},{ + /* Daniel Herrington <daniel.herrington@home.com> */ + .name = "Askey CPH060/ Phoebe TV Master Only (No FM)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xe00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x400, 0x400, 0x400, 0x400, 0x800, 0x400 }, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4036FY5_NTSC, +},{ + /* Matti Mottus <mottus@physic.ut.ee> */ + .name = "Askey CPH03x TV Capturer", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x03000F, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 2,0,0,0,1 }, + .pll = PLL_28, + .tuner_type = 0, +},{ + +/* ---- card 0x3c ---------------------------------- */ + /* Philip Blundell <philb@gnu.org> */ + .name = "Modular Technology MM100PCTV", + .video_inputs = 2, + .audio_inputs = 2, + .tuner = 0, + .svhs = -1, + .gpiomask = 11, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0, 0, 1, 8}, + .pll = PLL_35, + .tuner_type = TUNER_TEMIC_PAL, + +},{ + /* Adrian Cox <adrian@humboldt.co.uk */ + .name = "AG Electronics GMV1", + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .gpiomask = 0xF, + .muxsel = { 2, 2}, + .audiomux = { }, + .no_msp34xx = 1, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Miguel Angel Alvarez <maacruz@navegalia.com> + new Easy TV BT878 version (model CPH061) + special thanks to Informatica Mieres for providing the card */ + .name = "Askey CPH061/ BESTBUY Easy TV (bt878)", + .video_inputs = 3, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xFF, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 1, 0, 4, 4, 9}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, +},{ + /* Lukas Gebauer <geby@volny.cz> */ + .name = "ATI TV-Wonder", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xf03f, + .muxsel = { 2, 3, 1, 0 }, + .audiomux = { 0xbffe, 0, 0xbfff, 0, 0xbffe}, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4006FN5_MULTI_PAL, +},{ + +/* ---- card 0x40 ---------------------------------- */ + /* Lukas Gebauer <geby@volny.cz> */ + .name = "ATI TV-Wonder VE", + .video_inputs = 2, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 1, + .muxsel = { 2, 3, 0, 1}, + .audiomux = { 0, 0, 1, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_TEMIC_4006FN5_MULTI_PAL, +},{ + /* DeeJay <deejay@westel900.net (2000S) */ + .name = "Lifeview FlyVideo 2000S LR90", + .video_inputs = 3, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x18e0, + .muxsel = { 2, 3, 0, 1}, + /* Radio changed from 1e80 to 0x800 to make + FlyVideo2000S in .hu happy (gm)*/ + /* -dk-???: set mute=0x1800 for tda9874h daughterboard */ + .audiomux = { 0x0000,0x0800,0x1000,0x1000,0x1800, 0x1080 }, + .audio_hook = fv2000s_audio, + .no_msp34xx = 1, + .no_tda9875 = 1, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = 5, +},{ + .name = "Terratec TValueRadio", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0xffff00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0x500, 0x500, 0x300, 0x900, 0x900}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_radio = 1, +},{ + /* TANAKA Kei <peg00625@nifty.com> */ + .name = "IODATA GV-BCTV4/PCI", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x010f00, + .muxsel = {2, 3, 0, 0}, + .audiomux = {0x10000, 0, 0x10000, 0, 0, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_SHARP_2U5JF5540_NTSC, + .audio_hook = gvbctv3pci_audio, +},{ + +/* ---- card 0x44 ---------------------------------- */ + .name = "3Dfx VoodooTV FM (Euro), VoodooTV 200 (USA)", + // try "insmod msp3400 simple=0" if you have + // sound problems with this card. + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 0x4f8a00, + // 0x100000: 1=MSP enabled (0=disable again) + // 0x010000: Connected to "S0" on tda9880 (0=Pal/BG, 1=NTSC) + .audiomux = {0x947fff, 0x987fff,0x947fff,0x947fff, 0x947fff}, + // tvtuner, radio, external,internal, mute, stereo + /* tuner, Composit, SVid, Composit-on-Svid-adapter*/ + .muxsel = { 2, 3 ,0 ,1}, + .tuner_type = TUNER_MT2032, + .pll = PLL_28, + .has_radio = 1, +},{ + /* Philip Blundell <pb@nexus.co.uk> */ + .name = "Active Imaging AIMMS", + .video_inputs = 1, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .pll = PLL_28, + .muxsel = { 2 }, + .gpiomask = 0 +},{ + /* Tomasz Pyra <hellfire@sedez.iq.pl> */ + .name = "Prolink Pixelview PV-BT878P+ (Rev.4C,8E)", + .video_inputs = 3, + .audio_inputs = 4, + .tuner = 0, + .svhs = 2, + .gpiomask = 15, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 11, 7, 13, 0}, // TV and Radio with same GPIO ! + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = 25, + .has_remote = 1, + /* GPIO wiring: + GPIO0: U4.A0 (hef4052bt) + GPIO1: U4.A1 + GPIO2: U4.A1 (second hef4052bt) + GPIO3: U4.nEN, U5.A0, A5.nEN + GPIO8-15: vrd866b ? + */ +},{ + .name = "Lifeview FlyVideo 98EZ (capture only) LR51", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .muxsel = { 2, 3, 1, 1}, // AV1, AV2, SVHS, CVid adapter on SVHS + .pll = PLL_28, + .no_msp34xx = 1, +},{ + +/* ---- card 0x48 ---------------------------------- */ + /* Dariusz Kowalewski <darekk@automex.pl> */ + .name = "Prolink Pixelview PV-BT878P+9B (PlayTV Pro rev.9B FM+NICAM)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x3f, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0x01, 0x00, 0x03, 0x03, 0x09, 0x02 }, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .pll = PLL_28, + .tuner_type = 5, + .audio_hook = pvbt878p9b_audio, // Note: not all cards have stereo + .has_radio = 1, // Note: not all cards have radio + .has_remote = 1, + /* GPIO wiring: + GPIO0: A0 hef4052 + GPIO1: A1 hef4052 + GPIO3: nEN hef4052 + GPIO8-15: vrd866b + GPIO20,22,23: R30,R29,R28 + */ +},{ + /* Clay Kunz <ckunz@mail.arc.nasa.gov> */ + /* you must jumper JP5 for the card to work */ + .name = "Sensoray 311", + .video_inputs = 5, + .audio_inputs = 0, + .tuner = -1, + .svhs = 4, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0, 0}, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .tuner_type = -1, +},{ + /* Miguel Freitas <miguel@cetuc.puc-rio.br> */ + .name = "RemoteVision MX (RV605)", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0x00, + .gpiomask2 = 0x07ff, + .muxsel = { 0x33, 0x13, 0x23, 0x43, 0xf3, 0x73, 0xe3, 0x03, + 0xd3, 0xb3, 0xc3, 0x63, 0x93, 0x53, 0x83, 0xa3 }, + .no_msp34xx = 1, + .no_tda9875 = 1, + .tuner_type = -1, + .muxsel_hook = rv605_muxsel, +},{ + .name = "Powercolor MTV878/ MTV878R/ MTV878F", + .video_inputs = 3, + .audio_inputs = 2, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x1C800F, // Bit0-2: Audio select, 8-12:remote control 14:remote valid 15:remote reset + .muxsel = { 2, 1, 1, }, + .audiomux = { 0, 1, 2, 2, 4 }, + .needs_tvaudio = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .pll = PLL_28, + .has_radio = 1, +},{ + +/* ---- card 0x4c ---------------------------------- */ + /* Masaki Suzuki <masaki@btree.org> */ + .name = "Canopus WinDVR PCI (COMPAQ Presario 3524JP, 5112JP)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x140007, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0, 1, 2, 3, 4, 0 }, + .tuner_type = TUNER_PHILIPS_NTSC, + .audio_hook = windvr_audio, +},{ + .name = "GrandTec Multi Capture Card (Bt878)", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "Jetway TV/Capture JW-TV878-FBK, Kworld KW-TV878RF", + .video_inputs = 4, + .audio_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1 }, // Tuner, SVid, SVHS, SVid to SVHS connector + .audiomux = { 0 ,0 ,4, 4,4,4},// Yes, this tuner uses the same audio output for TV and FM radio! + // This card lacks external Audio In, so we mute it on Ext. & Int. + // The PCB can take a sbx1637/sbx1673, wiring unknown. + // This card lacks PCI subsystem ID, sigh. + // audiomux=1: lower volume, 2+3: mute + // btwincap uses 0x80000/0x80003 + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 5, // Samsung TCPA9095PC27A (BG+DK), philips compatible, w/FM, stereo and + // radio signal strength indicators work fine. + .has_radio = 1, + /* GPIO Info: + GPIO0,1: HEF4052 A0,A1 + GPIO2: HEF4052 nENABLE + GPIO3-7: n.c. + GPIO8-13: IRDC357 data0-5 (data6 n.c. ?) [chip not present on my card] + GPIO14,15: ?? + GPIO16-21: n.c. + GPIO22,23: ?? + ?? : mtu8b56ep microcontroller for IR (GPIO wiring unknown)*/ +},{ + /* Arthur Tetzlaff-Deas, DSP Design Ltd <software@dspdesign.com> */ + .name = "DSP Design TCVIDEO", + .video_inputs = 4, + .svhs = -1, + .muxsel = { 2, 3, 1, 0}, + .pll = PLL_28, + .tuner_type = -1, +},{ + + /* ---- card 0x50 ---------------------------------- */ + .name = "Hauppauge WinTV PVR", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 0, 1, 1}, + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, + + .gpiomask = 7, + .audiomux = {7}, +},{ + .name = "IODATA GV-BCTV5/PCI", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x0f0f80, + .muxsel = {2, 3, 1, 0}, + .audiomux = {0x030000, 0x010000, 0, 0, 0x020000, 0}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .audio_hook = gvbctv5pci_audio, + .has_radio = 1, +},{ + .name = "Osprey 100/150 (878)", /* 0x1(2|3)-45C6-C1 */ + .video_inputs = 4, /* id-inputs-clock */ + .audio_inputs = 0, + .tuner = -1, + .svhs = 3, + .muxsel = { 3, 2, 0, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 100/150 (848)", /* 0x04-54C0-C1 & older boards */ + .video_inputs = 3, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .muxsel = { 2, 3, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + + /* ---- card 0x54 ---------------------------------- */ + .name = "Osprey 101 (848)", /* 0x05-40C0-C1 */ + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .muxsel = { 3, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 101/151", /* 0x1(4|5)-0004-C4 */ + .video_inputs = 1, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .muxsel = { 0 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 101/151 w/ svid", /* 0x(16|17|20)-00C4-C1 */ + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = 1, + .muxsel = { 0, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 200/201/250/251", /* 0x1(8|9|E|F)-0004-C4 */ + .video_inputs = 1, + .audio_inputs = 1, + .tuner = -1, + .svhs = -1, + .muxsel = { 0 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + + /* ---- card 0x58 ---------------------------------- */ + .name = "Osprey 200/250", /* 0x1(A|B)-00C4-C1 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 0, 1 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 210/220", /* 0x1(A|B)-04C0-C1 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 3 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 500", /* 500 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 3 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +},{ + .name = "Osprey 540", /* 540 */ + .video_inputs = 4, + .audio_inputs = 1, + .tuner = -1, +#if 0 /* TODO ... */ + .svhs = OSPREY540_SVID_ANALOG, + .muxsel = { [OSPREY540_COMP_ANALOG] = 2, + [OSPREY540_SVID_ANALOG] = 3, }, +#endif + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, +#if 0 /* TODO ... */ + .muxsel_hook = osprey_540_muxsel, + .picture_hook = osprey_540_set_picture, +#endif +},{ + + /* ---- card 0x5C ---------------------------------- */ + .name = "Osprey 2000", /* 2000 */ + .video_inputs = 2, + .audio_inputs = 1, + .tuner = -1, + .svhs = 1, + .muxsel = { 2, 3 }, + .pll = PLL_28, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, /* must avoid, conflicts with the bt860 */ +},{ + /* M G Berberich <berberic@forwiss.uni-passau.de> */ + .name = "IDS Eagle", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 0, 1, 2, 3 }, + .muxsel_hook = eagle_muxsel, + .no_msp34xx = 1, + .no_tda9875 = 1, + .pll = PLL_28, +},{ + .name = "Pinnacle PCTV Sat", + .video_inputs = 2, + .audio_inputs = 0, + .svhs = 1, + .tuner = -1, + .tuner_type = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .gpiomask = 0x01, + .audiomux = { 0, 0, 0, 0, 1 }, + .muxsel = { 3, 0, 1, 2}, + .needs_tvaudio = 0, + .pll = PLL_28, + .no_gpioirq = 1, + .has_dvb = 1, +},{ + .name = "Formac ProTV II (bt878)", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 3, + .gpiomask = 2, + // TV, Comp1, Composite over SVID con, SVID + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 2, 0, 0, 0 }, + .pll = PLL_28, + .has_radio = 1, + .tuner_type = TUNER_PHILIPS_PAL, + /* sound routing: + GPIO=0x00,0x01,0x03: mute (?) + 0x02: both TV and radio (tuner: FM1216/I) + The card has onboard audio connectors labeled "cdrom" and "board", + not soldered here, though unknown wiring. + Card lacks: external audio in, pci subsystem id. + */ +},{ + + /* ---- card 0x60 ---------------------------------- */ + .name = "MachTV", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 7, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 1, 2, 3, 4}, + .needs_tvaudio = 1, + .tuner_type = 5, + .pll = 1, +},{ + .name = "Euresys Picolo", + .video_inputs = 3, + .audio_inputs = 0, + .tuner = -1, + .svhs = 2, + .gpiomask = 0, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .muxsel = { 2, 0, 1}, + .pll = PLL_28, +},{ + /* Luc Van Hoeylandt <luc@e-magic.be> */ + .name = "ProVideo PV150", /* 0x4f */ + .video_inputs = 2, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 2, 3 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* Hiroshi Takekawa <sian@big.or.jp> */ + /* This card lacks subsystem ID */ + .name = "AD-TVK503", /* 0x63 */ + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x001e8007, + .muxsel = { 2, 3, 1, 0 }, + /* Tuner, Radio, external, internal, off, on */ + .audiomux = { 0x08, 0x0f, 0x0a, 0x08, 0x0f, 0x08 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 2, + .audio_hook = adtvk503_audio, +},{ + + /* ---- card 0x64 ---------------------------------- */ + .name = "Hercules Smart TV Stereo", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x00, + .muxsel = { 2, 3, 1, 1 }, + .needs_tvaudio = 1, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = 5, + /* Notes: + - card lacks subsystem ID + - stereo variant w/ daughter board with tda9874a @0xb0 + - Audio Routing: + always from tda9874 independent of GPIO (?) + external line in: unknown + - Other chips: em78p156elp @ 0x96 (probably IR remote control) + hef4053 (instead 4052) for unknown function + */ +},{ + .name = "Pace TV & Radio Card", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 1}, // Tuner, CVid, SVid, CVid over SVid connector + .gpiomask = 0, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = 1, + .has_radio = 1, + .pll = PLL_28, + /* Bt878, Bt832, FI1246 tuner; no pci subsystem id + only internal line out: (4pin header) RGGL + Radio must be decoded by msp3410d (not routed through)*/ + // .digital_mode = DIGITAL_MODE_CAMERA, // todo! +},{ + /* Chris Willing <chris@vislab.usyd.edu.au> */ + .name = "IVC-200", + .video_inputs = 1, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0xdf, + .muxsel = { 2 }, + .pll = PLL_28, +},{ + .name = "Grand X-Guard / Trust 814PCI", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .tuner_type = 4, + .gpiomask2 = 0xff, + .muxsel = { 2,2,2,2, 3,3,3,3, 1,1,1,1, 0,0,0,0 }, + .muxsel_hook = xguard_muxsel, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, +},{ + + /* ---- card 0x68 ---------------------------------- */ + .name = "Nebula Electronics DigiTV", + .video_inputs = 1, + .tuner = -1, + .svhs = -1, + .muxsel = { 2, 3, 1, 0}, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_dvb = 1, + .no_gpioirq = 1, +},{ + /* Jorge Boncompte - DTI2 <jorge@dti2.net> */ + .name = "ProVideo PV143", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .muxsel = { 2, 3, 1, 0 }, + .audiomux = { 0 }, + .needs_tvaudio = 0, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* M.Klahr@phytec.de */ + .name = "PHYTEC VD-009-X1 MiniDIN (bt878)", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = { 2, 3, 1, 0}, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "PHYTEC VD-009-X1 Combi (bt878)", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 3, + .gpiomask = 0x00, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + + /* ---- card 0x6c ---------------------------------- */ + .name = "PHYTEC VD-009 MiniDIN (bt878)", + .video_inputs = 10, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 9, + .gpiomask = 0x00, + .gpiomask2 = 0x03, /* gpiomask2 defines the bits used to switch audio + via the upper nibble of muxsel. here: used for + xternal video-mux */ + .muxsel = { 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, 0x01, 0x00 }, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "PHYTEC VD-009 Combi (bt878)", + .video_inputs = 10, + .audio_inputs = 0, + .tuner = -1, /* card has no tuner */ + .svhs = 9, + .gpiomask = 0x00, + .gpiomask2 = 0x03, /* gpiomask2 defines the bits used to switch audio + via the upper nibble of muxsel. here: used for + xternal video-mux */ + .muxsel = { 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, 0x01, 0x01 }, + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .needs_tvaudio = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + .name = "IVC-100", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0xdf, + .muxsel = { 2, 3, 1, 0 }, + .pll = PLL_28, +},{ + /* IVC-120G - Alan Garfield <alan@fromorbit.com> */ + .name = "IVC-120G", + .video_inputs = 16, + .audio_inputs = 0, /* card has no audio */ + .tuner = -1, /* card has no tuner */ + .tuner_type = -1, + .svhs = -1, /* card has no svhs */ + .needs_tvaudio = 0, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .gpiomask = 0x00, + .muxsel = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, + .muxsel_hook = ivc120_muxsel, + .pll = PLL_28, +},{ + + /* ---- card 0x70 ---------------------------------- */ + .name = "pcHDTV HD-2000 TV", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 0}, + .tuner_type = TUNER_PHILIPS_ATSC, + .has_dvb = 1, +},{ + .name = "Twinhan DST + clones", + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .no_video = 1, + .has_dvb = 1, +},{ + .name = "Winfast VC100", + .video_inputs = 3, + .audio_inputs = 0, + .svhs = 1, + .tuner = -1, // no tuner + .muxsel = { 3, 1, 1, 3}, // Vid In, SVid In, Vid over SVid in connector + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = TUNER_ABSENT, + .no_video = 1, + .pll = PLL_28, +},{ + .name = "Teppro TEV-560/InterVision IV-560", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 3, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 1, 1, 1, 1, 0}, + .needs_tvaudio = 1, + .tuner_type = TUNER_PHILIPS_PAL, + .pll = PLL_35, +},{ + + /* ---- card 0x74 ---------------------------------- */ + .name = "SIMUS GVC1100", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .tuner_type = -1, + .pll = PLL_28, + .muxsel = { 2, 2, 2, 2}, + .gpiomask = 0x3F, + .muxsel_hook = gvc1100_muxsel, +},{ + /* Carlos Silva r3pek@r3pek.homelinux.org || card 0x75 */ + .name = "NGS NGSTV+", + .video_inputs = 3, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = {2, 3, 0, 0}, + .audiomux = {0, 0, 0, 0, 0x000003, 0}, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_remote = 1, +},{ + /* http://linuxmedialabs.com */ + .name = "LMLBT4", + .video_inputs = 4, /* IN1,IN2,IN3,IN4 */ + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .muxsel = { 2, 3, 1, 0 }, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .needs_tvaudio = 0, +},{ + /* Helmroos Harri <harri.helmroos@pp.inet.fi> */ + .name = "Tekram M205 PRO", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .tuner_type = TUNER_PHILIPS_PAL, + .svhs = 2, + .needs_tvaudio = 0, + .gpiomask = 0x68, + .muxsel = { 2, 3, 1}, + .audiomux = { 0x68, 0x68, 0x61, 0x61, 0x00 }, + .pll = PLL_28, +},{ + + /* ---- card 0x78 ---------------------------------- */ + /* Javier Cendan Ares <jcendan@lycos.es> */ + /* bt878 TV + FM without subsystem ID */ + .name = "Conceptronic CONTVFMi", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x008007, + .muxsel = { 2, 3, 1, 1 }, + .audiomux = { 0, 1, 2, 2, 3 }, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_remote = 1, + .has_radio = 1, +},{ + /*Eric DEBIEF <debief@telemsa.com>*/ + /*EURESYS Picolo Tetra : 4 Conexant Fusion 878A, no audio, video input set with analog multiplexers GPIO controled*/ + /* adds picolo_tetra_muxsel(), picolo_tetra_init(), the folowing declaration strucure, and #define BTTV_PICOLO_TETRA_CHIP*/ + /*0x79 in bttv.h*/ + .name = "Euresys Picolo Tetra", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0, + .gpiomask2 = 0x3C<<16,/*Set the GPIO[18]->GPIO[21] as output pin.==> drive the video inputs through analog multiplexers*/ + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .muxsel = {2,2,2,2},/*878A input is always MUX0, see above.*/ + .audiomux = { 0, 0, 0, 0, 0, 0 }, /* card has no audio */ + .pll = PLL_28, + .needs_tvaudio = 0, + .muxsel_hook = picolo_tetra_muxsel,/*Required as it doesn't follow the classic input selection policy*/ +},{ + /* Spirit TV Tuner from http://spiritmodems.com.au */ + /* Stafford Goodsell <surge@goliath.homeunix.org> */ + .name = "Spirit TV Tuner", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x0000000f, + .muxsel = { 2, 1, 1 }, + .audiomux = { 0x02, 0x00, 0x00, 0x00, 0x00}, + .tuner_type = TUNER_TEMIC_PAL, + .no_msp34xx = 1, + .no_tda9875 = 1, +},{ + /* Wolfram Joost <wojo@frokaschwei.de> */ + .name = "AVerMedia AVerTV DVB-T 771", + .video_inputs = 2, + .svhs = 1, + .tuner = -1, + .tuner_type = TUNER_ABSENT, + .muxsel = { 3 , 3 }, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .has_dvb = 1, + .no_gpioirq = 1, + .has_remote = 1, +},{ + /* ---- card 0x7c ---------------------------------- */ + /* Matt Jesson <dvb@jesson.eclipse.co.uk> */ + /* Based on the Nebula card data - added remote and new card number - BTTV_AVDVBT_761, see also ir-kbd-gpio.c */ + .name = "AverMedia AverTV DVB-T 761", + .video_inputs = 2, + .tuner = -1, + .svhs = 1, + .muxsel = { 3, 1, 2, 0}, /* Comp0, S-Video, ?, ? */ + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = -1, + .has_dvb = 1, + .no_gpioirq = 1, + .has_remote = 1, +},{ + /* andre.schwarz@matrix-vision.de */ + .name = "MATRIX Vision Sigma-SQ", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0x0, + .muxsel = { 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3 }, + .muxsel_hook = sigmaSQ_muxsel, + .audiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* andre.schwarz@matrix-vision.de */ + .name = "MATRIX Vision Sigma-SLC", + .video_inputs = 4, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .gpiomask = 0x0, + .muxsel = { 2, 2, 2, 2 }, + .muxsel_hook = sigmaSLC_muxsel, + .audiomux = { 0 }, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = -1, +},{ + /* BTTV_APAC_VIEWCOMP */ + /* Attila Kondoros <attila.kondoros@chello.hu> */ + /* bt878 TV + FM 0x00000000 subsystem ID */ + .name = "APAC Viewcomp 878(AMAX)", + .video_inputs = 2, + .audio_inputs = 1, + .tuner = 0, + .svhs = -1, + .gpiomask = 0xFF, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 2, 0, 0, 0, 10}, + .needs_tvaudio = 0, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_PAL, + .has_remote = 1, /* miniremote works, see ir-kbd-gpio.c */ + .has_radio = 1, /* not every card has radio */ +},{ + + /* ---- card 0x80 ---------------------------------- */ + /* Chris Pascoe <c.pascoe@itee.uq.edu.au> */ + .name = "DVICO FusionHDTV DVB-T Lite", + .tuner = -1, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .no_video = 1, + .has_dvb = 1, + .tuner_type = -1, +},{ + /* Steven <photon38@pchome.com.tw> */ + .name = "V-Gear MyVCD", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x3f, + .muxsel = {2, 3, 1, 0}, + .audiomux = {0x31, 0x31, 0x31, 0x31, 0x31, 0x31}, + .no_msp34xx = 1, + .pll = PLL_28, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .has_radio = 0, + // .has_remote = 1, +},{ + /* Rick C <cryptdragoon@gmail.com> */ + .name = "Super TV Tuner", + .video_inputs = 4, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .muxsel = { 2, 3, 1, 0}, + .tuner_type = TUNER_PHILIPS_NTSC, + .gpiomask = 0x008007, + .audiomux = { 0, 0x000001,0,0, 0}, + .needs_tvaudio = 1, + .has_radio = 1, +},{ + /* Chris Fanning <video4linux@haydon.net> */ + .name = "Tibet Systems 'Progress DVR' CS16", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .svhs = -1, + .muxsel = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .tuner_type = -1, + .muxsel_hook = tibetCS16_muxsel, +}, +{ + /* Bill Brack <wbrack@mmm.com.hk> */ + /* + * Note that, because of the card's wiring, the "master" + * BT878A chip (i.e. the one which controls the analog switch + * and must use this card type) is the 2nd one detected. The + * other 3 chips should use card type 0x85, whose description + * follows this one. There is a EEPROM on the card (which is + * connected to the I2C of one of those other chips), but is + * not currently handled. There is also a facility for a + * "monitor", which is also not currently implemented. + */ + .name = "Kodicom 4400R (master)", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + /* GPIO bits 0-9 used for analog switch: + * 00 - 03: camera selector + * 04 - 06: channel (controller) selector + * 07: data (1->on, 0->off) + * 08: strobe + * 09: reset + * bit 16 is input from sync separator for the channel + */ + .gpiomask = 0x0003ff, + .no_gpioirq = 1, + .muxsel = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .no_tda9875 = 1, + .muxsel_hook = kodicom4400r_muxsel, +}, +{ + /* Bill Brack <wbrack@mmm.com.hk> */ + /* Note that, for reasons unknown, the "master" BT878A chip (i.e. the + * one which controls the analog switch, and must use the card type) + * is the 2nd one detected. The other 3 chips should use this card + * type + */ + .name = "Kodicom 4400R (slave)", + .video_inputs = 16, + .audio_inputs = 0, + .tuner = -1, + .tuner_type = -1, + .svhs = -1, + .gpiomask = 0x010000, + .no_gpioirq = 1, + .muxsel = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + .pll = PLL_28, + .no_msp34xx = 1, + .no_tda7432 = 1, + .no_tda9875 = 1, + .muxsel_hook = kodicom4400r_muxsel, +}}; + +static const unsigned int bttv_num_tvcards = ARRAY_SIZE(bttv_tvcards); + +/* ----------------------------------------------------------------------- */ + +static unsigned char eeprom_data[256]; + +/* + * identify card + */ +void __devinit bttv_idcard(struct bttv *btv) +{ + unsigned int gpiobits; + int i,type; + unsigned short tmp; + + /* read PCI subsystem ID */ + pci_read_config_word(btv->c.pci, PCI_SUBSYSTEM_ID, &tmp); + btv->cardid = tmp << 16; + pci_read_config_word(btv->c.pci, PCI_SUBSYSTEM_VENDOR_ID, &tmp); + btv->cardid |= tmp; + + if (0 != btv->cardid && 0xffffffff != btv->cardid) { + /* look for the card */ + for (type = -1, i = 0; cards[i].id != 0; i++) + if (cards[i].id == btv->cardid) + type = i; + + if (type != -1) { + /* found it */ + printk(KERN_INFO "bttv%d: detected: %s [card=%d], " + "PCI subsystem ID is %04x:%04x\n", + btv->c.nr,cards[type].name,cards[type].cardnr, + btv->cardid & 0xffff, + (btv->cardid >> 16) & 0xffff); + btv->c.type = cards[type].cardnr; + } else { + /* 404 */ + printk(KERN_INFO "bttv%d: subsystem: %04x:%04x (UNKNOWN)\n", + btv->c.nr, btv->cardid & 0xffff, + (btv->cardid >> 16) & 0xffff); + printk(KERN_DEBUG "please mail id, board name and " + "the correct card= insmod option to kraxel@bytesex.org\n"); + } + } + + /* let the user override the autodetected type */ + if (card[btv->c.nr] < bttv_num_tvcards) + btv->c.type=card[btv->c.nr]; + + /* print which card config we are using */ + printk(KERN_INFO "bttv%d: using: %s [card=%d,%s]\n",btv->c.nr, + bttv_tvcards[btv->c.type].name, btv->c.type, + card[btv->c.nr] < bttv_num_tvcards + ? "insmod option" : "autodetected"); + + /* overwrite gpio stuff ?? */ + if (UNSET == audioall && UNSET == audiomux[0]) + return; + + if (UNSET != audiomux[0]) { + gpiobits = 0; + for (i = 0; i < 5; i++) { + bttv_tvcards[btv->c.type].audiomux[i] = audiomux[i]; + gpiobits |= audiomux[i]; + } + } else { + gpiobits = audioall; + for (i = 0; i < 5; i++) { + bttv_tvcards[btv->c.type].audiomux[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++) { + printk("%s0x%x", i ? "," : "", bttv_tvcards[btv->c.type].audiomux[i]); + } + printk("\n"); +} + +/* + * (most) board specific initialisations goes here + */ + +/* Some Modular Technology cards have an eeprom, but no subsystem ID */ +void identify_by_eeprom(struct bttv *btv, unsigned char eeprom_data[256]) +{ + int type = -1; + + if (0 == strncmp(eeprom_data,"GET MM20xPCTV",13)) + type = BTTV_MODTEC_205; + else if (0 == strncmp(eeprom_data+20,"Picolo",7)) + type = BTTV_EURESYS_PICOLO; + else if (eeprom_data[0] == 0x84 && eeprom_data[2]== 0) + type = BTTV_HAUPPAUGE; /* old bt848 */ + + if (-1 != type) { + btv->c.type = type; + printk("bttv%d: detected by eeprom: %s [card=%d]\n", + btv->c.nr, bttv_tvcards[btv->c.type].name, btv->c.type); + } +} + +static void flyvideo_gpio(struct bttv *btv) +{ + int gpio,has_remote,has_radio,is_capture_only,is_lr90,has_tda9820_tda9821; + int tuner=-1,ttype; + + gpio_inout(0xffffff, 0); + udelay(8); // without this we would see the 0x1800 mask + gpio = gpio_read(); + /* FIXME: must restore OUR_EN ??? */ + + // all cards provide GPIO info, some have an additional eeprom + // LR50: GPIO coding can be found lower right CP1 .. CP9 + // CP9=GPIO23 .. CP1=GPIO15; when OPEN, the corresponding GPIO reads 1. + // GPIO14-12: n.c. + // LR90: GP9=GPIO23 .. GP1=GPIO15 (right above the bt878) + + // lowest 3 bytes are remote control codes (no handshake needed) + // xxxFFF: No remote control chip soldered + // xxxF00(LR26/LR50), xxxFE0(LR90): Remote control chip (LVA001 or CF45) soldered + // Note: Some bits are Audio_Mask ! + + ttype=(gpio&0x0f0000)>>16; + switch(ttype) { + case 0x0: tuner=2; // NTSC, e.g. TPI8NSR11P + break; + case 0x2: tuner=39;// LG NTSC (newer TAPC series) TAPC-H701P + break; + case 0x4: tuner=5; // Philips PAL TPI8PSB02P, TPI8PSB12P, TPI8PSB12D or FI1216, FM1216 + break; + case 0x6: tuner=37; // LG PAL (newer TAPC series) TAPC-G702P + break; + case 0xC: tuner=3; // Philips SECAM(+PAL) FQ1216ME or FI1216MF + break; + default: + printk(KERN_INFO "bttv%d: FlyVideo_gpio: unknown tuner type.\n", btv->c.nr); + } + + has_remote = gpio & 0x800000; + has_radio = gpio & 0x400000; + // unknown 0x200000; + // unknown2 0x100000; + is_capture_only = !(gpio & 0x008000); //GPIO15 + has_tda9820_tda9821 = !(gpio & 0x004000); + is_lr90 = !(gpio & 0x002000); // else LR26/LR50 (LR38/LR51 f. capture only) + // gpio & 0x001000 // output bit for audio routing + + if(is_capture_only) + tuner=4; // No tuner present + + printk(KERN_INFO "bttv%d: FlyVideo Radio=%s RemoteControl=%s Tuner=%d gpio=0x%06x\n", + btv->c.nr, has_radio? "yes":"no ", has_remote? "yes":"no ", tuner, gpio); + printk(KERN_INFO "bttv%d: FlyVideo LR90=%s tda9821/tda9820=%s capture_only=%s\n", + btv->c.nr, is_lr90?"yes":"no ", has_tda9820_tda9821?"yes":"no ", + is_capture_only?"yes":"no "); + + if(tuner!= -1) // only set if known tuner autodetected, else let insmod option through + btv->tuner_type = tuner; + btv->has_radio = has_radio; + + // LR90 Audio Routing is done by 2 hef4052, so Audio_Mask has 4 bits: 0x001c80 + // LR26/LR50 only has 1 hef4052, Audio_Mask 0x000c00 + // Audio options: from tuner, from tda9821/tda9821(mono,stereo,sap), from tda9874, ext., mute + if(has_tda9820_tda9821) btv->audio_hook = lt9415_audio; + //todo: if(has_tda9874) btv->audio_hook = fv2000s_audio; +} + +static int miro_tunermap[] = { 0,6,2,3, 4,5,6,0, 3,0,4,5, 5,2,16,1, + 14,2,17,1, 4,1,4,3, 1,2,16,1, 4,4,4,4 }; +static int miro_fmtuner[] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, + 1,1,1,1, 1,1,1,0, 0,0,0,0, 0,1,0,0 }; + +static void miro_pinnacle_gpio(struct bttv *btv) +{ + int id,msp,gpio; + char *info; + + gpio_inout(0xffffff, 0); + gpio = gpio_read(); + id = ((gpio>>10) & 63) -1; + msp = bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx"); + if (id < 32) { + btv->tuner_type = miro_tunermap[id]; + if (0 == (gpio & 0x20)) { + btv->has_radio = 1; + if (!miro_fmtuner[id]) { + btv->has_matchbox = 1; + btv->mbox_we = (1<<6); + btv->mbox_most = (1<<7); + btv->mbox_clk = (1<<8); + btv->mbox_data = (1<<9); + btv->mbox_mask = (1<<6)|(1<<7)|(1<<8)|(1<<9); + } + } else { + btv->has_radio = 0; + } + if (-1 != msp) { + if (btv->c.type == BTTV_MIRO) + btv->c.type = BTTV_MIROPRO; + if (btv->c.type == BTTV_PINNACLE) + btv->c.type = BTTV_PINNACLEPRO; + } + printk(KERN_INFO + "bttv%d: miro: id=%d tuner=%d radio=%s stereo=%s\n", + btv->c.nr, id+1, btv->tuner_type, + !btv->has_radio ? "no" : + (btv->has_matchbox ? "matchbox" : "fmtuner"), + (-1 == msp) ? "no" : "yes"); + } else { + /* new cards with microtune tuner */ + id = 63 - id; + btv->has_radio = 0; + switch (id) { + case 1: + info = "PAL / mono"; + break; + case 2: + info = "PAL+SECAM / stereo"; + btv->has_radio = 1; + break; + case 3: + info = "NTSC / stereo"; + btv->has_radio = 1; + break; + case 4: + info = "PAL+SECAM / mono"; + break; + case 5: + info = "NTSC / mono"; + break; + case 6: + info = "NTSC / stereo"; + break; + case 7: + info = "PAL / stereo"; + break; + default: + info = "oops: unknown card"; + break; + } + if (-1 != msp) + btv->c.type = BTTV_PINNACLEPRO; + printk(KERN_INFO + "bttv%d: pinnacle/mt: id=%d info=\"%s\" radio=%s\n", + btv->c.nr, id, info, btv->has_radio ? "yes" : "no"); + btv->tuner_type = 33; + btv->pinnacle_id = id; + } +} + +/* GPIO21 L: Buffer aktiv, H: Buffer inaktiv */ +#define LM1882_SYNC_DRIVE 0x200000L + +static void init_ids_eagle(struct bttv *btv) +{ + gpio_inout(0xffffff,0xFFFF37); + gpio_write(0x200020); + + /* flash strobe inverter ?! */ + gpio_write(0x200024); + + /* switch sync drive off */ + gpio_bits(LM1882_SYNC_DRIVE,LM1882_SYNC_DRIVE); + + /* set BT848 muxel to 2 */ + btaor((2)<<5, ~(2<<5), BT848_IFORM); +} + +/* Muxsel helper for the IDS Eagle. + * the eagles does not use the standard muxsel-bits but + * has its own multiplexer */ +static void eagle_muxsel(struct bttv *btv, unsigned int input) +{ + btaor((2)<<5, ~(3<<5), BT848_IFORM); + gpio_bits(3,bttv_tvcards[btv->c.type].muxsel[input&7]); + +#if 0 + /* svhs */ + /* wake chroma ADC */ + btand(~BT848_ADC_C_SLEEP, BT848_ADC); + /* set to YC video */ + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); +#else + /* composite */ + /* set chroma ADC to sleep */ + btor(BT848_ADC_C_SLEEP, BT848_ADC); + /* set to composite video */ + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); +#endif + + /* switch sync drive off */ + gpio_bits(LM1882_SYNC_DRIVE,LM1882_SYNC_DRIVE); +} + +static void gvc1100_muxsel(struct bttv *btv, unsigned int input) +{ + static const int masks[] = {0x30, 0x01, 0x12, 0x23}; + gpio_write(masks[input%4]); +} + +/* LMLBT4x initialization - to allow access to GPIO bits for sensors input and + alarms output + + GPIObit | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + assignment | TI | O3|INx| O2| O1|IN4|IN3|IN2|IN1| | | + + IN - sensor inputs, INx - sensor inputs and TI XORed together + O1,O2,O3 - alarm outputs (relays) + + OUT ENABLE 1 1 0 . 1 1 0 0 . 0 0 0 0 = 0x6C0 + +*/ + +static void init_lmlbt4x(struct bttv *btv) +{ + printk(KERN_DEBUG "LMLBT4x init\n"); + btwrite(0x000000, BT848_GPIO_REG_INP); + gpio_inout(0xffffff, 0x0006C0); + gpio_write(0x000000); +} + +static void sigmaSQ_muxsel(struct bttv *btv, unsigned int input) +{ + unsigned int inmux = input % 8; + gpio_inout( 0xf, 0xf ); + gpio_bits( 0xf, inmux ); +} + +static void sigmaSLC_muxsel(struct bttv *btv, unsigned int input) +{ + unsigned int inmux = input % 4; + gpio_inout( 3<<9, 3<<9 ); + gpio_bits( 3<<9, inmux<<9 ); +} + +/* ----------------------------------------------------------------------- */ + +static void bttv_reset_audio(struct bttv *btv) +{ + /* + * BT878A has a audio-reset register. + * 1. This register is an audio reset function but it is in + * function-0 (video capture) address space. + * 2. It is enough to do this once per power-up of the card. + * 3. There is a typo in the Conexant doc -- it is not at + * 0x5B, but at 0x058. (B is an odd-number, obviously a typo!). + * --//Shrikumar 030609 + */ + if (btv->id != 878) + return; + + if (bttv_debug) + printk("bttv%d: BT878A ARESET\n",btv->c.nr); + btwrite((1<<7), 0x058); + udelay(10); + btwrite( 0, 0x058); +} + +/* initialization part one -- before registering i2c bus */ +void __devinit bttv_init_card1(struct bttv *btv) +{ + switch (btv->c.type) { + case BTTV_HAUPPAUGE: + case BTTV_HAUPPAUGE878: + boot_msp34xx(btv,5); + break; + case BTTV_VOODOOTV_FM: + boot_msp34xx(btv,20); + break; + case BTTV_AVERMEDIA98: + boot_msp34xx(btv,11); + break; + case BTTV_HAUPPAUGEPVR: + pvr_boot(btv); + break; + case BTTV_TWINHAN_DST: + case BTTV_AVDVBT_771: + btv->use_i2c_hw = 1; + break; + } + if (!bttv_tvcards[btv->c.type].has_dvb) + bttv_reset_audio(btv); +} + +/* initialization part two -- after registering i2c bus */ +void __devinit bttv_init_card2(struct bttv *btv) +{ + int tda9887; + btv->tuner_type = -1; + + if (BTTV_UNKNOWN == btv->c.type) { + bttv_readee(btv,eeprom_data,0xa0); + identify_by_eeprom(btv,eeprom_data); + } + + switch (btv->c.type) { + case BTTV_MIRO: + case BTTV_MIROPRO: + case BTTV_PINNACLE: + case BTTV_PINNACLEPRO: + /* miro/pinnacle */ + miro_pinnacle_gpio(btv); + break; + case BTTV_FLYVIDEO_98: + case BTTV_MAXI: + case BTTV_LIFE_FLYKIT: + case BTTV_FLYVIDEO: + case BTTV_TYPHOON_TVIEW: + case BTTV_CHRONOS_VS2: + case BTTV_FLYVIDEO_98FM: + case BTTV_FLYVIDEO2000: + case BTTV_FLYVIDEO98EZ: + case BTTV_CONFERENCETV: + case BTTV_LIFETEC_9415: + flyvideo_gpio(btv); + break; + case BTTV_HAUPPAUGE: + case BTTV_HAUPPAUGE878: + case BTTV_HAUPPAUGEPVR: + /* pick up some config infos from the eeprom */ + bttv_readee(btv,eeprom_data,0xa0); + hauppauge_eeprom(btv); + break; + case BTTV_AVERMEDIA98: + case BTTV_AVPHONE98: + bttv_readee(btv,eeprom_data,0xa0); + avermedia_eeprom(btv); + break; + case BTTV_PXC200: + init_PXC200(btv); + break; + case BTTV_PICOLO_TETRA_CHIP: + picolo_tetra_init(btv); + break; + case BTTV_VHX: + btv->has_radio = 1; + btv->has_matchbox = 1; + btv->mbox_we = 0x20; + btv->mbox_most = 0; + btv->mbox_clk = 0x08; + btv->mbox_data = 0x10; + btv->mbox_mask = 0x38; + break; + case BTTV_VOBIS_BOOSTAR: + case BTTV_TERRATV: + terratec_active_radio_upgrade(btv); + break; + case BTTV_MAGICTVIEW061: + if (btv->cardid == 0x3002144f) { + btv->has_radio=1; + printk("bttv%d: radio detected by subsystem id (CPH05x)\n",btv->c.nr); + } + break; + case BTTV_STB2: + if (btv->cardid == 0x3060121a) { + /* Fix up entry for 3DFX VoodooTV 100, + which is an OEM STB card variant. */ + btv->has_radio=0; + btv->tuner_type=TUNER_TEMIC_NTSC; + } + break; + case BTTV_OSPREY1x0: + case BTTV_OSPREY1x0_848: + case BTTV_OSPREY101_848: + case BTTV_OSPREY1x1: + case BTTV_OSPREY1x1_SVID: + case BTTV_OSPREY2xx: + case BTTV_OSPREY2x0_SVID: + case BTTV_OSPREY2x0: + case BTTV_OSPREY500: + case BTTV_OSPREY540: + case BTTV_OSPREY2000: + bttv_readee(btv,eeprom_data,0xa0); + osprey_eeprom(btv); + break; + case BTTV_IDS_EAGLE: + init_ids_eagle(btv); + break; + case BTTV_MODTEC_205: + bttv_readee(btv,eeprom_data,0xa0); + modtec_eeprom(btv); + break; + case BTTV_LMLBT4: + init_lmlbt4x(btv); + break; + case BTTV_TIBET_CS16: + tibetCS16_init(btv); + break; + case BTTV_KODICOM_4400R: + kodicom4400r_init(btv); + break; + } + + /* pll configuration */ + if (!(btv->id==848 && btv->revision==0x11)) { + /* defaults from card list */ + if (PLL_28 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq=28636363; + btv->pll.pll_crystal=BT848_IFORM_XT0; + } + if (PLL_35 == bttv_tvcards[btv->c.type].pll) { + btv->pll.pll_ifreq=35468950; + btv->pll.pll_crystal=BT848_IFORM_XT1; + } + /* insmod options can override */ + switch (pll[btv->c.nr]) { + case 0: /* none */ + btv->pll.pll_crystal = 0; + btv->pll.pll_ifreq = 0; + btv->pll.pll_ofreq = 0; + break; + case 1: /* 28 MHz */ + case 28: + btv->pll.pll_ifreq = 28636363; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT0; + break; + case 2: /* 35 MHz */ + case 35: + btv->pll.pll_ifreq = 35468950; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal = BT848_IFORM_XT1; + break; + } + } + btv->pll.pll_current = -1; + + bttv_reset_audio(btv); + + /* tuner configuration (from card list / autodetect / insmod option) */ + if (UNSET != bttv_tvcards[btv->c.type].tuner_type) + if(UNSET == btv->tuner_type) + btv->tuner_type = bttv_tvcards[btv->c.type].tuner_type; + if (UNSET != tuner[btv->c.nr]) + btv->tuner_type = tuner[btv->c.nr]; + printk("bttv%d: using tuner=%d\n",btv->c.nr,btv->tuner_type); + if (btv->pinnacle_id != UNSET) + bttv_call_i2c_clients(btv,AUDC_CONFIG_PINNACLE, + &btv->pinnacle_id); + if (btv->tuner_type != UNSET) + bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type); + btv->svhs = bttv_tvcards[btv->c.type].svhs; + if (svhs[btv->c.nr] != UNSET) + btv->svhs = svhs[btv->c.nr]; + if (remote[btv->c.nr] != UNSET) + btv->has_remote = remote[btv->c.nr]; + + if (bttv_tvcards[btv->c.type].has_radio) + btv->has_radio=1; + if (bttv_tvcards[btv->c.type].has_remote) + btv->has_remote=1; + if (bttv_tvcards[btv->c.type].no_gpioirq) + btv->gpioirq=0; + if (bttv_tvcards[btv->c.type].audio_hook) + btv->audio_hook=bttv_tvcards[btv->c.type].audio_hook; + + if (bttv_tvcards[btv->c.type].digital_mode == DIGITAL_MODE_CAMERA) { + /* detect Bt832 chip for quartzsight digital camera */ + if ((bttv_I2CRead(btv, I2C_BT832_ALT1, "Bt832") >=0) || + (bttv_I2CRead(btv, I2C_BT832_ALT2, "Bt832") >=0)) + boot_bt832(btv); + } + + if (!autoload) + return; + + /* try to detect audio/fader chips */ + if (!bttv_tvcards[btv->c.type].no_msp34xx && + bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx") >=0) + request_module("msp3400"); + + if (bttv_tvcards[btv->c.type].msp34xx_alt && + bttv_I2CRead(btv, I2C_MSP3400_ALT, "MSP34xx (alternate address)") >=0) + request_module("msp3400"); + + if (!bttv_tvcards[btv->c.type].no_tda9875 && + bttv_I2CRead(btv, I2C_TDA9875, "TDA9875") >=0) + request_module("tda9875"); + + if (!bttv_tvcards[btv->c.type].no_tda7432 && + bttv_I2CRead(btv, I2C_TDA7432, "TDA7432") >=0) + request_module("tda7432"); + + if (bttv_tvcards[btv->c.type].needs_tvaudio) + request_module("tvaudio"); + + /* tuner modules */ + tda9887 = 0; + if (btv->pinnacle_id != UNSET) + tda9887 = 1; + if (0 == tda9887 && 0 == bttv_tvcards[btv->c.type].has_dvb && + bttv_I2CRead(btv, I2C_TDA9887, "TDA9887") >=0) + tda9887 = 1; + if((btv->tuner_type == TUNER_PHILIPS_FM1216ME_MK3) || + (btv->tuner_type == TUNER_PHILIPS_FM1236_MK3) || + (btv->tuner_type == TUNER_PHILIPS_FM1256_IH3) || + tda9887) + request_module("tda9887"); + if (btv->tuner_type != UNSET) + request_module("tuner"); +} + + +/* ----------------------------------------------------------------------- */ + +static void modtec_eeprom(struct bttv *btv) +{ + if( strncmp(&(eeprom_data[0x1e]),"Temic 4066 FY5",14) ==0) { + btv->tuner_type=TUNER_TEMIC_4066FY5_PAL_I; + printk("bttv%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } else if (strncmp(&(eeprom_data[0x1e]),"Alps TSBB5",10) ==0) { + btv->tuner_type=TUNER_ALPS_TSBB5_PAL_I; + printk("bttv%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } else if (strncmp(&(eeprom_data[0x1e]),"Philips FM1246",14) ==0) { + btv->tuner_type=TUNER_PHILIPS_NTSC; + printk("bttv%d: Modtec: Tuner autodetected by eeprom: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } else { + printk("bttv%d: Modtec: Unknown TunerString: %s\n", + btv->c.nr,&eeprom_data[0x1e]); + } +} + +static void __devinit hauppauge_eeprom(struct bttv *btv) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + btv->tuner_type = tv.tuner_type; + btv->has_radio = tv.has_radio; +} + +static int terratec_active_radio_upgrade(struct bttv *btv) +{ + int freq; + + btv->has_radio = 1; + btv->has_matchbox = 1; + btv->mbox_we = 0x10; + btv->mbox_most = 0x20; + btv->mbox_clk = 0x08; + btv->mbox_data = 0x04; + btv->mbox_mask = 0x3c; + + btv->mbox_iow = 1 << 8; + btv->mbox_ior = 1 << 9; + btv->mbox_csel = 1 << 10; + + freq=88000/62.5; + tea5757_write(btv, 5 * freq + 0x358); // write 0x1ed8 + if (0x1ed8 == tea5757_read(btv)) { + printk("bttv%d: Terratec Active Radio Upgrade found.\n", + btv->c.nr); + btv->has_radio = 1; + btv->has_matchbox = 1; + } else { + btv->has_radio = 0; + btv->has_matchbox = 0; + } + return 0; +} + + +/* ----------------------------------------------------------------------- */ + +/* + * minimal bootstrap for the WinTV/PVR -- upload altera firmware. + * + * The hcwamc.rbf firmware file is on the Hauppauge driver CD. Have + * a look at Pvr/pvr45xxx.EXE (self-extracting zip archive, can be + * unpacked with unzip). + */ +#define PVR_GPIO_DELAY 10 + +#define BTTV_ALT_DATA 0x000001 +#define BTTV_ALT_DCLK 0x100000 +#define BTTV_ALT_NCONFIG 0x800000 + +static int __devinit pvr_altera_load(struct bttv *btv, u8 *micro, u32 microlen) +{ + u32 n; + u8 bits; + int i; + + gpio_inout(0xffffff,BTTV_ALT_DATA|BTTV_ALT_DCLK|BTTV_ALT_NCONFIG); + gpio_write(0); + udelay(PVR_GPIO_DELAY); + + gpio_write(BTTV_ALT_NCONFIG); + udelay(PVR_GPIO_DELAY); + + for (n = 0; n < microlen; n++) { + bits = micro[n]; + for ( i = 0 ; i < 8 ; i++ ) { + gpio_bits(BTTV_ALT_DCLK,0); + if (bits & 0x01) + gpio_bits(BTTV_ALT_DATA,BTTV_ALT_DATA); + else + gpio_bits(BTTV_ALT_DATA,0); + gpio_bits(BTTV_ALT_DCLK,BTTV_ALT_DCLK); + bits >>= 1; + } + } + gpio_bits(BTTV_ALT_DCLK,0); + udelay(PVR_GPIO_DELAY); + + /* begin Altera init loop (Not necessary,but doesn't hurt) */ + for (i = 0 ; i < 30 ; i++) { + gpio_bits(BTTV_ALT_DCLK,0); + gpio_bits(BTTV_ALT_DCLK,BTTV_ALT_DCLK); + } + gpio_bits(BTTV_ALT_DCLK,0); + return 0; +} + +static int __devinit pvr_boot(struct bttv *btv) +{ + const struct firmware *fw_entry; + int rc; + + rc = request_firmware(&fw_entry, "hcwamc.rbf", &btv->c.pci->dev); + if (rc != 0) { + printk(KERN_WARNING "bttv%d: no altera firmware [via hotplug]\n", + btv->c.nr); + return rc; + } + rc = pvr_altera_load(btv, fw_entry->data, fw_entry->size); + printk(KERN_INFO "bttv%d: altera firmware upload %s\n", + btv->c.nr, (rc < 0) ? "failed" : "ok"); + release_firmware(fw_entry); + return rc; +} + +/* ----------------------------------------------------------------------- */ +/* some osprey specific stuff */ + +static void __devinit osprey_eeprom(struct bttv *btv) +{ + int i = 0; + unsigned char *ee = eeprom_data; + unsigned long serial = 0; + + if (btv->c.type == 0) { + /* 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++) + checksum += ee[i]; + if (checksum != ee[21]) + return; + btv->c.type = BTTV_OSPREY1x0_848; + for (i = 12; i < 21; i++) + serial *= 10, serial += ee[i] - '0'; + } + } else { + unsigned short type; + int offset = 4*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]) && + ((checksum & 0x0FF) == ee[offset+15])) { + break; + } + } + + if (offset >= 8*16) + return; + + /* found a valid descriptor */ + type = (ee[offset+4]<<8) | (ee[offset+5]); + + switch(type) { + + /* 848 based */ + case 0x0004: + btv->c.type = BTTV_OSPREY1x0_848; + break; + case 0x0005: + btv->c.type = BTTV_OSPREY101_848; + break; + + /* 878 based */ + case 0x0012: + case 0x0013: + btv->c.type = BTTV_OSPREY1x0; + break; + case 0x0014: + case 0x0015: + btv->c.type = BTTV_OSPREY1x1; + break; + case 0x0016: + case 0x0017: + case 0x0020: + btv->c.type = BTTV_OSPREY1x1_SVID; + break; + case 0x0018: + case 0x0019: + case 0x001E: + case 0x001F: + btv->c.type = BTTV_OSPREY2xx; + break; + case 0x001A: + case 0x001B: + btv->c.type = BTTV_OSPREY2x0_SVID; + break; + case 0x0040: + btv->c.type = BTTV_OSPREY500; + break; + case 0x0050: + case 0x0056: + btv->c.type = BTTV_OSPREY540; + /* bttv_osprey_540_init(btv); */ + break; + case 0x0060: + case 0x0070: + btv->c.type = BTTV_OSPREY2x0; + //enable output on select control lines + gpio_inout(0xffffff,0x000303); + break; + default: + /* unknown...leave generic, but get serial # */ + break; + } + serial = (ee[offset+6] << 24) + | (ee[offset+7] << 16) + | (ee[offset+8] << 8) + | (ee[offset+9]); + } + + printk(KERN_INFO "bttv%d: osprey eeprom: card=%d name=%s serial=%ld\n", + btv->c.nr, btv->c.type, bttv_tvcards[btv->c.type].name,serial); +} + +/* ----------------------------------------------------------------------- */ +/* AVermedia specific stuff, from bktr_card.c */ + +static int tuner_0_table[] = { + TUNER_PHILIPS_NTSC, TUNER_PHILIPS_PAL /* PAL-BG*/, + TUNER_PHILIPS_PAL, TUNER_PHILIPS_PAL /* PAL-I*/, + TUNER_PHILIPS_PAL, TUNER_PHILIPS_PAL, + TUNER_PHILIPS_SECAM, TUNER_PHILIPS_SECAM, + TUNER_PHILIPS_SECAM, TUNER_PHILIPS_PAL, + TUNER_PHILIPS_FM1216ME_MK3 }; +#if 0 +int tuner_0_fm_table[] = { + PHILIPS_FR1236_NTSC, PHILIPS_FR1216_PAL, + PHILIPS_FR1216_PAL, PHILIPS_FR1216_PAL, + PHILIPS_FR1216_PAL, PHILIPS_FR1216_PAL, + PHILIPS_FR1236_SECAM, PHILIPS_FR1236_SECAM, + PHILIPS_FR1236_SECAM, PHILIPS_FR1216_PAL}; +#endif + +static int tuner_1_table[] = { + TUNER_TEMIC_NTSC, TUNER_TEMIC_PAL, + TUNER_TEMIC_PAL, TUNER_TEMIC_PAL, + TUNER_TEMIC_PAL, TUNER_TEMIC_PAL, + TUNER_TEMIC_4012FY5, TUNER_TEMIC_4012FY5, //TUNER_TEMIC_SECAM + TUNER_TEMIC_4012FY5, TUNER_TEMIC_PAL}; + +static void __devinit avermedia_eeprom(struct bttv *btv) +{ + int tuner_make,tuner_tv_fm,tuner_format,tuner=0; + + tuner_make = (eeprom_data[0x41] & 0x7); + tuner_tv_fm = (eeprom_data[0x41] & 0x18) >> 3; + tuner_format = (eeprom_data[0x42] & 0xf0) >> 4; + btv->has_remote = (eeprom_data[0x42] & 0x01); + + if (tuner_make == 0 || tuner_make == 2) + if(tuner_format <=0x0a) + tuner = tuner_0_table[tuner_format]; + if (tuner_make == 1) + if(tuner_format <=9) + tuner = tuner_1_table[tuner_format]; + + if (tuner_make == 4) + if(tuner_format == 0x09) + tuner = TUNER_LG_NTSC_NEW_TAPC; // TAPC-G702P + + printk(KERN_INFO "bttv%d: Avermedia eeprom[0x%02x%02x]: tuner=", + btv->c.nr,eeprom_data[0x41],eeprom_data[0x42]); + if(tuner) { + btv->tuner_type=tuner; + printk("%d",tuner); + } else + printk("Unknown type"); + printk(" radio:%s remote control:%s\n", + tuner_tv_fm ? "yes" : "no", + btv->has_remote ? "yes" : "no"); +} + +/* used on Voodoo TV/FM (Voodoo 200), S0 wired to 0x10000 */ +void bttv_tda9880_setnorm(struct bttv *btv, int norm) +{ + // fix up our card entry + if(norm==VIDEO_MODE_NTSC) { + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[0]=0x957fff; + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[4]=0x957fff; + dprintk("bttv_tda9880_setnorm to NTSC\n"); + } + else { + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[0]=0x947fff; + bttv_tvcards[BTTV_VOODOOTV_FM].audiomux[4]=0x947fff; + dprintk("bttv_tda9880_setnorm to PAL\n"); + } + // set GPIO according + gpio_bits(bttv_tvcards[btv->c.type].gpiomask, + bttv_tvcards[btv->c.type].audiomux[btv->audio]); +} + + +/* + * reset/enable the MSP on some Hauppauge cards + * Thanks to Kyösti Mälkki (kmalkki@cc.hut.fi)! + * + * Hauppauge: pin 5 + * Voodoo: pin 20 + */ +static void __devinit boot_msp34xx(struct bttv *btv, int pin) +{ + int mask = (1 << pin); + + gpio_inout(mask,mask); + gpio_bits(mask,0); + udelay(2500); + gpio_bits(mask,mask); + + if (bttv_gpio) + bttv_gpio_tracking(btv,"msp34xx"); + if (bttv_verbose) + printk(KERN_INFO "bttv%d: Hauppauge/Voodoo msp34xx: reset line " + "init [%d]\n", btv->c.nr, pin); +} + +static void __devinit boot_bt832(struct bttv *btv) +{ +#if 0 /* not working yet */ + int resetbit=0; + + switch (btv->c.type) { + case BTTV_PXELVWPLTVPAK: + resetbit = 0x400000; + break; + case BTTV_MODTEC_205: + resetbit = 1<<9; + break; + default: + BUG(); + } + + request_module("bt832"); + bttv_call_i2c_clients(btv, BT832_HEXDUMP, NULL); + + printk("bttv%d: Reset Bt832 [line=0x%x]\n",btv->c.nr,resetbit); + gpio_write(0); + gpio_inout(resetbit, resetbit); + udelay(5); + gpio_bits(resetbit, resetbit); + udelay(5); + gpio_bits(resetbit, 0); + udelay(5); + + // bt832 on pixelview changes from i2c 0x8a to 0x88 after + // being reset as above. So we must follow by this: + bttv_call_i2c_clients(btv, BT832_REATTACH, NULL); +#endif +} + +/* ----------------------------------------------------------------------- */ +/* Imagenation L-Model PXC200 Framegrabber */ +/* This is basically the same procedure as + * used by Alessandro Rubini in his pxc200 + * driver, but using BTTV functions */ + +static void __devinit init_PXC200(struct bttv *btv) +{ + static int vals[] __devinitdata = { 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0d, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x00 }; + unsigned int i; + int tmp; + u32 val; + + /* Initialise GPIO-connevted stuff */ + gpio_inout(0xffffff, (1<<13)); + gpio_write(0); + udelay(3); + gpio_write(1<<13); + /* GPIO inputs are pulled up, so no need to drive + * reset pin any longer */ + gpio_bits(0xffffff, 0); + if (bttv_gpio) + bttv_gpio_tracking(btv,"pxc200"); + + /* we could/should try and reset/control the AD pots? but + right now we simply turned off the crushing. Without + this the AGC drifts drifts + remember the EN is reverse logic --> + setting BT848_ADC_AGC_EN disable the AGC + tboult@eecs.lehigh.edu + */ + + btwrite(BT848_ADC_RESERVED|BT848_ADC_AGC_EN, BT848_ADC); + + /* Initialise MAX517 DAC */ + printk(KERN_INFO "Setting DAC reference voltage level ...\n"); + bttv_I2CWrite(btv,0x5E,0,0x80,1); + + /* Initialise 12C508 PIC */ + /* The I2CWrite and I2CRead commmands are actually to the + * same chips - but the R/W bit is included in the address + * argument so the numbers are different */ + + + printk(KERN_INFO "Initialising 12C508 PIC chip ...\n"); + + /* First of all, enable the clock line. This is used in the PXC200-F */ + val = btread(BT848_GPIO_DMA_CTL); + val |= BT848_GPIO_DMA_CTL_GPCLKMODE; + btwrite(val, BT848_GPIO_DMA_CTL); + + /* Then, push to 0 the reset pin long enough to reset the * + * device same as above for the reset line, but not the same + * value sent to the GPIO-connected stuff + * which one is the good one? */ + gpio_inout(0xffffff,(1<<2)); + gpio_write(0); + udelay(10); + gpio_write(1<<2); + + for (i = 0; i < ARRAY_SIZE(vals); i++) { + tmp=bttv_I2CWrite(btv,0x1E,0,vals[i],1); + if (tmp != -1) { + printk(KERN_INFO + "I2C Write(%2.2x) = %i\nI2C Read () = %2.2x\n\n", + vals[i],tmp,bttv_I2CRead(btv,0x1F,NULL)); + } + } + + printk(KERN_INFO "PXC200 Initialised.\n"); +} + + +/* ----------------------------------------------------------------------- */ +/* Miro Pro radio stuff -- the tea5757 is connected to some GPIO ports */ +/* + * Copyright (c) 1999 Csaba Halasz <qgehali@uni-miskolc.hu> + * This code is placed under the terms of the GNU General Public License + * + * Brutally hacked by Dan Sheridan <dan.sheridan@contact.org.uk> djs52 8/3/00 + */ + +static void bus_low(struct bttv *btv, int bit) +{ + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + + gpio_bits(bit,0); + udelay(5); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + } +} + +static void bus_high(struct bttv *btv, int bit) +{ + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + + gpio_bits(bit,bit); + udelay(5); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + } +} + +static int bus_in(struct bttv *btv, int bit) +{ + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + + gpio_bits(btv->mbox_iow | btv->mbox_csel, 0); + udelay(5); + } + return gpio_read() & (bit); +} + +/* TEA5757 register bits */ +#define TEA_FREQ 0:14 +#define TEA_BUFFER 15:15 + +#define TEA_SIGNAL_STRENGTH 16:17 + +#define TEA_PORT1 18:18 +#define TEA_PORT0 19:19 + +#define TEA_BAND 20:21 +#define TEA_BAND_FM 0 +#define TEA_BAND_MW 1 +#define TEA_BAND_LW 2 +#define TEA_BAND_SW 3 + +#define TEA_MONO 22:22 +#define TEA_ALLOW_STEREO 0 +#define TEA_FORCE_MONO 1 + +#define TEA_SEARCH_DIRECTION 23:23 +#define TEA_SEARCH_DOWN 0 +#define TEA_SEARCH_UP 1 + +#define TEA_STATUS 24:24 +#define TEA_STATUS_TUNED 0 +#define TEA_STATUS_SEARCHING 1 + +/* Low-level stuff */ +static int tea5757_read(struct bttv *btv) +{ + unsigned long timeout; + int value = 0; + int i; + + /* better safe than sorry */ + gpio_inout(btv->mbox_mask, btv->mbox_clk | btv->mbox_we); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + + if (bttv_gpio) + bttv_gpio_tracking(btv,"tea5757 read"); + + bus_low(btv,btv->mbox_we); + bus_low(btv,btv->mbox_clk); + + udelay(10); + timeout= jiffies + HZ; + + // wait for DATA line to go low; error if it doesn't + while (bus_in(btv,btv->mbox_data) && time_before(jiffies, timeout)) + schedule(); + if (bus_in(btv,btv->mbox_data)) { + printk(KERN_WARNING "bttv%d: tea5757: read timeout\n",btv->c.nr); + return -1; + } + + dprintk("bttv%d: tea5757:",btv->c.nr); + for(i = 0; i < 24; i++) + { + udelay(5); + bus_high(btv,btv->mbox_clk); + udelay(5); + dprintk("%c",(bus_in(btv,btv->mbox_most) == 0)?'T':'-'); + bus_low(btv,btv->mbox_clk); + value <<= 1; + value |= (bus_in(btv,btv->mbox_data) == 0)?0:1; /* MSB first */ + dprintk("%c", (bus_in(btv,btv->mbox_most) == 0)?'S':'M'); + } + dprintk("\nbttv%d: tea5757: read 0x%X\n", btv->c.nr, value); + return value; +} + +static int tea5757_write(struct bttv *btv, int value) +{ + int i; + int reg = value; + + gpio_inout(btv->mbox_mask, btv->mbox_clk | btv->mbox_we | btv->mbox_data); + + if (btv->mbox_ior) { + gpio_bits(btv->mbox_ior | btv->mbox_iow | btv->mbox_csel, + btv->mbox_ior | btv->mbox_iow | btv->mbox_csel); + udelay(5); + } + if (bttv_gpio) + bttv_gpio_tracking(btv,"tea5757 write"); + + 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++) + { + if (reg & 0x1000000) + bus_high(btv,btv->mbox_data); + else + bus_low(btv,btv->mbox_data); + reg <<= 1; + bus_high(btv,btv->mbox_clk); + udelay(10); + bus_low(btv,btv->mbox_clk); + udelay(10); + } + bus_low(btv,btv->mbox_we); /* unmute !!! */ + return 0; +} + +void tea5757_set_freq(struct bttv *btv, unsigned short freq) +{ + dprintk("tea5757_set_freq %d\n",freq); + tea5757_write(btv, 5 * freq + 0x358); /* add 10.7MHz (see docs) */ +#if 0 + /* breaks Miro PCTV */ + value = tea5757_read(btv); + dprintk("bttv%d: tea5757 readback=0x%x\n",btv->c.nr,value); +#endif +} + + +/* ----------------------------------------------------------------------- */ +/* winview */ + +void winview_audio(struct bttv *btv, struct video_audio *v, int set) +{ + /* PT2254A programming Jon Tombs, jon@gte.esi.us.es */ + int bits_out, loops, vol, data; + + if (!set) { + /* Fixed by Leandro Lucarella <luca@linuxmendoza.org.ar (07/31/01) */ + v->flags |= VIDEO_AUDIO_VOLUME; + return; + } + + /* 32 levels logarithmic */ + vol = 32 - ((v->volume>>11)); + /* units */ + bits_out = (PT2254_DBS_IN_2>>(vol%5)); + /* tens */ + bits_out |= (PT2254_DBS_IN_10>>(vol/5)); + bits_out |= PT2254_L_CHANNEL | PT2254_R_CHANNEL; + data = gpio_read(); + data &= ~(WINVIEW_PT2254_CLK| WINVIEW_PT2254_DATA| + WINVIEW_PT2254_STROBE); + for (loops = 17; loops >= 0 ; loops--) { + if (bits_out & (1<<loops)) + data |= WINVIEW_PT2254_DATA; + else + data &= ~WINVIEW_PT2254_DATA; + gpio_write(data); + udelay(5); + data |= WINVIEW_PT2254_CLK; + gpio_write(data); + udelay(5); + data &= ~WINVIEW_PT2254_CLK; + gpio_write(data); + } + data |= WINVIEW_PT2254_STROBE; + data &= ~WINVIEW_PT2254_DATA; + gpio_write(data); + udelay(10); + data &= ~WINVIEW_PT2254_STROBE; + gpio_write(data); +} + +/* ----------------------------------------------------------------------- */ +/* mono/stereo control for various cards (which don't use i2c chips but */ +/* connect something to the GPIO pins */ + +static void +gvbctv3pci_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int con = 0; + + if (set) { + gpio_inout(0x300, 0x300); + if (v->mode & VIDEO_SOUND_LANG1) + con = 0x000; + if (v->mode & VIDEO_SOUND_LANG2) + con = 0x300; + if (v->mode & VIDEO_SOUND_STEREO) + con = 0x200; +// if (v->mode & VIDEO_SOUND_MONO) +// con = 0x100; + gpio_bits(0x300, con); + } else { + v->mode = VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +static void +gvbctv5pci_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int val, con; + +#if BTTV_VERSION_CODE > KERNEL_VERSION(0,8,0) + if (btv->radio_user) + return; +#else + if (btv->radio) + return; +#endif + + val = gpio_read(); + if (set) { + con = 0x000; + if (v->mode & VIDEO_SOUND_LANG2) { + if (v->mode & VIDEO_SOUND_LANG1) { + /* LANG1 + LANG2 */ + con = 0x100; + } + else { + /* LANG2 */ + con = 0x300; + } + } + if (con != (val & 0x300)) { + gpio_bits(0x300, con); + if (bttv_gpio) + bttv_gpio_tracking(btv,"gvbctv5pci"); + } + } else { + switch (val & 0x70) { + case 0x10: + v->mode = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + break; + case 0x30: + v->mode = VIDEO_SOUND_LANG2; + break; + case 0x50: + v->mode = VIDEO_SOUND_LANG1; + break; + case 0x60: + v->mode = VIDEO_SOUND_STEREO; + break; + case 0x70: + v->mode = VIDEO_SOUND_MONO; + break; + default: + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } + } +} + +/* + * Mario Medina Nussbaum <medisoft@alohabbs.org.mx> + * I discover that on BT848_GPIO_DATA address a byte 0xcce enable stereo, + * 0xdde enables mono and 0xccd enables sap + * + * Petr Vandrovec <VANDROVE@vc.cvut.cz> + * P.S.: At least mask in line above is wrong - GPIO pins 3,2 select + * input/output sound connection, so both must be set for output mode. + * + * Looks like it's needed only for the "tvphone", the "tvphone 98" + * handles this with a tda9840 + * + */ +static void +avermedia_tvphone_audio(struct bttv *btv, struct video_audio *v, int set) +{ + int val = 0; + + if (set) { + if (v->mode & VIDEO_SOUND_LANG2) /* SAP */ + val = 0x02; + if (v->mode & VIDEO_SOUND_STEREO) + val = 0x01; + if (val) { + gpio_bits(0x03,val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"avermedia"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1; + return; + } +} + +static void +avermedia_tv_stereo_audio(struct bttv *btv, struct video_audio *v, int set) +{ + int val = 0; + + if (set) { + if (v->mode & VIDEO_SOUND_LANG2) /* SAP */ + val = 0x01; + if (v->mode & VIDEO_SOUND_STEREO) /* STEREO */ + val = 0x02; + btaor(val, ~0x03, BT848_GPIO_DATA); + if (bttv_gpio) + bttv_gpio_tracking(btv,"avermedia"); + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + return; + } +} + +/* Lifetec 9415 handling */ +static void +lt9415_audio(struct bttv *btv, struct video_audio *v, int set) +{ + int val = 0; + + if (gpio_read() & 0x4000) { + v->mode = VIDEO_SOUND_MONO; + return; + } + + if (set) { + if (v->mode & VIDEO_SOUND_LANG2) /* A2 SAP */ + val = 0x0080; + if (v->mode & VIDEO_SOUND_STEREO) /* A2 stereo */ + val = 0x0880; + if ((v->mode & VIDEO_SOUND_LANG1) || + (v->mode & VIDEO_SOUND_MONO)) + val = 0; + gpio_bits(0x0880, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"lt9415"); + } else { + /* autodetect doesn't work with this card :-( */ + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + return; + } +} + +// TDA9821 on TerraTV+ Bt848, Bt878 +static void +terratv_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int con = 0; + + if (set) { + gpio_inout(0x180000,0x180000); + if (v->mode & VIDEO_SOUND_LANG2) + con = 0x080000; + if (v->mode & VIDEO_SOUND_STEREO) + con = 0x180000; + gpio_bits(0x180000, con); + if (bttv_gpio) + bttv_gpio_tracking(btv,"terratv"); + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +static void +winfast2000_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned long val = 0; + + if (set) { + /*btor (0xc32000, BT848_GPIO_OUT_EN);*/ + if (v->mode & VIDEO_SOUND_MONO) /* Mono */ + val = 0x420000; + if (v->mode & VIDEO_SOUND_LANG1) /* Mono */ + val = 0x420000; + if (v->mode & VIDEO_SOUND_LANG2) /* SAP */ + val = 0x410000; + if (v->mode & VIDEO_SOUND_STEREO) /* Stereo */ + val = 0x020000; + if (val) { + gpio_bits(0x430000, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"winfast2000"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * Dariusz Kowalewski <darekk@automex.pl> + * sound control for Prolink PV-BT878P+9B (PixelView PlayTV Pro FM+NICAM + * revision 9B has on-board TDA9874A sound decoder). + * + * Note: There are card variants without tda9874a. Forcing the "stereo sound route" + * will mute this cards. + */ +static void +pvbt878p9b_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int val = 0; + +#if BTTV_VERSION_CODE > KERNEL_VERSION(0,8,0) + if (btv->radio_user) + return; +#else + if (btv->radio) + return; +#endif + + if (set) { + if (v->mode & VIDEO_SOUND_MONO) { + val = 0x01; + } + if ((v->mode & (VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2)) + || (v->mode & VIDEO_SOUND_STEREO)) { + val = 0x02; + } + if (val) { + gpio_bits(0x03,val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"pvbt878p9b"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * Dariusz Kowalewski <darekk@automex.pl> + * sound control for FlyVideo 2000S (with tda9874 decoder) + * based on pvbt878p9b_audio() - this is not tested, please fix!!! + */ +static void +fv2000s_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int val = 0xffff; + +#if BTTV_VERSION_CODE > KERNEL_VERSION(0,8,0) + if (btv->radio_user) + return; +#else + if (btv->radio) + return; +#endif + if (set) { + if (v->mode & VIDEO_SOUND_MONO) { + val = 0x0000; + } + if ((v->mode & (VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2)) + || (v->mode & VIDEO_SOUND_STEREO)) { + val = 0x1080; //-dk-???: 0x0880, 0x0080, 0x1800 ... + } + if (val != 0xffff) { + gpio_bits(0x1800, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"fv2000s"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * sound control for Canopus WinDVR PCI + * Masaki Suzuki <masaki@btree.org> + */ +static void +windvr_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned long val = 0; + + if (set) { + if (v->mode & VIDEO_SOUND_MONO) + val = 0x040000; + if (v->mode & VIDEO_SOUND_LANG1) + val = 0; + if (v->mode & VIDEO_SOUND_LANG2) + val = 0x100000; + if (v->mode & VIDEO_SOUND_STEREO) + val = 0; + if (val) { + gpio_bits(0x140000, val); + if (bttv_gpio) + bttv_gpio_tracking(btv,"windvr"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* + * sound control for AD-TVK503 + * Hiroshi Takekawa <sian@big.or.jp> + */ +static void +adtvk503_audio(struct bttv *btv, struct video_audio *v, int set) +{ + unsigned int con = 0xffffff; + + //btaor(0x1e0000, ~0x1e0000, BT848_GPIO_OUT_EN); + + if (set) { + //btor(***, BT848_GPIO_OUT_EN); + if (v->mode & VIDEO_SOUND_LANG1) + con = 0x00000000; + if (v->mode & VIDEO_SOUND_LANG2) + con = 0x00180000; + if (v->mode & VIDEO_SOUND_STEREO) + con = 0x00000000; + if (v->mode & VIDEO_SOUND_MONO) + con = 0x00060000; + if (con != 0xffffff) { + gpio_bits(0x1e0000,con); + if (bttv_gpio) + bttv_gpio_tracking(btv, "adtvk503"); + } + } else { + v->mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +} + +/* RemoteVision MX (rv605) muxsel helper [Miguel Freitas] + * + * This is needed because rv605 don't use a normal multiplex, but a crosspoint + * switch instead (CD22M3494E). This IC can have multiple active connections + * between Xn (input) and Yn (output) pins. We need to clear any existing + * connection prior to establish a new one, pulsing the STROBE pin. + * + * The board hardwire Y0 (xpoint) to MUX1 and MUXOUT to Yin. + * GPIO pins are wired as: + * GPIO[0:3] - AX[0:3] (xpoint) - P1[0:3] (microcontroler) + * GPIO[4:6] - AY[0:2] (xpoint) - P1[4:6] (microcontroler) + * GPIO[7] - DATA (xpoint) - P1[7] (microcontroler) + * GPIO[8] - - P3[5] (microcontroler) + * GPIO[9] - RESET (xpoint) - P3[6] (microcontroler) + * GPIO[10] - STROBE (xpoint) - P3[7] (microcontroler) + * GPINTR - - P3[4] (microcontroler) + * + * The microcontroler is a 80C32 like. It should be possible to change xpoint + * configuration either directly (as we are doing) or using the microcontroler + * which is also wired to I2C interface. I have no further info on the + * microcontroler features, one would need to disassembly the firmware. + * note: the vendor refused to give any information on this product, all + * that stuff was found using a multimeter! :) + */ +static void rv605_muxsel(struct bttv *btv, unsigned int input) +{ + /* reset all conections */ + gpio_bits(0x200,0x200); + mdelay(1); + gpio_bits(0x200,0x000); + mdelay(1); + + /* create a new conection */ + gpio_bits(0x480,0x080); + gpio_bits(0x480,0x480); + mdelay(1); + gpio_bits(0x480,0x080); + mdelay(1); +} + +/* Tibet Systems 'Progress DVR' CS16 muxsel helper [Chris Fanning] + * + * The CS16 (available on eBay cheap) is a PCI board with four Fusion + * 878A chips, a PCI bridge, an Atmel microcontroller, four sync seperator + * chips, ten eight input analog multiplexors, a not chip and a few + * other components. + * + * 16 inputs on a secondary bracket are provided and can be selected + * from each of the four capture chips. Two of the eight input + * multiplexors are used to select from any of the 16 input signals. + * + * Unsupported hardware capabilities: + * . A video output monitor on the secondary bracket can be selected from + * one of the 878A chips. + * . Another passthrough but I haven't spent any time investigating it. + * . Digital I/O (logic level connected to GPIO) is available from an + * onboard header. + * + * The on chip input mux should always be set to 2. + * GPIO[16:19] - Video input selection + * GPIO[0:3] - Video output monitor select (only available from one 878A) + * GPIO[?:?] - Digital I/O. + * + * There is an ATMEL microcontroller with an 8031 core on board. I have not + * determined what function (if any) it provides. With the microcontroller + * and sync seperator chips a guess is that it might have to do with video + * switching and maybe some digital I/O. + */ +static void tibetCS16_muxsel(struct bttv *btv, unsigned int input) +{ + /* video mux */ + gpio_bits(0x0f0000, input << 16); +} + +static void tibetCS16_init(struct bttv *btv) +{ + /* enable gpio bits, mask obtained via btSpy */ + gpio_inout(0xffffff, 0x0f7fff); + gpio_write(0x0f7fff); +} + +/* + * The following routines for the Kodicom-4400r get a little mind-twisting. + * There is a "master" controller and three "slave" controllers, together + * an analog switch which connects any of 16 cameras to any of the BT87A's. + * The analog switch is controlled by the "master", but the detection order + * of the four BT878A chips is in an order which I just don't understand. + * The "master" is actually the second controller to be detected. The + * logic on the board uses logical numbers for the 4 controlers, but + * those numbers are different from the detection sequence. When working + * with the analog switch, we need to "map" from the detection sequence + * over to the board's logical controller number. This mapping sequence + * is {3, 0, 2, 1}, i.e. the first controller to be detected is logical + * unit 3, the second (which is the master) is logical unit 0, etc. + * We need to maintain the status of the analog switch (which of the 16 + * cameras is connected to which of the 4 controllers). Rather than + * add to the bttv structure for this, we use the data reserved for + * the mbox (unused for this card type). + */ + +/* + * First a routine to set the analog switch, which controls which camera + * is routed to which controller. The switch comprises an X-address + * (gpio bits 0-3, representing the camera, ranging from 0-15), and a + * Y-address (gpio bits 4-6, representing the controller, ranging from 0-3). + * A data value (gpio bit 7) of '1' enables the switch, and '0' disables + * the switch. A STROBE bit (gpio bit 8) latches the data value into the + * specified address. The idea is to set the address and data, then bring + * STROBE high, and finally bring STROBE back to low. + */ +static void kodicom4400r_write(struct bttv *btv, + unsigned char xaddr, + unsigned char yaddr, + unsigned char data) { + unsigned int udata; + + udata = (data << 7) | ((yaddr&3) << 4) | (xaddr&0xf); + gpio_bits(0x1ff, udata); /* write ADDR and DAT */ + gpio_bits(0x1ff, udata | (1 << 8)); /* strobe high */ + gpio_bits(0x1ff, udata); /* strobe low */ +} + +/* + * Next the mux select. Both the "master" and "slave" 'cards' (controllers) + * use this routine. The routine finds the "master" for the card, maps + * the controller number from the detected position over to the logical + * number, writes the appropriate data to the analog switch, and housekeeps + * the local copy of the switch information. The parameter 'input' is the + * requested camera number (0 - 15). + */ +static void kodicom4400r_muxsel(struct bttv *btv, unsigned int input) +{ + char *sw_status; + int xaddr, yaddr; + struct bttv *mctlr; + static unsigned char map[4] = {3, 0, 2, 1}; + + mctlr = master[btv->c.nr]; + if (mctlr == NULL) { /* ignore if master not yet detected */ + return; + } + yaddr = (btv->c.nr - mctlr->c.nr + 1) & 3; /* the '&' is for safety */ + yaddr = map[yaddr]; + sw_status = (char *)(&mctlr->mbox_we); + xaddr = input & 0xf; + /* Check if the controller/camera pair has changed, else ignore */ + if (sw_status[yaddr] != xaddr) + { + /* "open" the old switch, "close" the new one, save the new */ + kodicom4400r_write(mctlr, sw_status[yaddr], yaddr, 0); + sw_status[yaddr] = xaddr; + kodicom4400r_write(mctlr, xaddr, yaddr, 1); + } +} + +/* + * During initialisation, we need to reset the analog switch. We + * also preset the switch to map the 4 connectors on the card to the + * *user's* (see above description of kodicom4400r_muxsel) channels + * 0 through 3 + */ +static void kodicom4400r_init(struct bttv *btv) +{ + char *sw_status = (char *)(&btv->mbox_we); + int ix; + + gpio_inout(0x0003ff, 0x0003ff); + gpio_write(1 << 9); /* reset MUX */ + gpio_write(0); + /* Preset camera 0 to the 4 controllers */ + for (ix=0; ix<4; ix++) { + sw_status[ix] = ix; + kodicom4400r_write(btv, ix, ix, 1); + } + /* + * Since this is the "master", we need to set up the + * other three controller chips' pointers to this structure + * for later use in the muxsel routine. + */ + if ((btv->c.nr<1) || (btv->c.nr>BTTV_MAX-3)) + return; + master[btv->c.nr-1] = btv; + master[btv->c.nr] = btv; + master[btv->c.nr+1] = btv; + master[btv->c.nr+2] = btv; +} + +// The Grandtec X-Guard framegrabber card uses two Dual 4-channel +// video multiplexers to provide up to 16 video inputs. These +// multiplexers are controlled by the lower 8 GPIO pins of the +// bt878. The multiplexers probably Pericom PI5V331Q or similar. + +// xxx0 is pin xxx of multiplexer U5, +// yyy1 is pin yyy of multiplexer U2 + +#define ENA0 0x01 +#define ENB0 0x02 +#define ENA1 0x04 +#define ENB1 0x08 + +#define IN10 0x10 +#define IN00 0x20 +#define IN11 0x40 +#define IN01 0x80 + +static void xguard_muxsel(struct bttv *btv, unsigned int input) +{ + static const int masks[] = { + ENB0, ENB0|IN00, ENB0|IN10, ENB0|IN00|IN10, + ENA0, ENA0|IN00, ENA0|IN10, ENA0|IN00|IN10, + ENB1, ENB1|IN01, ENB1|IN11, ENB1|IN01|IN11, + ENA1, ENA1|IN01, ENA1|IN11, ENA1|IN01|IN11, + }; + gpio_write(masks[input%16]); +} +static void picolo_tetra_init(struct bttv *btv) +{ + /*This is the video input redirection fonctionality : I DID NOT USED IT. */ + btwrite (0x08<<16,BT848_GPIO_DATA);/*GPIO[19] [==> 4053 B+C] set to 1 */ + btwrite (0x04<<16,BT848_GPIO_DATA);/*GPIO[18] [==> 4053 A] set to 1*/ +} +static void picolo_tetra_muxsel (struct bttv* btv, unsigned int input) +{ + + dprintk (KERN_DEBUG "bttv%d : picolo_tetra_muxsel => input = %d\n",btv->c.nr,input); + /*Just set the right path in the analog multiplexers : channel 1 -> 4 ==> Analog Mux ==> MUX0*/ + /*GPIO[20]&GPIO[21] used to choose the right input*/ + btwrite (input<<20,BT848_GPIO_DATA); + +} + +/* + * ivc120_muxsel [Added by Alan Garfield <alan@fromorbit.com>] + * + * The IVC120G security card has 4 i2c controlled TDA8540 matrix + * swichers to provide 16 channels to MUX0. The TDA8540's have + * 4 indepedant outputs and as such the IVC120G also has the + * optional "Monitor Out" bus. This allows the card to be looking + * at one input while the monitor is looking at another. + * + * Since I've couldn't be bothered figuring out how to add an + * independant muxsel for the monitor bus, I've just set it to + * whatever the card is looking at. + * + * OUT0 of the TDA8540's is connected to MUX0 (0x03) + * OUT1 of the TDA8540's is connected to "Monitor Out" (0x0C) + * + * TDA8540_ALT3 IN0-3 = Channel 13 - 16 (0x03) + * TDA8540_ALT4 IN0-3 = Channel 1 - 4 (0x03) + * TDA8540_ALT5 IN0-3 = Channel 5 - 8 (0x03) + * TDA8540_ALT6 IN0-3 = Channel 9 - 12 (0x03) + * + */ + +/* All 7 possible sub-ids for the TDA8540 Matrix Switcher */ +#define I2C_TDA8540 0x90 +#define I2C_TDA8540_ALT1 0x92 +#define I2C_TDA8540_ALT2 0x94 +#define I2C_TDA8540_ALT3 0x96 +#define I2C_TDA8540_ALT4 0x98 +#define I2C_TDA8540_ALT5 0x9a +#define I2C_TDA8540_ALT6 0x9c + +static void ivc120_muxsel(struct bttv *btv, unsigned int input) +{ + // Simple maths + int key = input % 4; + int matrix = input / 4; + + dprintk("bttv%d: ivc120_muxsel: Input - %02d | TDA - %02d | In - %02d\n", + btv->c.nr, input, matrix, key); + + // Handles the input selection on the TDA8540's + bttv_I2CWrite(btv, I2C_TDA8540_ALT3, 0x00, + ((matrix == 3) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT4, 0x00, + ((matrix == 0) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT5, 0x00, + ((matrix == 1) ? (key | key << 2) : 0x00), 1); + bttv_I2CWrite(btv, I2C_TDA8540_ALT6, 0x00, + ((matrix == 2) ? (key | key << 2) : 0x00), 1); + + // Handles the output enables on the TDA8540's + bttv_I2CWrite(btv, I2C_TDA8540_ALT3, 0x02, + ((matrix == 3) ? 0x03 : 0x00), 1); // 13 - 16 + bttv_I2CWrite(btv, I2C_TDA8540_ALT4, 0x02, + ((matrix == 0) ? 0x03 : 0x00), 1); // 1-4 + bttv_I2CWrite(btv, I2C_TDA8540_ALT5, 0x02, + ((matrix == 1) ? 0x03 : 0x00), 1); // 5-8 + bttv_I2CWrite(btv, I2C_TDA8540_ALT6, 0x02, + ((matrix == 2) ? 0x03 : 0x00), 1); // 9-12 + + // Selects MUX0 for input on the 878 + btaor((0)<<5, ~(3<<5), BT848_IFORM); +} + + +/* PXC200 muxsel helper + * luke@syseng.anu.edu.au + * another transplant + * from Alessandro Rubini (rubini@linux.it) + * + * There are 4 kinds of cards: + * PXC200L which is bt848 + * PXC200F which is bt848 with PIC controlling mux + * PXC200AL which is bt878 + * PXC200AF which is bt878 with PIC controlling mux + */ +#define PX_CFG_PXC200F 0x01 +#define PX_FLAG_PXC200A 0x00001000 /* a pxc200A is bt-878 based */ +#define PX_I2C_PIC 0x0f +#define PX_PXC200A_CARDID 0x200a1295 +#define PX_I2C_CMD_CFG 0x00 + +static void PXC200_muxsel(struct bttv *btv, unsigned int input) +{ + int rc; + long mux; + int bitmask; + unsigned char buf[2]; + + /* Read PIC config to determine if this is a PXC200F */ + /* PX_I2C_CMD_CFG*/ + buf[0]=0; + buf[1]=0; + rc=bttv_I2CWrite(btv,(PX_I2C_PIC<<1),buf[0],buf[1],1); + if (rc) { + printk(KERN_DEBUG "bttv%d: PXC200_muxsel: pic cfg write failed:%d\n", btv->c.nr,rc); + /* not PXC ? do nothing */ + return; + } + + rc=bttv_I2CRead(btv,(PX_I2C_PIC<<1),NULL); + if (!(rc & PX_CFG_PXC200F)) { + printk(KERN_DEBUG "bttv%d: PXC200_muxsel: not PXC200F rc:%d \n", btv->c.nr,rc); + return; + } + + + /* The multiplexer in the 200F is handled by the GPIO port */ + /* get correct mapping between inputs */ + /* mux = bttv_tvcards[btv->type].muxsel[input] & 3; */ + /* ** not needed!? */ + mux = input; + + /* make sure output pins are enabled */ + /* bitmask=0x30f; */ + bitmask=0x302; + /* check whether we have a PXC200A */ + if (btv->cardid == PX_PXC200A_CARDID) { + bitmask ^= 0x180; /* use 7 and 9, not 8 and 9 */ + bitmask |= 7<<4; /* the DAC */ + } + btwrite(bitmask, BT848_GPIO_OUT_EN); + + bitmask = btread(BT848_GPIO_DATA); + if (btv->cardid == PX_PXC200A_CARDID) + bitmask = (bitmask & ~0x280) | ((mux & 2) << 8) | ((mux & 1) << 7); + else /* older device */ + bitmask = (bitmask & ~0x300) | ((mux & 3) << 8); + btwrite(bitmask,BT848_GPIO_DATA); + + /* + * Was "to be safe, set the bt848 to input 0" + * Actually, since it's ok at load time, better not messing + * with these bits (on PXC200AF you need to set mux 2 here) + * + * needed because bttv-driver sets mux before calling this function + */ + if (btv->cardid == PX_PXC200A_CARDID) + btaor(2<<5, ~BT848_IFORM_MUXSEL, BT848_IFORM); + else /* older device */ + btand(~BT848_IFORM_MUXSEL,BT848_IFORM); + + printk(KERN_DEBUG "bttv%d: setting input channel to:%d\n", btv->c.nr,(int)mux); +} + +/* ----------------------------------------------------------------------- */ +/* motherboard chipset specific stuff */ + +void __devinit bttv_check_chipset(void) +{ + int pcipci_fail = 0; + struct pci_dev *dev = NULL; + + if (pci_pci_problems & PCIPCI_FAIL) + pcipci_fail = 1; + if (pci_pci_problems & (PCIPCI_TRITON|PCIPCI_NATOMA|PCIPCI_VIAETBF)) + triton1 = 1; + if (pci_pci_problems & PCIPCI_VSFX) + vsfx = 1; +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) + latency = 0x0A; +#endif + +#if 0 + /* print which chipset we have */ + while ((dev = pci_find_class(PCI_CLASS_BRIDGE_HOST << 8,dev))) + printk(KERN_INFO "bttv: Host bridge is %s\n",pci_name(dev)); +#endif + + /* print warnings about any quirks found */ + if (triton1) + printk(KERN_INFO "bttv: Host bridge needs ETBF enabled.\n"); + if (vsfx) + printk(KERN_INFO "bttv: Host bridge needs VSFX enabled.\n"); + if (pcipci_fail) { + printk(KERN_WARNING "bttv: BT848 and your chipset may not work together.\n"); + if (UNSET == no_overlay) { + printk(KERN_WARNING "bttv: going to disable overlay.\n"); + no_overlay = 1; + } + } + if (UNSET != latency) + printk(KERN_INFO "bttv: pci latency fixup [%d]\n",latency); + + while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82441, dev))) { + unsigned char b; + pci_read_config_byte(dev, 0x53, &b); + if (bttv_debug) + printk(KERN_INFO "bttv: Host bridge: 82441FX Natoma, " + "bufcon=0x%02x\n",b); + } +} + +int __devinit bttv_handle_chipset(struct bttv *btv) +{ + unsigned char command; + + if (!triton1 && !vsfx && UNSET == latency) + return 0; + + if (bttv_verbose) { + if (triton1) + printk(KERN_INFO "bttv%d: enabling ETBF (430FX/VP3 compatibilty)\n",btv->c.nr); + if (vsfx && btv->id >= 878) + printk(KERN_INFO "bttv%d: enabling VSFX\n",btv->c.nr); + if (UNSET != latency) + printk(KERN_INFO "bttv%d: setting pci timer to %d\n", + btv->c.nr,latency); + } + + if (btv->id < 878) { + /* bt848 (mis)uses a bit in the irq mask for etbf */ + if (triton1) + btv->triton1 = BT848_INT_ETBF; + } else { + /* bt878 has a bit in the pci config space for it */ + pci_read_config_byte(btv->c.pci, BT878_DEVCTRL, &command); + if (triton1) + command |= BT878_EN_TBFX; + if (vsfx) + command |= BT878_EN_VSFX; + pci_write_config_byte(btv->c.pci, BT878_DEVCTRL, command); + } + if (UNSET != latency) + pci_write_config_byte(btv->c.pci, PCI_LATENCY_TIMER, latency); + return 0; +} + + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-driver.c b/drivers/media/video/bttv-driver.c new file mode 100644 index 000000000000..c13f222fe6bd --- /dev/null +++ b/drivers/media/video/bttv-driver.c @@ -0,0 +1,4116 @@ +/* + $Id: bttv-driver.c,v 1.37 2005/02/21 13:57:59 kraxel Exp $ + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler <rjkm@thp.uni-koeln.de> + & Marcus Metzler <mocm@thp.uni-koeln.de> + (c) 1999-2002 Gerd Knorr <kraxel@bytesex.org> + + some v4l2 code lines are taken from Justin's bttv2 driver which is + (c) 2000 Justin Schoeman <justin@suntiger.ee.up.ac.za> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/kdev_t.h> + +#include <asm/io.h> +#include <asm/byteorder.h> + +#include "bttvp.h" + +unsigned int bttv_num; /* number of Bt848s in use */ +struct bttv bttvs[BTTV_MAX]; + +unsigned int bttv_debug = 0; +unsigned int bttv_verbose = 1; +unsigned int bttv_gpio = 0; + +/* config variables */ +#ifdef __BIG_ENDIAN +static unsigned int bigendian=1; +#else +static unsigned int bigendian=0; +#endif +static unsigned int radio[BTTV_MAX]; +static unsigned int irq_debug = 0; +static unsigned int gbuffers = 8; +static unsigned int gbufsize = 0x208000; + +static int video_nr = -1; +static int radio_nr = -1; +static int vbi_nr = -1; +static int debug_latency = 0; + +static unsigned int fdsr = 0; + +/* options */ +static unsigned int combfilter = 0; +static unsigned int lumafilter = 0; +static unsigned int automute = 1; +static unsigned int chroma_agc = 0; +static unsigned int adc_crush = 1; +static unsigned int whitecrush_upper = 0xCF; +static unsigned int whitecrush_lower = 0x7F; +static unsigned int vcr_hack = 0; +static unsigned int irq_iswitch = 0; + +/* API features (turn on/off stuff for testing) */ +static unsigned int v4l2 = 1; + + +/* insmod args */ +module_param(bttv_verbose, int, 0644); +module_param(bttv_gpio, int, 0644); +module_param(bttv_debug, int, 0644); +module_param(irq_debug, int, 0644); +module_param(debug_latency, int, 0644); + +module_param(fdsr, int, 0444); +module_param(video_nr, int, 0444); +module_param(radio_nr, int, 0444); +module_param(vbi_nr, int, 0444); +module_param(gbuffers, int, 0444); +module_param(gbufsize, int, 0444); + +module_param(v4l2, int, 0644); +module_param(bigendian, int, 0644); +module_param(irq_iswitch, int, 0644); +module_param(combfilter, int, 0444); +module_param(lumafilter, int, 0444); +module_param(automute, int, 0444); +module_param(chroma_agc, int, 0444); +module_param(adc_crush, int, 0444); +module_param(whitecrush_upper, int, 0444); +module_param(whitecrush_lower, int, 0444); +module_param(vcr_hack, int, 0444); + +module_param_array(radio, int, NULL, 0444); + +MODULE_PARM_DESC(radio,"The TV card supports radio, default is 0 (no)"); +MODULE_PARM_DESC(bigendian,"byte order of the framebuffer, default is native endian"); +MODULE_PARM_DESC(bttv_verbose,"verbose startup messages, default is 1 (yes)"); +MODULE_PARM_DESC(bttv_gpio,"log gpio changes, default is 0 (no)"); +MODULE_PARM_DESC(bttv_debug,"debug messages, default is 0 (no)"); +MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)"); +MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8"); +MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000"); +MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)"); +MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)"); +MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)"); +MODULE_PARM_DESC(whitecrush_upper,"sets the white crush upper value, default is 207"); +MODULE_PARM_DESC(whitecrush_lower,"sets the white crush lower value, default is 127"); +MODULE_PARM_DESC(vcr_hack,"enables the VCR hack (improves synch on poor VCR tapes), default is 0 (no)"); +MODULE_PARM_DESC(irq_iswitch,"switch inputs in irq handler"); + +MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards"); +MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* ----------------------------------------------------------------------- */ +/* sysfs */ + +static ssize_t show_card(struct class_device *cd, char *buf) +{ + struct video_device *vfd = to_video_device(cd); + struct bttv *btv = dev_get_drvdata(vfd->dev); + return sprintf(buf, "%d\n", btv ? btv->c.type : UNSET); +} +static CLASS_DEVICE_ATTR(card, S_IRUGO, show_card, NULL); + +/* ----------------------------------------------------------------------- */ +/* static data */ + +/* special timing tables from conexant... */ +static u8 SRAM_Table[][60] = +{ + /* PAL digital input over GPIO[7:0] */ + { + 45, // 45 bytes following + 0x36,0x11,0x01,0x00,0x90,0x02,0x05,0x10,0x04,0x16, + 0x12,0x05,0x11,0x00,0x04,0x12,0xC0,0x00,0x31,0x00, + 0x06,0x51,0x08,0x03,0x89,0x08,0x07,0xC0,0x44,0x00, + 0x81,0x01,0x01,0xA9,0x0D,0x02,0x02,0x50,0x03,0x37, + 0x37,0x00,0xAF,0x21,0x00 + }, + /* NTSC digital input over GPIO[7:0] */ + { + 51, // 51 bytes following + 0x0C,0xC0,0x00,0x00,0x90,0x02,0x03,0x10,0x03,0x06, + 0x10,0x04,0x12,0x12,0x05,0x02,0x13,0x04,0x19,0x00, + 0x04,0x39,0x00,0x06,0x59,0x08,0x03,0x83,0x08,0x07, + 0x03,0x50,0x00,0xC0,0x40,0x00,0x86,0x01,0x01,0xA6, + 0x0D,0x02,0x03,0x11,0x01,0x05,0x37,0x00,0xAC,0x21, + 0x00, + }, + // TGB_NTSC392 // quartzsight + // This table has been modified to be used for Fusion Rev D + { + 0x2A, // size of table = 42 + 0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24, + 0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10, + 0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00, + 0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3, + 0x20, 0x00 + } +}; + +const struct bttv_tvnorm bttv_tvnorms[] = { + /* PAL-BDGHI */ + /* max. active video is actually 922, but 924 is divisible by 4 and 3! */ + /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */ + { + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x20, + .vbipack = 255, + .sram = 0, + },{ + .v4l2_id = V4L2_STD_NTSC_M, + .name = "NTSC", + .Fsc = 28636363, + .swidth = 768, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC|BT848_IFORM_XT0), + .scaledtwidth = 910, + .hdelayx1 = 128, + .hactivex1 = 910, + .vdelay = 0x1a, + .vbipack = 144, + .sram = 1, + },{ + .v4l2_id = V4L2_STD_SECAM, + .name = "SECAM", + .Fsc = 35468950, + .swidth = 924, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0xb0, + .iform = (BT848_IFORM_SECAM|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 255, + .sram = 0, /* like PAL, correct? */ + },{ + .v4l2_id = V4L2_STD_PAL_Nc, + .name = "PAL-Nc", + .Fsc = 28636363, + .swidth = 640, + .sheight = 576, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_NC|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 130, + .hactivex1 = 734, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + },{ + .v4l2_id = V4L2_STD_PAL_M, + .name = "PAL-M", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_PAL_M|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x1a, + .vbipack = 144, + .sram = -1, + },{ + .v4l2_id = V4L2_STD_PAL_N, + .name = "PAL-N", + .Fsc = 35468950, + .swidth = 768, + .sheight = 576, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_N|BT848_IFORM_XT1), + .scaledtwidth = 944, + .hdelayx1 = 186, + .hactivex1 = 922, + .vdelay = 0x20, + .vbipack = 144, + .sram = -1, + },{ + .v4l2_id = V4L2_STD_NTSC_M_JP, + .name = "NTSC-JP", + .Fsc = 28636363, + .swidth = 640, + .sheight = 480, + .totalwidth = 910, + .adelay = 0x68, + .bdelay = 0x5d, + .iform = (BT848_IFORM_NTSC_J|BT848_IFORM_XT0), + .scaledtwidth = 780, + .hdelayx1 = 135, + .hactivex1 = 754, + .vdelay = 0x16, + .vbipack = 144, + .sram = -1, + },{ + /* that one hopefully works with the strange timing + * which video recorders produce when playing a NTSC + * tape on a PAL TV ... */ + .v4l2_id = V4L2_STD_PAL_60, + .name = "PAL-60", + .Fsc = 35468950, + .swidth = 924, + .sheight = 480, + .totalwidth = 1135, + .adelay = 0x7f, + .bdelay = 0x72, + .iform = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + .scaledtwidth = 1135, + .hdelayx1 = 186, + .hactivex1 = 924, + .vdelay = 0x1a, + .vbipack = 255, + .vtotal = 524, + .sram = -1, + } +}; +static const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms); + +/* ----------------------------------------------------------------------- */ +/* bttv format list + packed pixel formats must come first */ +static const struct bttv_format bttv_formats[] = { + { + .name = "8 bpp, gray", + .palette = VIDEO_PALETTE_GREY, + .fourcc = V4L2_PIX_FMT_GREY, + .btformat = BT848_COLOR_FMT_Y8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "8 bpp, dithered color", + .palette = VIDEO_PALETTE_HI240, + .fourcc = V4L2_PIX_FMT_HI240, + .btformat = BT848_COLOR_FMT_RGB8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED | FORMAT_FLAGS_DITHER, + },{ + .name = "15 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB555, + .fourcc = V4L2_PIX_FMT_RGB555, + .btformat = BT848_COLOR_FMT_RGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .palette = -1, + .fourcc = V4L2_PIX_FMT_RGB555X, + .btformat = BT848_COLOR_FMT_RGB15, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB565, + .fourcc = V4L2_PIX_FMT_RGB565, + .btformat = BT848_COLOR_FMT_RGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .palette = -1, + .fourcc = V4L2_PIX_FMT_RGB565X, + .btformat = BT848_COLOR_FMT_RGB16, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB24, + .fourcc = V4L2_PIX_FMT_BGR24, + .btformat = BT848_COLOR_FMT_RGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .palette = VIDEO_PALETTE_RGB32, + .fourcc = V4L2_PIX_FMT_BGR32, + .btformat = BT848_COLOR_FMT_RGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .palette = -1, + .fourcc = V4L2_PIX_FMT_RGB32, + .btformat = BT848_COLOR_FMT_RGB32, + .btswap = 0x0f, /* byte+word swap */ + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .palette = VIDEO_PALETTE_YUV422, + .fourcc = V4L2_PIX_FMT_YUYV, + .btformat = BT848_COLOR_FMT_YUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .palette = VIDEO_PALETTE_YUYV, + .fourcc = V4L2_PIX_FMT_YUYV, + .btformat = BT848_COLOR_FMT_YUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .palette = VIDEO_PALETTE_UYVY, + .fourcc = V4L2_PIX_FMT_UYVY, + .btformat = BT848_COLOR_FMT_YUY2, + .btswap = 0x03, /* byteswap */ + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV422P, + .fourcc = V4L2_PIX_FMT_YUV422P, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 16, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 0, + },{ + .name = "4:2:0, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV420P, + .fourcc = V4L2_PIX_FMT_YUV420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:2:0, planar, Y-Cr-Cb", + .palette = -1, + .fourcc = V4L2_PIX_FMT_YVU420, + .btformat = BT848_COLOR_FMT_YCrCb422, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:1:1, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV411P, + .fourcc = V4L2_PIX_FMT_YUV411P, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 12, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 0, + },{ + .name = "4:1:0, planar, Y-Cb-Cr", + .palette = VIDEO_PALETTE_YUV410P, + .fourcc = V4L2_PIX_FMT_YUV410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR, + .hshift = 2, + .vshift = 2, + },{ + .name = "4:1:0, planar, Y-Cr-Cb", + .palette = -1, + .fourcc = V4L2_PIX_FMT_YVU410, + .btformat = BT848_COLOR_FMT_YCrCb411, + .depth = 9, + .flags = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb, + .hshift = 2, + .vshift = 2, + },{ + .name = "raw scanlines", + .palette = VIDEO_PALETTE_RAW, + .fourcc = -1, + .btformat = BT848_COLOR_FMT_RAW, + .depth = 8, + .flags = FORMAT_FLAGS_RAW, + } +}; +static const unsigned int BTTV_FORMATS = ARRAY_SIZE(bttv_formats); + +/* ----------------------------------------------------------------------- */ + +#define V4L2_CID_PRIVATE_CHROMA_AGC (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_COMBFILTER (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_LUMAFILTER (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_PRIVATE_AGC_CRUSH (V4L2_CID_PRIVATE_BASE + 4) +#define V4L2_CID_PRIVATE_VCR_HACK (V4L2_CID_PRIVATE_BASE + 5) +#define V4L2_CID_PRIVATE_WHITECRUSH_UPPER (V4L2_CID_PRIVATE_BASE + 6) +#define V4L2_CID_PRIVATE_WHITECRUSH_LOWER (V4L2_CID_PRIVATE_BASE + 7) +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 8) + +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl bttv_ctls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 65535, + .step = 256, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 65535, + .step = 128, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 65535, + .step = 128, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = 0, + .maximum = 65535, + .step = 256, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 65535, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_BALANCE, + .name = "Balance", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_BASS, + .name = "Bass", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_AUDIO_TREBLE, + .name = "Treble", + .minimum = 0, + .maximum = 65535, + .step = 65535/100, + .default_value = 32768, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- private --- */ + { + .id = V4L2_CID_PRIVATE_CHROMA_AGC, + .name = "chroma agc", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_COMBFILTER, + .name = "combfilter", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "automute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_LUMAFILTER, + .name = "luma decimation filter", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_AGC_CRUSH, + .name = "agc crush", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_VCR_HACK, + .name = "vcr hack", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_WHITECRUSH_UPPER, + .name = "whitecrush upper", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0xCF, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_WHITECRUSH_LOWER, + .name = "whitecrush lower", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 0x7F, + .type = V4L2_CTRL_TYPE_INTEGER, + } + +}; +static const int BTTV_CTLS = ARRAY_SIZE(bttv_ctls); + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static +int check_alloc_btres(struct bttv *btv, struct bttv_fh *fh, int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + down(&btv->reslock); + if (btv->resources & bit) { + /* no, someone else uses it */ + up(&btv->reslock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + btv->resources |= bit; + up(&btv->reslock); + return 1; +} + +static +int check_btres(struct bttv_fh *fh, int bit) +{ + return (fh->resources & bit); +} + +static +int locked_btres(struct bttv *btv, int bit) +{ + return (btv->resources & bit); +} + +static +void free_btres(struct bttv *btv, struct bttv_fh *fh, int bits) +{ +#if 1 /* DEBUG */ + if ((fh->resources & bits) != bits) { + /* trying to free ressources not allocated by us ... */ + printk("bttv: BUG! (btres)\n"); + } +#endif + down(&btv->reslock); + fh->resources &= ~bits; + btv->resources &= ~bits; + up(&btv->reslock); +} + +/* ----------------------------------------------------------------------- */ +/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC */ + +/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C + PLL_X = Reference pre-divider (0=1, 1=2) + PLL_C = Post divider (0=6, 1=4) + PLL_I = Integer input + PLL_F = Fractional input + + F_input = 28.636363 MHz: + PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0 +*/ + +static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout) +{ + unsigned char fl, fh, fi; + + /* prevent overflows */ + fin/=4; + fout/=4; + + fout*=12; + fi=fout/fin; + + fout=(fout%fin)*256; + fh=fout/fin; + + fout=(fout%fin)*256; + fl=fout/fin; + + btwrite(fl, BT848_PLL_F_LO); + btwrite(fh, BT848_PLL_F_HI); + btwrite(fi|BT848_PLL_X, BT848_PLL_XCI); +} + +static void set_pll(struct bttv *btv) +{ + int i; + + if (!btv->pll.pll_crystal) + return; + + if (btv->pll.pll_ofreq == btv->pll.pll_current) { + dprintk("bttv%d: PLL: no change required\n",btv->c.nr); + return; + } + + if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) { + /* no PLL needed */ + if (btv->pll.pll_current == 0) + return; + vprintk(KERN_INFO "bttv%d: PLL can sleep, using XTAL (%d).\n", + btv->c.nr,btv->pll.pll_ifreq); + btwrite(0x00,BT848_TGCTRL); + btwrite(0x00,BT848_PLL_XCI); + btv->pll.pll_current = 0; + return; + } + + vprintk(KERN_INFO "bttv%d: PLL: %d => %d ",btv->c.nr, + btv->pll.pll_ifreq, btv->pll.pll_ofreq); + set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq); + + for (i=0; i<10; i++) { + /* Let other people run while the PLL stabilizes */ + vprintk("."); + msleep(10); + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_PLOCK) { + btwrite(0,BT848_DSTATUS); + } else { + btwrite(0x08,BT848_TGCTRL); + btv->pll.pll_current = btv->pll.pll_ofreq; + vprintk(" ok\n"); + return; + } + } + btv->pll.pll_current = -1; + vprintk("failed\n"); + return; +} + +/* used to switch between the bt848's analog/digital video capture modes */ +static void bt848A_set_timing(struct bttv *btv) +{ + int i, len; + int table_idx = bttv_tvnorms[btv->tvnorm].sram; + int fsc = bttv_tvnorms[btv->tvnorm].Fsc; + + if (UNSET == bttv_tvcards[btv->c.type].muxsel[btv->input]) { + dprintk("bttv%d: load digital timing table (table_idx=%d)\n", + btv->c.nr,table_idx); + + /* timing change...reset timing generator address */ + btwrite(0x00, BT848_TGCTRL); + btwrite(0x02, BT848_TGCTRL); + btwrite(0x00, BT848_TGCTRL); + + len=SRAM_Table[table_idx][0]; + for(i = 1; i <= len; i++) + btwrite(SRAM_Table[table_idx][i],BT848_TGLB); + btv->pll.pll_ofreq = 27000000; + + set_pll(btv); + btwrite(0x11, BT848_TGCTRL); + btwrite(0x41, BT848_DVSIF); + } else { + btv->pll.pll_ofreq = fsc; + set_pll(btv); + btwrite(0x0, BT848_DVSIF); + } +} + +/* ----------------------------------------------------------------------- */ + +static void bt848_bright(struct bttv *btv, int bright) +{ + int value; + + // printk("bttv: set bright: %d\n",bright); // DEBUG + btv->bright = bright; + + /* We want -128 to 127 we get 0-65535 */ + value = (bright >> 8) - 128; + btwrite(value & 0xff, BT848_BRIGHT); +} + +static void bt848_hue(struct bttv *btv, int hue) +{ + int value; + + btv->hue = hue; + + /* -128 to 127 */ + value = (hue >> 8) - 128; + btwrite(value & 0xff, BT848_HUE); +} + +static void bt848_contrast(struct bttv *btv, int cont) +{ + int value,hibit; + + btv->contrast = cont; + + /* 0-511 */ + value = (cont >> 7); + hibit = (value >> 6) & 4; + btwrite(value & 0xff, BT848_CONTRAST_LO); + btaor(hibit, ~4, BT848_E_CONTROL); + btaor(hibit, ~4, BT848_O_CONTROL); +} + +static void bt848_sat(struct bttv *btv, int color) +{ + int val_u,val_v,hibits; + + btv->saturation = color; + + /* 0-511 for the color */ + val_u = color >> 7; + val_v = ((color>>7)*180L)/254; + hibits = (val_u >> 7) & 2; + hibits |= (val_v >> 8) & 1; + btwrite(val_u & 0xff, BT848_SAT_U_LO); + btwrite(val_v & 0xff, BT848_SAT_V_LO); + btaor(hibits, ~3, BT848_E_CONTROL); + btaor(hibits, ~3, BT848_O_CONTROL); +} + +/* ----------------------------------------------------------------------- */ + +static int +video_mux(struct bttv *btv, unsigned int input) +{ + int mux,mask2; + + if (input >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + + /* needed by RemoteVideo MX */ + mask2 = bttv_tvcards[btv->c.type].gpiomask2; + if (mask2) + gpio_inout(mask2,mask2); + + if (input == btv->svhs) { + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); + } else { + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); + } + mux = bttv_tvcards[btv->c.type].muxsel[input] & 3; + btaor(mux<<5, ~(3<<5), BT848_IFORM); + dprintk(KERN_DEBUG "bttv%d: video mux: input=%d mux=%d\n", + btv->c.nr,input,mux); + + /* card specific hook */ + if(bttv_tvcards[btv->c.type].muxsel_hook) + bttv_tvcards[btv->c.type].muxsel_hook (btv, input); + return 0; +} + +static char *audio_modes[] = { + "audio: tuner", "audio: radio", "audio: extern", + "audio: intern", "audio: off" +}; + +static int +audio_mux(struct bttv *btv, int mode) +{ + int val,mux,i2c_mux,signal; + + gpio_inout(bttv_tvcards[btv->c.type].gpiomask, + bttv_tvcards[btv->c.type].gpiomask); + signal = btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC; + + switch (mode) { + case AUDIO_MUTE: + btv->audio |= AUDIO_MUTE; + break; + case AUDIO_UNMUTE: + btv->audio &= ~AUDIO_MUTE; + break; + case AUDIO_TUNER: + case AUDIO_RADIO: + case AUDIO_EXTERN: + case AUDIO_INTERN: + btv->audio &= AUDIO_MUTE; + btv->audio |= mode; + } + i2c_mux = mux = (btv->audio & AUDIO_MUTE) ? AUDIO_OFF : btv->audio; + if (btv->opt_automute && !signal && !btv->radio_user) + mux = AUDIO_OFF; +#if 0 + printk("bttv%d: amux: mode=%d audio=%d signal=%s mux=%d/%d irq=%s\n", + btv->c.nr, mode, btv->audio, signal ? "yes" : "no", + mux, i2c_mux, in_interrupt() ? "yes" : "no"); +#endif + + val = bttv_tvcards[btv->c.type].audiomux[mux]; + gpio_bits(bttv_tvcards[btv->c.type].gpiomask,val); + if (bttv_gpio) + bttv_gpio_tracking(btv,audio_modes[mux]); + if (!in_interrupt()) + bttv_call_i2c_clients(btv,AUDC_SET_INPUT,&(i2c_mux)); + return 0; +} + +static void +i2c_vidiocschan(struct bttv *btv) +{ + struct video_channel c; + + memset(&c,0,sizeof(c)); + c.norm = btv->tvnorm; + c.channel = btv->input; + bttv_call_i2c_clients(btv,VIDIOCSCHAN,&c); + if (btv->c.type == BTTV_VOODOOTV_FM) + bttv_tda9880_setnorm(btv,c.norm); +} + +static int +set_tvnorm(struct bttv *btv, unsigned int norm) +{ + const struct bttv_tvnorm *tvnorm; + + if (norm < 0 || norm >= BTTV_TVNORMS) + return -EINVAL; + + btv->tvnorm = norm; + tvnorm = &bttv_tvnorms[norm]; + + btwrite(tvnorm->adelay, BT848_ADELAY); + btwrite(tvnorm->bdelay, BT848_BDELAY); + btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH), + BT848_IFORM); + btwrite(tvnorm->vbipack, BT848_VBI_PACK_SIZE); + btwrite(1, BT848_VBI_PACK_DEL); + bt848A_set_timing(btv); + + switch (btv->c.type) { + case BTTV_VOODOOTV_FM: + bttv_tda9880_setnorm(btv,norm); + break; +#if 0 + case BTTV_OSPREY540: + osprey_540_set_norm(btv,norm); + break; +#endif + } + return 0; +} + +static void +set_input(struct bttv *btv, unsigned int input) +{ + unsigned long flags; + + btv->input = input; + if (irq_iswitch) { + spin_lock_irqsave(&btv->s_lock,flags); + if (btv->curr.frame_irq) { + /* active capture -> delayed input switch */ + btv->new_input = input; + } else { + video_mux(btv,input); + } + spin_unlock_irqrestore(&btv->s_lock,flags); + } else { + video_mux(btv,input); + } + audio_mux(btv,(input == bttv_tvcards[btv->c.type].tuner ? + AUDIO_TUNER : AUDIO_EXTERN)); + set_tvnorm(btv,btv->tvnorm); + i2c_vidiocschan(btv); +} + +static void init_irqreg(struct bttv *btv) +{ + /* clear status */ + btwrite(0xfffffUL, BT848_INT_STAT); + + if (bttv_tvcards[btv->c.type].no_video) { + /* i2c only */ + btwrite(BT848_INT_I2CDONE, + BT848_INT_MASK); + } else { + /* full video */ + btwrite((btv->triton1) | + (btv->gpioirq ? BT848_INT_GPINT : 0) | + BT848_INT_SCERR | + (fdsr ? BT848_INT_FDSR : 0) | + BT848_INT_RISCI|BT848_INT_OCERR|BT848_INT_VPRES| + BT848_INT_FMTCHG|BT848_INT_HLOCK| + BT848_INT_I2CDONE, + BT848_INT_MASK); + } +} + +static void init_bt848(struct bttv *btv) +{ + int val; + + if (bttv_tvcards[btv->c.type].no_video) { + /* very basic init only */ + init_irqreg(btv); + return; + } + + btwrite(0x00, BT848_CAP_CTL); + btwrite(BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL); + btwrite(BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, BT848_IFORM); + + /* set planar and packed mode trigger points and */ + /* set rising edge of inverted GPINTR pin as irq trigger */ + btwrite(BT848_GPIO_DMA_CTL_PKTP_32| + BT848_GPIO_DMA_CTL_PLTP1_16| + BT848_GPIO_DMA_CTL_PLTP23_16| + BT848_GPIO_DMA_CTL_GPINTC| + BT848_GPIO_DMA_CTL_GPINTI, + BT848_GPIO_DMA_CTL); + + val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + + btwrite(0x20, BT848_E_VSCALE_HI); + btwrite(0x20, BT848_O_VSCALE_HI); + btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0), + BT848_ADC); + + btwrite(whitecrush_upper, BT848_WC_UP); + btwrite(whitecrush_lower, BT848_WC_DOWN); + + if (btv->opt_lumafilter) { + btwrite(0, BT848_E_CONTROL); + btwrite(0, BT848_O_CONTROL); + } else { + btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + + bt848_bright(btv, btv->bright); + bt848_hue(btv, btv->hue); + bt848_contrast(btv, btv->contrast); + bt848_sat(btv, btv->saturation); + + /* interrupt */ + init_irqreg(btv); +} + +static void bttv_reinit_bt848(struct bttv *btv) +{ + unsigned long flags; + + if (bttv_verbose) + printk(KERN_INFO "bttv%d: reset, reinitialize\n",btv->c.nr); + spin_lock_irqsave(&btv->s_lock,flags); + btv->errors=0; + bttv_set_dma(btv,0); + spin_unlock_irqrestore(&btv->s_lock,flags); + + init_bt848(btv); + btv->pll.pll_current = -1; + set_input(btv,btv->input); +} + +static int get_control(struct bttv *btv, struct v4l2_control *c) +{ + struct video_audio va; + int i; + + for (i = 0; i < BTTV_CTLS; i++) + if (bttv_ctls[i].id == c->id) + break; + if (i == BTTV_CTLS) + return -EINVAL; + if (i >= 4 && i <= 8) { + memset(&va,0,sizeof(va)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + } + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = btv->bright; + break; + case V4L2_CID_HUE: + c->value = btv->hue; + break; + case V4L2_CID_CONTRAST: + c->value = btv->contrast; + break; + case V4L2_CID_SATURATION: + c->value = btv->saturation; + break; + + case V4L2_CID_AUDIO_MUTE: + c->value = (VIDEO_AUDIO_MUTE & va.flags) ? 1 : 0; + break; + case V4L2_CID_AUDIO_VOLUME: + c->value = va.volume; + break; + case V4L2_CID_AUDIO_BALANCE: + c->value = va.balance; + break; + case V4L2_CID_AUDIO_BASS: + c->value = va.bass; + break; + case V4L2_CID_AUDIO_TREBLE: + c->value = va.treble; + break; + + case V4L2_CID_PRIVATE_CHROMA_AGC: + c->value = btv->opt_chroma_agc; + break; + case V4L2_CID_PRIVATE_COMBFILTER: + c->value = btv->opt_combfilter; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + c->value = btv->opt_lumafilter; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + c->value = btv->opt_automute; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + c->value = btv->opt_adc_crush; + break; + case V4L2_CID_PRIVATE_VCR_HACK: + c->value = btv->opt_vcr_hack; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + c->value = btv->opt_whitecrush_upper; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + c->value = btv->opt_whitecrush_lower; + break; + default: + return -EINVAL; + } + return 0; +} + +static int set_control(struct bttv *btv, struct v4l2_control *c) +{ + struct video_audio va; + int i,val; + + for (i = 0; i < BTTV_CTLS; i++) + if (bttv_ctls[i].id == c->id) + break; + if (i == BTTV_CTLS) + return -EINVAL; + if (i >= 4 && i <= 8) { + memset(&va,0,sizeof(va)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + } + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + bt848_bright(btv,c->value); + break; + case V4L2_CID_HUE: + bt848_hue(btv,c->value); + break; + case V4L2_CID_CONTRAST: + bt848_contrast(btv,c->value); + break; + case V4L2_CID_SATURATION: + bt848_sat(btv,c->value); + break; + case V4L2_CID_AUDIO_MUTE: + if (c->value) { + va.flags |= VIDEO_AUDIO_MUTE; + audio_mux(btv, AUDIO_MUTE); + } else { + va.flags &= ~VIDEO_AUDIO_MUTE; + audio_mux(btv, AUDIO_UNMUTE); + } + break; + + case V4L2_CID_AUDIO_VOLUME: + va.volume = c->value; + break; + case V4L2_CID_AUDIO_BALANCE: + va.balance = c->value; + break; + case V4L2_CID_AUDIO_BASS: + va.bass = c->value; + break; + case V4L2_CID_AUDIO_TREBLE: + va.treble = c->value; + break; + + case V4L2_CID_PRIVATE_CHROMA_AGC: + btv->opt_chroma_agc = c->value; + val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0; + btwrite(val, BT848_E_SCLOOP); + btwrite(val, BT848_O_SCLOOP); + break; + case V4L2_CID_PRIVATE_COMBFILTER: + btv->opt_combfilter = c->value; + break; + case V4L2_CID_PRIVATE_LUMAFILTER: + btv->opt_lumafilter = c->value; + if (btv->opt_lumafilter) { + btand(~BT848_CONTROL_LDEC, BT848_E_CONTROL); + btand(~BT848_CONTROL_LDEC, BT848_O_CONTROL); + } else { + btor(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btor(BT848_CONTROL_LDEC, BT848_O_CONTROL); + } + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + btv->opt_automute = c->value; + break; + case V4L2_CID_PRIVATE_AGC_CRUSH: + btv->opt_adc_crush = c->value; + btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0), + BT848_ADC); + break; + case V4L2_CID_PRIVATE_VCR_HACK: + btv->opt_vcr_hack = c->value; + break; + case V4L2_CID_PRIVATE_WHITECRUSH_UPPER: + btv->opt_whitecrush_upper = c->value; + btwrite(c->value, BT848_WC_UP); + break; + case V4L2_CID_PRIVATE_WHITECRUSH_LOWER: + btv->opt_whitecrush_lower = c->value; + btwrite(c->value, BT848_WC_DOWN); + break; + default: + return -EINVAL; + } + if (i >= 4 && i <= 8) { + bttv_call_i2c_clients(btv, VIDIOCSAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,1); + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +void bttv_gpio_tracking(struct bttv *btv, char *comment) +{ + unsigned int outbits, data; + outbits = btread(BT848_GPIO_OUT_EN); + data = btread(BT848_GPIO_DATA); + printk(KERN_DEBUG "bttv%d: gpio: en=%08x, out=%08x in=%08x [%s]\n", + btv->c.nr,outbits,data & outbits, data & ~outbits, comment); +} + +static void bttv_field_count(struct bttv *btv) +{ + int need_count = 0; + + if (btv->users) + need_count++; + + if (need_count) { + /* start field counter */ + btor(BT848_INT_VSYNC,BT848_INT_MASK); + } else { + /* stop field counter */ + btand(~BT848_INT_VSYNC,BT848_INT_MASK); + btv->field_count = 0; + } +} + +static const struct bttv_format* +format_by_palette(int palette) +{ + unsigned int i; + + for (i = 0; i < BTTV_FORMATS; i++) { + if (-1 == bttv_formats[i].palette) + continue; + if (bttv_formats[i].palette == palette) + return bttv_formats+i; + } + return NULL; +} + +static const struct bttv_format* +format_by_fourcc(int fourcc) +{ + unsigned int i; + + for (i = 0; i < BTTV_FORMATS; i++) { + if (-1 == bttv_formats[i].fourcc) + continue; + if (bttv_formats[i].fourcc == fourcc) + return bttv_formats+i; + } + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* misc helpers */ + +static int +bttv_switch_overlay(struct bttv *btv, struct bttv_fh *fh, + struct bttv_buffer *new) +{ + struct bttv_buffer *old; + unsigned long flags; + int retval = 0; + + dprintk("switch_overlay: enter [new=%p]\n",new); + if (new) + new->vb.state = STATE_DONE; + spin_lock_irqsave(&btv->s_lock,flags); + old = btv->screen; + btv->screen = new; + btv->loop_irq |= 1; + bttv_set_dma(btv, 0x03); + spin_unlock_irqrestore(&btv->s_lock,flags); + if (NULL == new) + free_btres(btv,fh,RESOURCE_OVERLAY); + if (NULL != old) { + dprintk("switch_overlay: old=%p state is %d\n",old,old->vb.state); + bttv_dma_free(btv, old); + kfree(old); + } + dprintk("switch_overlay: done\n"); + return retval; +} + +/* ----------------------------------------------------------------------- */ +/* video4linux (1) interface */ + +static int bttv_prepare_buffer(struct bttv *btv, struct bttv_buffer *buf, + const struct bttv_format *fmt, + unsigned int width, unsigned int height, + enum v4l2_field field) +{ + int redo_dma_risc = 0; + int rc; + + /* check settings */ + if (NULL == fmt) + return -EINVAL; + if (fmt->btformat == BT848_COLOR_FMT_RAW) { + width = RAW_BPL; + height = RAW_LINES*2; + if (width*height > buf->vb.bsize) + return -EINVAL; + buf->vb.size = buf->vb.bsize; + } else { + if (width < 48 || + height < 32 || + width > bttv_tvnorms[btv->tvnorm].swidth || + height > bttv_tvnorms[btv->tvnorm].sheight) + return -EINVAL; + buf->vb.size = (width * height * fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + } + + /* alloc + fill struct bttv_buffer (if changed) */ + if (buf->vb.width != width || buf->vb.height != height || + buf->vb.field != field || + buf->tvnorm != btv->tvnorm || buf->fmt != fmt) { + buf->vb.width = width; + buf->vb.height = height; + buf->vb.field = field; + buf->tvnorm = btv->tvnorm; + buf->fmt = fmt; + redo_dma_risc = 1; + } + + /* alloc risc memory */ + if (STATE_NEEDS_INIT == buf->vb.state) { + redo_dma_risc = 1; + if (0 != (rc = videobuf_iolock(btv->c.pci,&buf->vb,&btv->fbuf))) + goto fail; + } + + if (redo_dma_risc) + if (0 != (rc = bttv_buffer_risc(btv,buf))) + goto fail; + + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + bttv_dma_free(btv,buf); + return rc; +} + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct bttv_fh *fh = q->priv_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = gbuffers; + while (*size * *count > gbuffers * gbufsize) + (*count)--; + return 0; +} + +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + struct bttv_fh *fh = q->priv_data; + + return bttv_prepare_buffer(fh->btv, buf, fh->fmt, + fh->width, fh->height, field); +} + +static void +buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + struct bttv_fh *fh = q->priv_data; + struct bttv *btv = fh->btv; + + buf->vb.state = STATE_QUEUED; + list_add_tail(&buf->vb.queue,&btv->capture); + if (!btv->curr.frame_irq) { + btv->loop_irq |= 1; + bttv_set_dma(btv, 0x03); + } +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + struct bttv_fh *fh = q->priv_data; + + bttv_dma_free(fh->btv,buf); +} + +static struct videobuf_queue_ops bttv_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +static const char *v4l1_ioctls[] = { + "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", + "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", + "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", + "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", + "SMICROCODE", "GVBIFMT", "SVBIFMT" }; +#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls) + +static int bttv_common_ioctls(struct bttv *btv, unsigned int cmd, void *arg) +{ + switch (cmd) { + case BTTV_VERSION: + return BTTV_VERSION_CODE; + + /* *** v4l1 *** ************************************************ */ + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = btv->freq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + down(&btv->lock); + btv->freq=*freq; + bttv_call_i2c_clients(btv,VIDIOCSFREQ,freq); + if (btv->has_matchbox && btv->radio_user) + tea5757_set_freq(btv,*freq); + up(&btv->lock); + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + + if (UNSET == bttv_tvcards[btv->c.type].tuner) + return -EINVAL; + if (v->tuner) /* Only tuner 0 */ + return -EINVAL; + strcpy(v->name, "Television"); + v->rangelow = 0; + v->rangehigh = 0x7FFFFFFF; + v->flags = VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + v->mode = btv->tvnorm; + v->signal = (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) ? 0xFFFF : 0; + bttv_call_i2c_clients(btv,cmd,v); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + + if (v->tuner) /* Only tuner 0 */ + return -EINVAL; + if (v->mode >= BTTV_TVNORMS) + return -EINVAL; + + down(&btv->lock); + set_tvnorm(btv,v->mode); + bttv_call_i2c_clients(btv,cmd,v); + up(&btv->lock); + return 0; + } + + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + unsigned int channel = v->channel; + + if (channel >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + v->tuners=0; + v->flags = VIDEO_VC_AUDIO; + v->type = VIDEO_TYPE_CAMERA; + v->norm = btv->tvnorm; + if (channel == bttv_tvcards[btv->c.type].tuner) { + strcpy(v->name,"Television"); + v->flags|=VIDEO_VC_TUNER; + v->type=VIDEO_TYPE_TV; + v->tuners=1; + } else if (channel == btv->svhs) { + strcpy(v->name,"S-Video"); + } else { + sprintf(v->name,"Composite%d",channel); + } + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + unsigned int channel = v->channel; + + if (channel >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + if (v->norm >= BTTV_TVNORMS) + return -EINVAL; + + down(&btv->lock); + if (channel == btv->input && + v->norm == btv->tvnorm) { + /* nothing to do */ + up(&btv->lock); + return 0; + } + + btv->tvnorm = v->norm; + set_input(btv,v->channel); + up(&btv->lock); + return 0; + } + + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + + memset(v,0,sizeof(*v)); + strcpy(v->name,"Television"); + v->flags |= VIDEO_AUDIO_MUTABLE; + v->mode = VIDEO_SOUND_MONO; + + down(&btv->lock); + bttv_call_i2c_clients(btv,cmd,v); + + /* card specific hooks */ + if (btv->audio_hook) + btv->audio_hook(btv,v,0); + + up(&btv->lock); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + unsigned int audio = v->audio; + + if (audio >= bttv_tvcards[btv->c.type].audio_inputs) + return -EINVAL; + + down(&btv->lock); + audio_mux(btv, (v->flags&VIDEO_AUDIO_MUTE) ? AUDIO_MUTE : AUDIO_UNMUTE); + bttv_call_i2c_clients(btv,cmd,v); + + /* card specific hooks */ + if (btv->audio_hook) + btv->audio_hook(btv,v,1); + + up(&btv->lock); + return 0; + } + + /* *** v4l2 *** ************************************************ */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int index = e->index; + + if (index >= BTTV_TVNORMS) + return -EINVAL; + v4l2_video_std_construct(e, bttv_tvnorms[e->index].v4l2_id, + bttv_tvnorms[e->index].name); + e->index = index; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = bttv_tvnorms[btv->tvnorm].v4l2_id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + + for (i = 0; i < BTTV_TVNORMS; i++) + if (*id & bttv_tvnorms[i].v4l2_id) + break; + if (i == BTTV_TVNORMS) + return -EINVAL; + + down(&btv->lock); + set_tvnorm(btv,i); + i2c_vidiocschan(btv); + up(&btv->lock); + return 0; + } + case VIDIOC_QUERYSTD: + { + v4l2_std_id *id = arg; + + if (btread(BT848_DSTATUS) & BT848_DSTATUS_NUML) + *id = V4L2_STD_625_50; + else + *id = V4L2_STD_525_60; + return 0; + } + + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + unsigned int n; + + n = i->index; + if (n >= bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + memset(i,0,sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + i->audioset = 1; + if (i->index == bttv_tvcards[btv->c.type].tuner) { + sprintf(i->name, "Television"); + i->type = V4L2_INPUT_TYPE_TUNER; + i->tuner = 0; + } else if (i->index == btv->svhs) { + sprintf(i->name, "S-Video"); + } else { + sprintf(i->name,"Composite%d",i->index); + } + if (i->index == btv->input) { + __u32 dstatus = btread(BT848_DSTATUS); + if (0 == (dstatus & BT848_DSTATUS_PRES)) + i->status |= V4L2_IN_ST_NO_SIGNAL; + if (0 == (dstatus & BT848_DSTATUS_HLOC)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + } + for (n = 0; n < BTTV_TVNORMS; n++) + i->std |= bttv_tvnorms[n].v4l2_id; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = btv->input; + return 0; + } + case VIDIOC_S_INPUT: + { + unsigned int *i = arg; + + if (*i > bttv_tvcards[btv->c.type].video_inputs) + return -EINVAL; + down(&btv->lock); + set_input(btv,*i); + up(&btv->lock); + return 0; + } + + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + + if (UNSET == bttv_tvcards[btv->c.type].tuner) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + down(&btv->lock); + memset(t,0,sizeof(*t)); + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->rangehigh = 0xffffffffUL; + t->capability = V4L2_TUNER_CAP_NORM; + t->rxsubchans = V4L2_TUNER_SUB_MONO; + if (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) + t->signal = 0xffff; + { + /* Hmmm ... */ + struct video_audio va; + memset(&va, 0, sizeof(struct video_audio)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + if(va.mode & VIDEO_SOUND_STEREO) { + t->audmode = V4L2_TUNER_MODE_STEREO; + t->rxsubchans |= V4L2_TUNER_SUB_STEREO; + } + if(va.mode & VIDEO_SOUND_LANG1) { + t->audmode = V4L2_TUNER_MODE_LANG1; + t->rxsubchans = V4L2_TUNER_SUB_LANG1 + | V4L2_TUNER_SUB_LANG2; + } + } + /* FIXME: fill capability+audmode */ + up(&btv->lock); + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + + if (UNSET == bttv_tvcards[btv->c.type].tuner) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + down(&btv->lock); + { + struct video_audio va; + memset(&va, 0, sizeof(struct video_audio)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (t->audmode == V4L2_TUNER_MODE_MONO) + va.mode = VIDEO_SOUND_MONO; + else if (t->audmode == V4L2_TUNER_MODE_STEREO) + va.mode = VIDEO_SOUND_STEREO; + else if (t->audmode == V4L2_TUNER_MODE_LANG1) + va.mode = VIDEO_SOUND_LANG1; + else if (t->audmode == V4L2_TUNER_MODE_LANG2) + va.mode = VIDEO_SOUND_LANG2; + bttv_call_i2c_clients(btv, VIDIOCSAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,1); + } + up(&btv->lock); + return 0; + } + + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + memset(f,0,sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = btv->freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (unlikely(f->tuner != 0)) + return -EINVAL; + if (unlikely(f->type != V4L2_TUNER_ANALOG_TV)) + return -EINVAL; + down(&btv->lock); + btv->freq = f->frequency; + bttv_call_i2c_clients(btv,VIDIOCSFREQ,&btv->freq); + if (btv->has_matchbox && btv->radio_user) + tea5757_set_freq(btv,btv->freq); + up(&btv->lock); + return 0; + } + + default: + return -ENOIOCTLCMD; + + } + return 0; +} + +static int verify_window(const struct bttv_tvnorm *tvn, + struct v4l2_window *win, int fixup) +{ + enum v4l2_field field; + int maxw, maxh; + + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = tvn->swidth; + maxh = tvn->sheight; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + if (!fixup && (win->w.width > maxw || win->w.height > maxh)) + return -EINVAL; + + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + win->field = field; + return 0; +} + +static int setup_window(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_window *win, int fixup) +{ + struct v4l2_clip *clips = NULL; + int n,size,retval = 0; + + if (NULL == fh->ovfmt) + return -EINVAL; + if (!(fh->ovfmt->flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + retval = verify_window(&bttv_tvnorms[btv->tvnorm],win,fixup); + if (0 != retval) + return retval; + + /* copy clips -- luckily v4l1 + v4l2 are binary + compatible here ...*/ + n = win->clipcount; + size = sizeof(*clips)*(n+4); + clips = kmalloc(size,GFP_KERNEL); + if (NULL == clips) + return -ENOMEM; + if (n > 0) { + if (copy_from_user(clips,win->clips,sizeof(struct v4l2_clip)*n)) { + kfree(clips); + return -EFAULT; + } + } + /* clip against screen */ + if (NULL != btv->fbuf.base) + n = btcx_screen_clips(btv->fbuf.fmt.width, btv->fbuf.fmt.height, + &win->w, clips, n); + btcx_sort_clips(clips,n); + + /* 4-byte alignments */ + switch (fh->ovfmt->depth) { + case 8: + case 24: + btcx_align(&win->w, clips, n, 3); + break; + case 16: + btcx_align(&win->w, clips, n, 1); + break; + case 32: + /* no alignment fixups needed */ + break; + default: + BUG(); + } + + down(&fh->cap.lock); + if (fh->ov.clips) + kfree(fh->ov.clips); + fh->ov.clips = clips; + fh->ov.nclips = n; + + fh->ov.w = win->w; + fh->ov.field = win->field; + fh->ov.setup_ok = 1; + btv->init.ov.w.width = win->w.width; + btv->init.ov.w.height = win->w.height; + btv->init.ov.field = win->field; + + /* update overlay if needed */ + retval = 0; + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + retval = bttv_switch_overlay(btv,fh,new); + } + up(&fh->cap.lock); + return retval; +} + +/* ----------------------------------------------------------------------- */ + +static struct videobuf_queue* bttv_queue(struct bttv_fh *fh) +{ + struct videobuf_queue* q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int bttv_resource(struct bttv_fh *fh) +{ + int res = 0; + + 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; +} + +static int bttv_switch_type(struct bttv_fh *fh, enum v4l2_buf_type type) +{ + struct videobuf_queue *q = bttv_queue(fh); + int res = bttv_resource(fh); + + if (check_btres(fh,res)) + return -EBUSY; + if (videobuf_queue_is_busy(q)) + return -EBUSY; + fh->type = type; + return 0; +} + +static int bttv_g_fmt(struct bttv_fh *fh, struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(&f->fmt.pix,0,sizeof(struct v4l2_pix_format)); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + memset(&f->fmt.win,0,sizeof(struct v4l2_window)); + f->fmt.win.w = fh->ov.w; + f->fmt.win.field = fh->ov.field; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + bttv_vbi_get_fmt(fh,f); + return 0; + default: + return -EINVAL; + } +} + +static int bttv_try_fmt(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + const struct bttv_format *fmt; + enum v4l2_field field; + unsigned int maxw,maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* fixup format */ + maxw = bttv_tvnorms[btv->tvnorm].swidth; + maxh = bttv_tvnorms[btv->tvnorm].sheight; + field = f->fmt.pix.field; + if (V4L2_FIELD_ANY == field) + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + if (V4L2_FIELD_SEQ_BT == field) + field = V4L2_FIELD_SEQ_TB; + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_ALTERNATE: + maxh = maxh/2; + break; + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_SEQ_TB: + if (fmt->flags & FORMAT_FLAGS_PLANAR) + return -EINVAL; + break; + default: + return -EINVAL; + } + + /* update data for the application */ + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + return verify_window(&bttv_tvnorms[btv->tvnorm], + &f->fmt.win, 1); + case V4L2_BUF_TYPE_VBI_CAPTURE: + bttv_vbi_try_fmt(fh,f); + return 0; + default: + return -EINVAL; + } +} + +static int bttv_s_fmt(struct bttv_fh *fh, struct bttv *btv, + struct v4l2_format *f) +{ + int retval; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + const struct bttv_format *fmt; + + retval = bttv_switch_type(fh,f->type); + if (0 != retval) + return retval; + retval = bttv_try_fmt(fh,btv,f); + if (0 != retval) + return retval; + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + + /* update our state informations */ + down(&fh->cap.lock); + fh->fmt = fmt; + fh->cap.field = f->fmt.pix.field; + fh->cap.last = V4L2_FIELD_NONE; + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + btv->init.fmt = fmt; + btv->init.width = f->fmt.pix.width; + btv->init.height = f->fmt.pix.height; + up(&fh->cap.lock); + + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + return setup_window(fh, btv, &f->fmt.win, 1); + case V4L2_BUF_TYPE_VBI_CAPTURE: + retval = bttv_switch_type(fh,f->type); + if (0 != retval) + return retval; + if (locked_btres(fh->btv, RESOURCE_VBI)) + return -EBUSY; + bttv_vbi_try_fmt(fh,f); + bttv_vbi_setlines(fh,btv,f->fmt.vbi.count[0]); + bttv_vbi_get_fmt(fh,f); + return 0; + default: + return -EINVAL; + } +} + +static int bttv_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + unsigned long flags; + int retval = 0; + + if (bttv_debug > 1) { + switch (_IOC_TYPE(cmd)) { + case 'v': + printk("bttv%d: ioctl 0x%x (v4l1, VIDIOC%s)\n", + btv->c.nr, cmd, (_IOC_NR(cmd) < V4L1_IOCTLS) ? + v4l1_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'V': + printk("bttv%d: ioctl 0x%x (v4l2, %s)\n", + btv->c.nr, cmd, v4l2_ioctl_names[_IOC_NR(cmd)]); + break; + default: + printk("bttv%d: ioctl 0x%x (???)\n", + btv->c.nr, cmd); + } + } + if (btv->errors) + bttv_reinit_bt848(btv); + + switch (cmd) { + case VIDIOCSFREQ: + case VIDIOCSTUNER: + case VIDIOCSCHAN: + case VIDIOC_S_CTRL: + case VIDIOC_S_STD: + case VIDIOC_S_INPUT: + case VIDIOC_S_TUNER: + case VIDIOC_S_FREQUENCY: + retval = v4l2_prio_check(&btv->prio,&fh->prio); + if (0 != retval) + return retval; + }; + + switch (cmd) { + + /* *** v4l1 *** ************************************************ */ + case VIDIOCGCAP: + { + struct video_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->name,btv->video_dev->name); + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + /* vbi */ + cap->type = VID_TYPE_TUNER|VID_TYPE_TELETEXT; + } else { + /* others */ + cap->type = VID_TYPE_CAPTURE| + VID_TYPE_TUNER| + VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING| + VID_TYPE_SCALES; + cap->maxwidth = bttv_tvnorms[btv->tvnorm].swidth; + cap->maxheight = bttv_tvnorms[btv->tvnorm].sheight; + cap->minwidth = 48; + cap->minheight = 32; + } + cap->channels = bttv_tvcards[btv->c.type].video_inputs; + cap->audios = bttv_tvcards[btv->c.type].audio_inputs; + return 0; + } + + case VIDIOCGPICT: + { + struct video_picture *pic = arg; + + memset(pic,0,sizeof(*pic)); + pic->brightness = btv->bright; + pic->contrast = btv->contrast; + pic->hue = btv->hue; + pic->colour = btv->saturation; + if (fh->fmt) { + pic->depth = fh->fmt->depth; + pic->palette = fh->fmt->palette; + } + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *pic = arg; + const struct bttv_format *fmt; + + fmt = format_by_palette(pic->palette); + if (NULL == fmt) + return -EINVAL; + down(&fh->cap.lock); + if (fmt->depth != pic->depth) { + retval = -EINVAL; + goto fh_unlock_and_return; + } + fh->ovfmt = fmt; + fh->fmt = fmt; + btv->init.ovfmt = fmt; + btv->init.fmt = fmt; + if (bigendian) { + /* dirty hack time: swap bytes for overlay if the + display adaptor is big endian (insmod option) */ + if (fmt->palette == VIDEO_PALETTE_RGB555 || + fmt->palette == VIDEO_PALETTE_RGB565 || + fmt->palette == VIDEO_PALETTE_RGB32) { + fh->ovfmt = fmt+1; + } + } + bt848_bright(btv,pic->brightness); + bt848_contrast(btv,pic->contrast); + bt848_hue(btv,pic->hue); + bt848_sat(btv,pic->colour); + up(&fh->cap.lock); + return 0; + } + + case VIDIOCGWIN: + { + struct video_window *win = arg; + + memset(win,0,sizeof(*win)); + win->x = fh->ov.w.left; + win->y = fh->ov.w.top; + win->width = fh->ov.w.width; + win->height = fh->ov.w.height; + return 0; + } + case VIDIOCSWIN: + { + struct video_window *win = arg; + struct v4l2_window w2; + + w2.field = V4L2_FIELD_ANY; + w2.w.left = win->x; + w2.w.top = win->y; + w2.w.width = win->width; + w2.w.height = win->height; + w2.clipcount = win->clipcount; + w2.clips = (struct v4l2_clip __user *)win->clips; + retval = setup_window(fh, btv, &w2, 0); + if (0 == retval) { + /* on v4l1 this ioctl affects the read() size too */ + fh->width = fh->ov.w.width; + fh->height = fh->ov.w.height; + btv->init.width = fh->ov.w.width; + btv->init.height = fh->ov.w.height; + } + return retval; + } + + case VIDIOCGFBUF: + { + struct video_buffer *fbuf = arg; + + fbuf->base = btv->fbuf.base; + fbuf->width = btv->fbuf.fmt.width; + fbuf->height = btv->fbuf.fmt.height; + fbuf->bytesperline = btv->fbuf.fmt.bytesperline; + if (fh->ovfmt) + fbuf->depth = fh->ovfmt->depth; + return 0; + } + case VIDIOCSFBUF: + { + struct video_buffer *fbuf = arg; + const struct bttv_format *fmt; + unsigned long end; + + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + end = (unsigned long)fbuf->base + + fbuf->height * fbuf->bytesperline; + down(&fh->cap.lock); + retval = -EINVAL; + + switch (fbuf->depth) { + case 8: + fmt = format_by_palette(VIDEO_PALETTE_HI240); + break; + case 16: + fmt = format_by_palette(VIDEO_PALETTE_RGB565); + break; + case 24: + fmt = format_by_palette(VIDEO_PALETTE_RGB24); + break; + case 32: + fmt = format_by_palette(VIDEO_PALETTE_RGB32); + break; + case 15: + fbuf->depth = 16; + fmt = format_by_palette(VIDEO_PALETTE_RGB555); + break; + default: + fmt = NULL; + break; + } + if (NULL == fmt) + goto fh_unlock_and_return; + + fh->ovfmt = fmt; + fh->fmt = fmt; + btv->init.ovfmt = fmt; + btv->init.fmt = fmt; + btv->fbuf.base = fbuf->base; + btv->fbuf.fmt.width = fbuf->width; + btv->fbuf.fmt.height = fbuf->height; + if (fbuf->bytesperline) + btv->fbuf.fmt.bytesperline = fbuf->bytesperline; + else + btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fbuf->depth/8; + up(&fh->cap.lock); + return 0; + } + + case VIDIOCCAPTURE: + case VIDIOC_OVERLAY: + { + struct bttv_buffer *new; + int *on = arg; + + if (*on) { + /* verify args */ + if (NULL == btv->fbuf.base) + return -EINVAL; + if (!fh->ov.setup_ok) { + dprintk("bttv%d: overlay: !setup_ok\n",btv->c.nr); + return -EINVAL; + } + } + + if (!check_alloc_btres(btv,fh,RESOURCE_OVERLAY)) + return -EBUSY; + + down(&fh->cap.lock); + if (*on) { + fh->ov.tvnorm = btv->tvnorm; + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + } else { + new = NULL; + } + + /* switch over */ + retval = bttv_switch_overlay(btv,fh,new); + up(&fh->cap.lock); + return retval; + } + + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + unsigned int i; + + down(&fh->cap.lock); + retval = videobuf_mmap_setup(&fh->cap,gbuffers,gbufsize, + V4L2_MEMORY_MMAP); + if (retval < 0) + goto fh_unlock_and_return; + memset(mbuf,0,sizeof(*mbuf)); + mbuf->frames = gbuffers; + mbuf->size = gbuffers * gbufsize; + for (i = 0; i < gbuffers; i++) + mbuf->offsets[i] = i * gbufsize; + up(&fh->cap.lock); + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + struct bttv_buffer *buf; + enum v4l2_field field; + + if (vm->frame >= VIDEO_MAX_FRAME) + return -EINVAL; + + down(&fh->cap.lock); + retval = -EINVAL; + buf = (struct bttv_buffer *)fh->cap.bufs[vm->frame]; + if (NULL == buf) + goto fh_unlock_and_return; + if (0 == buf->vb.baddr) + goto fh_unlock_and_return; + if (buf->vb.state == STATE_QUEUED || + buf->vb.state == STATE_ACTIVE) + goto fh_unlock_and_return; + + field = (vm->height > bttv_tvnorms[btv->tvnorm].sheight/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + retval = bttv_prepare_buffer(btv,buf, + format_by_palette(vm->format), + vm->width,vm->height,field); + if (0 != retval) + goto fh_unlock_and_return; + spin_lock_irqsave(&btv->s_lock,flags); + buffer_queue(&fh->cap,&buf->vb); + spin_unlock_irqrestore(&btv->s_lock,flags); + up(&fh->cap.lock); + return 0; + } + case VIDIOCSYNC: + { + int *frame = arg; + struct bttv_buffer *buf; + + if (*frame >= VIDEO_MAX_FRAME) + return -EINVAL; + + down(&fh->cap.lock); + retval = -EINVAL; + buf = (struct bttv_buffer *)fh->cap.bufs[*frame]; + if (NULL == buf) + goto fh_unlock_and_return; + retval = videobuf_waiton(&buf->vb,0,1); + if (0 != retval) + goto fh_unlock_and_return; + switch (buf->vb.state) { + case STATE_ERROR: + retval = -EIO; + /* fall through */ + case STATE_DONE: + videobuf_dma_pci_sync(btv->c.pci,&buf->vb.dma); + bttv_dma_free(btv,buf); + break; + default: + retval = -EINVAL; + break; + } + up(&fh->cap.lock); + return retval; + } + + case VIDIOCGVBIFMT: + { + struct vbi_format *fmt = (void *) arg; + struct v4l2_format fmt2; + + if (fh->type != V4L2_BUF_TYPE_VBI_CAPTURE) { + retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); + if (0 != retval) + return retval; + } + bttv_vbi_get_fmt(fh, &fmt2); + + memset(fmt,0,sizeof(*fmt)); + fmt->sampling_rate = fmt2.fmt.vbi.sampling_rate; + fmt->samples_per_line = fmt2.fmt.vbi.samples_per_line; + fmt->sample_format = VIDEO_PALETTE_RAW; + fmt->start[0] = fmt2.fmt.vbi.start[0]; + fmt->count[0] = fmt2.fmt.vbi.count[0]; + fmt->start[1] = fmt2.fmt.vbi.start[1]; + fmt->count[1] = fmt2.fmt.vbi.count[1]; + if (fmt2.fmt.vbi.flags & VBI_UNSYNC) + fmt->flags |= V4L2_VBI_UNSYNC; + if (fmt2.fmt.vbi.flags & VBI_INTERLACED) + fmt->flags |= V4L2_VBI_INTERLACED; + return 0; + } + case VIDIOCSVBIFMT: + { + struct vbi_format *fmt = (void *) arg; + struct v4l2_format fmt2; + + retval = bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); + if (0 != retval) + return retval; + bttv_vbi_get_fmt(fh, &fmt2); + + if (fmt->sampling_rate != fmt2.fmt.vbi.sampling_rate || + fmt->samples_per_line != fmt2.fmt.vbi.samples_per_line || + fmt->sample_format != VIDEO_PALETTE_RAW || + fmt->start[0] != fmt2.fmt.vbi.start[0] || + fmt->start[1] != fmt2.fmt.vbi.start[1] || + fmt->count[0] != fmt->count[1] || + fmt->count[0] < 1 || + fmt->count[0] > 32 /* VBI_MAXLINES */) + return -EINVAL; + + bttv_vbi_setlines(fh,btv,fmt->count[0]); + return 0; + } + + case BTTV_VERSION: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGCHAN: + case VIDIOCSCHAN: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return bttv_common_ioctls(btv,cmd,arg); + + /* *** v4l2 *** ************************************************ */ + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + if (0 == v4l2) + return -EINVAL; + strcpy(cap->driver,"bttv"); + strlcpy(cap->card,btv->video_dev->name,sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(btv->c.pci)); + cap->version = BTTV_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + if (bttv_tvcards[btv->c.type].tuner != UNSET && + bttv_tvcards[btv->c.type].tuner != TUNER_ABSENT) + cap->capabilities |= V4L2_CAP_TUNER; + return 0; + } + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + enum v4l2_buf_type type; + unsigned int i; + int index; + + type = f->type; + if (V4L2_BUF_TYPE_VBI_CAPTURE == type) { + /* vbi */ + index = f->index; + if (0 != index) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + f->pixelformat = V4L2_PIX_FMT_GREY; + strcpy(f->description,"vbi data"); + return 0; + } + + /* video capture + overlay */ + index = -1; + for (i = 0; i < BTTV_FORMATS; i++) { + if (bttv_formats[i].fourcc != -1) + index++; + if ((unsigned int)index == f->index) + break; + } + if (BTTV_FORMATS == i) + return -EINVAL; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (!(bttv_formats[i].flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + break; + default: + return -EINVAL; + } + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + f->pixelformat = bttv_formats[i].fourcc; + strlcpy(f->description,bttv_formats[i].name,sizeof(f->description)); + return 0; + } + + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + return bttv_try_fmt(fh,btv,f); + } + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + return bttv_g_fmt(fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + return bttv_s_fmt(fh,btv,f); + } + + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + *fb = btv->fbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + if (fh->ovfmt) + fb->fmt.pixelformat = fh->ovfmt->fourcc; + return 0; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + const struct bttv_format *fmt; + + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + if (0 == (fmt->flags & FORMAT_FLAGS_PACKED)) + return -EINVAL; + + down(&fh->cap.lock); + retval = -EINVAL; + if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) { + if (fb->fmt.width > bttv_tvnorms[btv->tvnorm].swidth) + goto fh_unlock_and_return; + if (fb->fmt.height > bttv_tvnorms[btv->tvnorm].sheight) + goto fh_unlock_and_return; + } + + /* ok, accept it */ + btv->fbuf.base = fb->base; + btv->fbuf.fmt.width = fb->fmt.width; + btv->fbuf.fmt.height = fb->fmt.height; + if (0 != fb->fmt.bytesperline) + btv->fbuf.fmt.bytesperline = fb->fmt.bytesperline; + else + btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fmt->depth/8; + + retval = 0; + fh->ovfmt = fmt; + btv->init.ovfmt = fmt; + if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) { + fh->ov.w.left = 0; + fh->ov.w.top = 0; + fh->ov.w.width = fb->fmt.width; + fh->ov.w.height = fb->fmt.height; + btv->init.ov.w.width = fb->fmt.width; + btv->init.ov.w.height = fb->fmt.height; + if (fh->ov.clips) + kfree(fh->ov.clips); + fh->ov.clips = NULL; + fh->ov.nclips = 0; + + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv,&fh->ov,fh->ovfmt,new); + retval = bttv_switch_overlay(btv,fh,new); + } + } + up(&fh->cap.lock); + return retval; + } + + case VIDIOC_REQBUFS: + return videobuf_reqbufs(bttv_queue(fh),arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(bttv_queue(fh),arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(bttv_queue(fh),arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(bttv_queue(fh),arg, + file->f_flags & O_NONBLOCK); + + case VIDIOC_STREAMON: + { + int res = bttv_resource(fh); + + if (!check_alloc_btres(btv,fh,res)) + return -EBUSY; + return videobuf_streamon(bttv_queue(fh)); + } + case VIDIOC_STREAMOFF: + { + int res = bttv_resource(fh); + + retval = videobuf_streamoff(bttv_queue(fh)); + if (retval < 0) + return retval; + free_btres(btv,fh,res); + return 0; + } + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *c = arg; + int i; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + for (i = 0; i < BTTV_CTLS; i++) + if (bttv_ctls[i].id == c->id) + break; + if (i == BTTV_CTLS) { + *c = no_ctl; + return 0; + } + *c = bttv_ctls[i]; + if (i >= 4 && i <= 8) { + struct video_audio va; + memset(&va,0,sizeof(va)); + bttv_call_i2c_clients(btv, VIDIOCGAUDIO, &va); + if (btv->audio_hook) + btv->audio_hook(btv,&va,0); + switch (bttv_ctls[i].id) { + case V4L2_CID_AUDIO_VOLUME: + if (!(va.flags & VIDEO_AUDIO_VOLUME)) + *c = no_ctl; + break; + case V4L2_CID_AUDIO_BALANCE: + if (!(va.flags & VIDEO_AUDIO_BALANCE)) + *c = no_ctl; + break; + case V4L2_CID_AUDIO_BASS: + if (!(va.flags & VIDEO_AUDIO_BASS)) + *c = no_ctl; + break; + case V4L2_CID_AUDIO_TREBLE: + if (!(va.flags & VIDEO_AUDIO_TREBLE)) + *c = no_ctl; + break; + } + } + return 0; + } + case VIDIOC_G_CTRL: + return get_control(btv,arg); + case VIDIOC_S_CTRL: + return set_control(btv,arg); + case VIDIOC_G_PARM: + { + struct v4l2_streamparm *parm = arg; + struct v4l2_standard s; + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + memset(parm,0,sizeof(*parm)); + v4l2_video_std_construct(&s, bttv_tvnorms[btv->tvnorm].v4l2_id, + bttv_tvnorms[btv->tvnorm].name); + parm->parm.capture.timeperframe = s.frameperiod; + return 0; + } + + case VIDIOC_G_PRIORITY: + { + enum v4l2_priority *p = arg; + + *p = v4l2_prio_max(&btv->prio); + return 0; + } + case VIDIOC_S_PRIORITY: + { + enum v4l2_priority *prio = arg; + + return v4l2_prio_change(&btv->prio, &fh->prio, *prio); + } + + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + return bttv_common_ioctls(btv,cmd,arg); + + default: + return -ENOIOCTLCMD; + } + return 0; + + fh_unlock_and_return: + up(&fh->cap.lock); + return retval; +} + +static int bttv_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct bttv_fh *fh = file->private_data; + + switch (cmd) { + case BTTV_VBISIZE: + bttv_switch_type(fh,V4L2_BUF_TYPE_VBI_CAPTURE); + return fh->lines * 2 * 2048; + default: + return video_usercopy(inode, file, cmd, arg, bttv_do_ioctl); + } +} + +static ssize_t bttv_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct bttv_fh *fh = file->private_data; + int retval = 0; + + if (fh->btv->errors) + bttv_reinit_bt848(fh->btv); + dprintk("bttv%d: read count=%d type=%s\n", + fh->btv->c.nr,(int)count,v4l2_type_names[fh->type]); + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (locked_btres(fh->btv,RESOURCE_VIDEO)) + return -EBUSY; + retval = videobuf_read_one(&fh->cap, data, count, ppos, + file->f_flags & O_NONBLOCK); + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI)) + return -EBUSY; + retval = videobuf_read_stream(&fh->vbi, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + break; + default: + BUG(); + } + return retval; +} + +static unsigned int bttv_poll(struct file *file, poll_table *wait) +{ + struct bttv_fh *fh = file->private_data; + struct bttv_buffer *buf; + enum v4l2_field field; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + if (!check_alloc_btres(fh->btv,fh,RESOURCE_VBI)) + return POLLERR; + return videobuf_poll_stream(file, &fh->vbi, wait); + } + + if (check_btres(fh,RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->cap.stream)) + return POLLERR; + buf = list_entry(fh->cap.stream.next,struct bttv_buffer,vb.stream); + } else { + /* read() capture */ + down(&fh->cap.lock); + if (NULL == fh->cap.read_buf) { + /* need to capture a new frame */ + if (locked_btres(fh->btv,RESOURCE_VIDEO)) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.read_buf = videobuf_alloc(fh->cap.msize); + if (NULL == fh->cap.read_buf) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.read_buf->memory = V4L2_MEMORY_USERPTR; + field = videobuf_next_field(&fh->cap); + if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,field)) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf); + fh->cap.read_off = 0; + } + up(&fh->cap.lock); + buf = (struct bttv_buffer*)fh->cap.read_buf; + } + + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == STATE_DONE || + buf->vb.state == STATE_ERROR) + return POLLIN|POLLRDNORM; + return 0; +} + +static int bttv_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct bttv *btv = NULL; + struct bttv_fh *fh; + enum v4l2_buf_type type = 0; + unsigned int i; + + dprintk(KERN_DEBUG "bttv: open minor=%d\n",minor); + + for (i = 0; i < bttv_num; i++) { + if (bttvs[i].video_dev && + bttvs[i].video_dev->minor == minor) { + btv = &bttvs[i]; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + } + if (bttvs[i].vbi_dev && + bttvs[i].vbi_dev->minor == minor) { + btv = &bttvs[i]; + type = V4L2_BUF_TYPE_VBI_CAPTURE; + break; + } + } + if (NULL == btv) + return -ENODEV; + + dprintk(KERN_DEBUG "bttv%d: open called (type=%s)\n", + btv->c.nr,v4l2_type_names[type]); + + /* allocate per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + file->private_data = fh; + *fh = btv->init; + fh->type = type; + fh->ov.setup_ok = 0; + v4l2_prio_open(&btv->prio,&fh->prio); + + videobuf_queue_init(&fh->cap, &bttv_video_qops, + btv->c.pci, &btv->s_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct bttv_buffer), + fh); + videobuf_queue_init(&fh->vbi, &bttv_vbi_qops, + btv->c.pci, &btv->s_lock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct bttv_buffer), + fh); + i2c_vidiocschan(btv); + + btv->users++; + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + bttv_vbi_setlines(fh,btv,16); + bttv_field_count(btv); + return 0; +} + +static int bttv_release(struct inode *inode, struct file *file) +{ + struct bttv_fh *fh = file->private_data; + struct bttv *btv = fh->btv; + + /* turn off overlay */ + if (check_btres(fh, RESOURCE_OVERLAY)) + bttv_switch_overlay(btv,fh,NULL); + + /* stop video capture */ + if (check_btres(fh, RESOURCE_VIDEO)) { + videobuf_streamoff(&fh->cap); + free_btres(btv,fh,RESOURCE_VIDEO); + } + if (fh->cap.read_buf) { + buffer_release(&fh->cap,fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + + /* stop vbi capture */ + if (check_btres(fh, RESOURCE_VBI)) { + if (fh->vbi.streaming) + videobuf_streamoff(&fh->vbi); + if (fh->vbi.reading) + videobuf_read_stop(&fh->vbi); + free_btres(btv,fh,RESOURCE_VBI); + } + + /* free stuff */ + videobuf_mmap_free(&fh->cap); + videobuf_mmap_free(&fh->vbi); + v4l2_prio_close(&btv->prio,&fh->prio); + file->private_data = NULL; + kfree(fh); + + btv->users--; + bttv_field_count(btv); + return 0; +} + +static int +bttv_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct bttv_fh *fh = file->private_data; + + dprintk("bttv%d: mmap type=%s 0x%lx+%ld\n", + fh->btv->c.nr, v4l2_type_names[fh->type], + vma->vm_start, vma->vm_end - vma->vm_start); + return videobuf_mmap_mapper(bttv_queue(fh),vma); +} + +static struct file_operations bttv_fops = +{ + .owner = THIS_MODULE, + .open = bttv_open, + .release = bttv_release, + .ioctl = bttv_ioctl, + .llseek = no_llseek, + .read = bttv_read, + .mmap = bttv_mmap, + .poll = bttv_poll, +}; + +static struct video_device bttv_video_template = +{ + .name = "UNSET", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING|VID_TYPE_SCALES, + .hardware = VID_HARDWARE_BT848, + .fops = &bttv_fops, + .minor = -1, +}; + +static struct video_device bttv_vbi_template = +{ + .name = "bt848/878 vbi", + .type = VID_TYPE_TUNER|VID_TYPE_TELETEXT, + .hardware = VID_HARDWARE_BT848, + .fops = &bttv_fops, + .minor = -1, +}; + +/* ----------------------------------------------------------------------- */ +/* radio interface */ + +static int radio_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct bttv *btv = NULL; + unsigned int i; + + dprintk("bttv: open minor=%d\n",minor); + + for (i = 0; i < bttv_num; i++) { + if (bttvs[i].radio_dev->minor == minor) { + btv = &bttvs[i]; + break; + } + } + if (NULL == btv) + return -ENODEV; + + dprintk("bttv%d: open called (radio)\n",btv->c.nr); + down(&btv->lock); + if (btv->radio_user) { + up(&btv->lock); + return -EBUSY; + } + btv->radio_user++; + file->private_data = btv; + + i2c_vidiocschan(btv); + bttv_call_i2c_clients(btv,AUDC_SET_RADIO,&btv->tuner_type); + audio_mux(btv,AUDIO_RADIO); + + up(&btv->lock); + return 0; +} + +static int radio_release(struct inode *inode, struct file *file) +{ + struct bttv *btv = file->private_data; + + btv->radio_user--; + return 0; +} + +static int radio_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct bttv *btv = file->private_data; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->name,btv->radio_dev->name); + cap->type = VID_TYPE_TUNER; + cap->channels = 1; + cap->audios = 1; + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + + if(v->tuner) + return -EINVAL; + memset(v,0,sizeof(*v)); + strcpy(v->name, "Radio"); + /* japan: 76.0 MHz - 89.9 MHz + western europe: 87.5 MHz - 108.0 MHz + russia: 65.0 MHz - 108.0 MHz */ + v->rangelow=(int)(65*16); + v->rangehigh=(int)(108*16); + bttv_call_i2c_clients(btv,cmd,v); + return 0; + } + case VIDIOCSTUNER: + /* nothing to do */ + return 0; + + case BTTV_VERSION: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return bttv_common_ioctls(btv,cmd,arg); + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, radio_do_ioctl); +} + +static struct file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = radio_open, + .release = radio_release, + .ioctl = radio_ioctl, + .llseek = no_llseek, +}; + +static struct video_device radio_template = +{ + .name = "bt848/878 radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_BT848, + .fops = &radio_fops, + .minor = -1, +}; + +/* ----------------------------------------------------------------------- */ +/* some debug code */ + +int bttv_risc_decode(u32 risc) +{ + static char *instr[16] = { + [ BT848_RISC_WRITE >> 28 ] = "write", + [ BT848_RISC_SKIP >> 28 ] = "skip", + [ BT848_RISC_WRITEC >> 28 ] = "writec", + [ BT848_RISC_JUMP >> 28 ] = "jump", + [ BT848_RISC_SYNC >> 28 ] = "sync", + [ BT848_RISC_WRITE123 >> 28 ] = "write123", + [ BT848_RISC_SKIP123 >> 28 ] = "skip123", + [ BT848_RISC_WRITE1S23 >> 28 ] = "write1s23", + }; + static int incr[16] = { + [ BT848_RISC_WRITE >> 28 ] = 2, + [ BT848_RISC_JUMP >> 28 ] = 2, + [ BT848_RISC_SYNC >> 28 ] = 2, + [ BT848_RISC_WRITE123 >> 28 ] = 5, + [ BT848_RISC_SKIP123 >> 28 ] = 2, + [ BT848_RISC_WRITE1S23 >> 28 ] = 3, + }; + static char *bits[] = { + "be0", "be1", "be2", "be3/resync", + "set0", "set1", "set2", "set3", + "clr0", "clr1", "clr2", "clr3", + "irq", "res", "eol", "sol", + }; + int i; + + printk("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) + if (risc & (1 << (i + 12))) + printk(" %s",bits[i]); + printk(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +void bttv_risc_disasm(struct bttv *btv, + struct btcx_riscmem *risc) +{ + unsigned int i,j,n; + + printk("%s: risc disasm: %p [dma=0x%08lx]\n", + btv->c.name, risc->cpu, (unsigned long)risc->dma); + for (i = 0; i < (risc->size >> 2); i += n) { + printk("%s: 0x%lx: ", btv->c.name, + (unsigned long)(risc->dma + (i<<2))); + n = bttv_risc_decode(risc->cpu[i]); + for (j = 1; j < n; j++) + printk("%s: 0x%lx: 0x%08x [ arg #%d ]\n", + btv->c.name, (unsigned long)(risc->dma + ((i+j)<<2)), + risc->cpu[i+j], j); + if (0 == risc->cpu[i]) + break; + } +} + +static void bttv_print_riscaddr(struct bttv *btv) +{ + printk(" main: %08Lx\n", + (unsigned long long)btv->main.dma); + printk(" vbi : o=%08Lx e=%08Lx\n", + btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0, + btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0); + printk(" cap : o=%08Lx e=%08Lx\n", + btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); + printk(" scr : o=%08Lx e=%08Lx\n", + btv->screen ? (unsigned long long)btv->screen->top.dma : 0, + btv->screen ? (unsigned long long)btv->screen->bottom.dma : 0); + bttv_risc_disasm(btv, &btv->main); +} + +/* ----------------------------------------------------------------------- */ +/* irq handler */ + +static char *irq_name[] = { + "FMTCHG", // format change detected (525 vs. 625) + "VSYNC", // vertical sync (new field) + "HSYNC", // horizontal sync + "OFLOW", // chroma/luma AGC overflow + "HLOCK", // horizontal lock changed + "VPRES", // video presence changed + "6", "7", + "I2CDONE", // hw irc operation finished + "GPINT", // gpio port triggered irq + "10", + "RISCI", // risc instruction triggered irq + "FBUS", // pixel data fifo dropped data (high pci bus latencies) + "FTRGT", // pixel data fifo overrun + "FDSR", // fifo data stream resyncronisation + "PPERR", // parity error (data transfer) + "RIPERR", // parity error (read risc instructions) + "PABORT", // pci abort + "OCERR", // risc instruction error + "SCERR", // syncronisation error +}; + +static void bttv_print_irqbits(u32 print, u32 mark) +{ + unsigned int i; + + printk("bits:"); + for (i = 0; i < ARRAY_SIZE(irq_name); i++) { + if (print & (1 << i)) + printk(" %s",irq_name[i]); + if (mark & (1 << i)) + printk("*"); + } +} + +static void bttv_irq_debug_low_latency(struct bttv *btv, u32 rc) +{ + printk("bttv%d: irq: skipped frame [main=%lx,o_vbi=%lx,o_field=%lx,rc=%lx]\n", + btv->c.nr, + (unsigned long)btv->main.dma, + (unsigned long)btv->main.cpu[RISC_SLOT_O_VBI+1], + (unsigned long)btv->main.cpu[RISC_SLOT_O_FIELD+1], + (unsigned long)rc); + + if (0 == (btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC)) { + printk("bttv%d: Oh, there (temporarely?) is no input signal. " + "Ok, then this is harmless, don't worry ;)\n", + btv->c.nr); + return; + } + printk("bttv%d: Uhm. Looks like we have unusual high IRQ latencies.\n", + btv->c.nr); + printk("bttv%d: Lets try to catch the culpit red-handed ...\n", + btv->c.nr); + dump_stack(); +} + +static int +bttv_irq_next_video(struct bttv *btv, struct bttv_buffer_set *set) +{ + struct bttv_buffer *item; + + memset(set,0,sizeof(*set)); + + /* capture request ? */ + if (!list_empty(&btv->capture)) { + set->frame_irq = 1; + item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); + if (V4L2_FIELD_HAS_TOP(item->vb.field)) + set->top = item; + if (V4L2_FIELD_HAS_BOTTOM(item->vb.field)) + set->bottom = item; + + /* capture request for other field ? */ + if (!V4L2_FIELD_HAS_BOTH(item->vb.field) && + (item->vb.queue.next != &btv->capture)) { + item = list_entry(item->vb.queue.next, struct bttv_buffer, vb.queue); + if (!V4L2_FIELD_HAS_BOTH(item->vb.field)) { + if (NULL == set->top && + V4L2_FIELD_TOP == item->vb.field) { + set->top = item; + } + if (NULL == set->bottom && + V4L2_FIELD_BOTTOM == item->vb.field) { + set->bottom = item; + } + if (NULL != set->top && NULL != set->bottom) + set->top_irq = 2; + } + } + } + + /* screen overlay ? */ + if (NULL != btv->screen) { + if (V4L2_FIELD_HAS_BOTH(btv->screen->vb.field)) { + if (NULL == set->top && NULL == set->bottom) { + set->top = btv->screen; + set->bottom = btv->screen; + } + } else { + if (V4L2_FIELD_TOP == btv->screen->vb.field && + NULL == set->top) { + set->top = btv->screen; + } + if (V4L2_FIELD_BOTTOM == btv->screen->vb.field && + NULL == set->bottom) { + set->bottom = btv->screen; + } + } + } + + dprintk("bttv%d: next set: top=%p bottom=%p [screen=%p,irq=%d,%d]\n", + btv->c.nr,set->top, set->bottom, + btv->screen,set->frame_irq,set->top_irq); + return 0; +} + +static void +bttv_irq_wakeup_video(struct bttv *btv, struct bttv_buffer_set *wakeup, + struct bttv_buffer_set *curr, unsigned int state) +{ + struct timeval ts; + + do_gettimeofday(&ts); + + if (wakeup->top == wakeup->bottom) { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + printk("bttv%d: wakeup: both=%p\n",btv->c.nr,wakeup->top); + wakeup->top->vb.ts = ts; + wakeup->top->vb.field_count = btv->field_count; + wakeup->top->vb.state = state; + wake_up(&wakeup->top->vb.done); + } + } else { + if (NULL != wakeup->top && curr->top != wakeup->top) { + if (irq_debug > 1) + printk("bttv%d: wakeup: top=%p\n",btv->c.nr,wakeup->top); + wakeup->top->vb.ts = ts; + wakeup->top->vb.field_count = btv->field_count; + wakeup->top->vb.state = state; + wake_up(&wakeup->top->vb.done); + } + if (NULL != wakeup->bottom && curr->bottom != wakeup->bottom) { + if (irq_debug > 1) + printk("bttv%d: wakeup: bottom=%p\n",btv->c.nr,wakeup->bottom); + wakeup->bottom->vb.ts = ts; + wakeup->bottom->vb.field_count = btv->field_count; + wakeup->bottom->vb.state = state; + wake_up(&wakeup->bottom->vb.done); + } + } +} + +static void +bttv_irq_wakeup_vbi(struct bttv *btv, struct bttv_buffer *wakeup, + unsigned int state) +{ + struct timeval ts; + + if (NULL == wakeup) + return; + + do_gettimeofday(&ts); + wakeup->vb.ts = ts; + wakeup->vb.field_count = btv->field_count; + wakeup->vb.state = state; + wake_up(&wakeup->vb.done); +} + +static void bttv_irq_timeout(unsigned long data) +{ + struct bttv *btv = (struct bttv *)data; + struct bttv_buffer_set old,new; + struct bttv_buffer *ovbi; + struct bttv_buffer *item; + unsigned long flags; + + if (bttv_verbose) { + printk(KERN_INFO "bttv%d: timeout: drop=%d irq=%d/%d, risc=%08x, ", + btv->c.nr, btv->framedrop, btv->irq_me, btv->irq_total, + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(btread(BT848_INT_STAT),0); + printk("\n"); + } + + spin_lock_irqsave(&btv->s_lock,flags); + + /* deactivate stuff */ + memset(&new,0,sizeof(new)); + old = btv->curr; + ovbi = btv->cvbi; + btv->curr = new; + btv->cvbi = NULL; + btv->loop_irq = 0; + bttv_buffer_activate_video(btv, &new); + bttv_buffer_activate_vbi(btv, NULL); + bttv_set_dma(btv, 0); + + /* wake up */ + bttv_irq_wakeup_video(btv, &old, &new, STATE_ERROR); + bttv_irq_wakeup_vbi(btv, ovbi, STATE_ERROR); + + /* cancel all outstanding capture / vbi requests */ + while (!list_empty(&btv->capture)) { + item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue); + list_del(&item->vb.queue); + item->vb.state = STATE_ERROR; + wake_up(&item->vb.done); + } + while (!list_empty(&btv->vcapture)) { + item = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); + list_del(&item->vb.queue); + item->vb.state = STATE_ERROR; + wake_up(&item->vb.done); + } + + btv->errors++; + spin_unlock_irqrestore(&btv->s_lock,flags); +} + +static void +bttv_irq_wakeup_top(struct bttv *btv) +{ + struct bttv_buffer *wakeup = btv->curr.top; + + if (NULL == wakeup) + return; + + spin_lock(&btv->s_lock); + btv->curr.top_irq = 0; + btv->curr.top = NULL; + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + + do_gettimeofday(&wakeup->vb.ts); + wakeup->vb.field_count = btv->field_count; + wakeup->vb.state = STATE_DONE; + wake_up(&wakeup->vb.done); + spin_unlock(&btv->s_lock); +} + +static inline int is_active(struct btcx_riscmem *risc, u32 rc) +{ + if (rc < risc->dma) + return 0; + if (rc > risc->dma + risc->size) + return 0; + return 1; +} + +static void +bttv_irq_switch_video(struct bttv *btv) +{ + struct bttv_buffer_set new; + struct bttv_buffer_set old; + dma_addr_t rc; + + spin_lock(&btv->s_lock); + + /* new buffer set */ + bttv_irq_next_video(btv, &new); + rc = btread(BT848_RISC_COUNT); + if ((btv->curr.top && is_active(&btv->curr.top->top, rc)) || + (btv->curr.bottom && is_active(&btv->curr.bottom->bottom, rc))) { + btv->framedrop++; + if (debug_latency) + bttv_irq_debug_low_latency(btv, rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch over */ + old = btv->curr; + btv->curr = new; + btv->loop_irq &= ~1; + bttv_buffer_activate_video(btv, &new); + bttv_set_dma(btv, 0); + + /* switch input */ + if (UNSET != btv->new_input) { + video_mux(btv,btv->new_input); + btv->new_input = UNSET; + } + + /* wake up finished buffers */ + bttv_irq_wakeup_video(btv, &old, &new, STATE_DONE); + spin_unlock(&btv->s_lock); +} + +static void +bttv_irq_switch_vbi(struct bttv *btv) +{ + struct bttv_buffer *new = NULL; + struct bttv_buffer *old; + u32 rc; + + spin_lock(&btv->s_lock); + + if (!list_empty(&btv->vcapture)) + new = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue); + old = btv->cvbi; + + rc = btread(BT848_RISC_COUNT); + if (NULL != old && (is_active(&old->top, rc) || + is_active(&old->bottom, rc))) { + btv->framedrop++; + if (debug_latency) + bttv_irq_debug_low_latency(btv, rc); + spin_unlock(&btv->s_lock); + return; + } + + /* switch */ + btv->cvbi = new; + btv->loop_irq &= ~4; + bttv_buffer_activate_vbi(btv, new); + bttv_set_dma(btv, 0); + + bttv_irq_wakeup_vbi(btv, old, STATE_DONE); + spin_unlock(&btv->s_lock); +} + +static irqreturn_t bttv_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + u32 stat,astat; + u32 dstat; + int count; + struct bttv *btv; + int handled = 0; + + btv=(struct bttv *)dev_id; + count=0; + while (1) { + /* get/clear interrupt status bits */ + stat=btread(BT848_INT_STAT); + astat=stat&btread(BT848_INT_MASK); + if (!astat) + break; + handled = 1; + btwrite(stat,BT848_INT_STAT); + + /* get device status bits */ + dstat=btread(BT848_DSTATUS); + + if (irq_debug) { + printk(KERN_DEBUG "bttv%d: irq loop=%d fc=%d " + "riscs=%x, riscc=%08x, ", + btv->c.nr, count, btv->field_count, + stat>>28, btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + if (stat & BT848_INT_HLOCK) + printk(" HLOC => %s", (dstat & BT848_DSTATUS_HLOC) + ? "yes" : "no"); + if (stat & BT848_INT_VPRES) + printk(" PRES => %s", (dstat & BT848_DSTATUS_PRES) + ? "yes" : "no"); + if (stat & BT848_INT_FMTCHG) + printk(" NUML => %s", (dstat & BT848_DSTATUS_NUML) + ? "625" : "525"); + printk("\n"); + } + + if (astat&BT848_INT_VSYNC) + btv->field_count++; + + if (astat & BT848_INT_GPINT) { + wake_up(&btv->gpioq); + bttv_gpio_irq(&btv->c); + } + + if (astat & BT848_INT_I2CDONE) { + btv->i2c_done = stat; + wake_up(&btv->i2c_queue); + } + + if ((astat & BT848_INT_RISCI) && (stat & (4<<28))) + bttv_irq_switch_vbi(btv); + + if ((astat & BT848_INT_RISCI) && (stat & (2<<28))) + bttv_irq_wakeup_top(btv); + + if ((astat & BT848_INT_RISCI) && (stat & (1<<28))) + bttv_irq_switch_video(btv); + + if ((astat & BT848_INT_HLOCK) && btv->opt_automute) + audio_mux(btv, -1); + + if (astat & (BT848_INT_SCERR|BT848_INT_OCERR)) { + printk(KERN_INFO "bttv%d: %s%s @ %08x,",btv->c.nr, + (astat & BT848_INT_SCERR) ? "SCERR" : "", + (astat & BT848_INT_OCERR) ? "OCERR" : "", + btread(BT848_RISC_COUNT)); + bttv_print_irqbits(stat,astat); + printk("\n"); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + if (fdsr && astat & BT848_INT_FDSR) { + printk(KERN_INFO "bttv%d: FDSR @ %08x\n", + btv->c.nr,btread(BT848_RISC_COUNT)); + if (bttv_debug) + bttv_print_riscaddr(btv); + } + + count++; + if (count > 4) { + btwrite(0, BT848_INT_MASK); + printk(KERN_ERR + "bttv%d: IRQ lockup, cleared int mask [", btv->c.nr); + bttv_print_irqbits(stat,astat); + printk("]\n"); + } + } + btv->irq_total++; + if (handled) + btv->irq_me++; + return IRQ_RETVAL(handled); +} + + +/* ----------------------------------------------------------------------- */ +/* initialitation */ + +static struct video_device *vdev_init(struct bttv *btv, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; + vfd->dev = &btv->c.pci->dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "BT%d%s %s (%s)", + btv->id, (btv->id==848 && btv->revision==0x12) ? "A" : "", + type, bttv_tvcards[btv->c.type].name); + return vfd; +} + +static void bttv_unregister_video(struct bttv *btv) +{ + if (btv->video_dev) { + if (-1 != btv->video_dev->minor) + video_unregister_device(btv->video_dev); + else + video_device_release(btv->video_dev); + btv->video_dev = NULL; + } + if (btv->vbi_dev) { + if (-1 != btv->vbi_dev->minor) + video_unregister_device(btv->vbi_dev); + else + video_device_release(btv->vbi_dev); + btv->vbi_dev = NULL; + } + if (btv->radio_dev) { + if (-1 != btv->radio_dev->minor) + video_unregister_device(btv->radio_dev); + else + video_device_release(btv->radio_dev); + btv->radio_dev = NULL; + } +} + +/* register video4linux devices */ +static int __devinit bttv_register_video(struct bttv *btv) +{ + /* video */ + btv->video_dev = vdev_init(btv, &bttv_video_template, "video"); + if (NULL == btv->video_dev) + goto err; + if (video_register_device(btv->video_dev,VFL_TYPE_GRABBER,video_nr)<0) + goto err; + printk(KERN_INFO "bttv%d: registered device video%d\n", + btv->c.nr,btv->video_dev->minor & 0x1f); + video_device_create_file(btv->video_dev, &class_device_attr_card); + + /* vbi */ + btv->vbi_dev = vdev_init(btv, &bttv_vbi_template, "vbi"); + if (NULL == btv->vbi_dev) + goto err; + if (video_register_device(btv->vbi_dev,VFL_TYPE_VBI,vbi_nr)<0) + goto err; + printk(KERN_INFO "bttv%d: registered device vbi%d\n", + btv->c.nr,btv->vbi_dev->minor & 0x1f); + + if (!btv->has_radio) + return 0; + /* radio */ + btv->radio_dev = vdev_init(btv, &radio_template, "radio"); + if (NULL == btv->radio_dev) + goto err; + if (video_register_device(btv->radio_dev, VFL_TYPE_RADIO,radio_nr)<0) + goto err; + printk(KERN_INFO "bttv%d: registered device radio%d\n", + btv->c.nr,btv->radio_dev->minor & 0x1f); + + /* all done */ + return 0; + + err: + bttv_unregister_video(btv); + return -1; +} + + +/* on OpenFirmware machines (PowerMac at least), PCI memory cycle */ +/* response on cards with no firmware is not enabled by OF */ +static void pci_set_command(struct pci_dev *dev) +{ +#if defined(__powerpc__) + unsigned int cmd; + + pci_read_config_dword(dev, PCI_COMMAND, &cmd); + cmd = (cmd | PCI_COMMAND_MEMORY ); + pci_write_config_dword(dev, PCI_COMMAND, cmd); +#endif +} + +static int __devinit bttv_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + int result; + unsigned char lat; + struct bttv *btv; + + if (bttv_num == BTTV_MAX) + return -ENOMEM; + printk(KERN_INFO "bttv: Bt8xx card found (%d).\n", bttv_num); + btv=&bttvs[bttv_num]; + memset(btv,0,sizeof(*btv)); + btv->c.nr = bttv_num; + sprintf(btv->c.name,"bttv%d",btv->c.nr); + + /* initialize structs / fill in defaults */ + init_MUTEX(&btv->lock); + init_MUTEX(&btv->reslock); + spin_lock_init(&btv->s_lock); + spin_lock_init(&btv->gpio_lock); + init_waitqueue_head(&btv->gpioq); + init_waitqueue_head(&btv->i2c_queue); + INIT_LIST_HEAD(&btv->c.subs); + INIT_LIST_HEAD(&btv->capture); + INIT_LIST_HEAD(&btv->vcapture); + v4l2_prio_init(&btv->prio); + + init_timer(&btv->timeout); + btv->timeout.function = bttv_irq_timeout; + btv->timeout.data = (unsigned long)btv; + + btv->i2c_rc = -1; + btv->tuner_type = UNSET; + btv->pinnacle_id = UNSET; + btv->new_input = UNSET; + btv->gpioirq = 1; + btv->has_radio=radio[btv->c.nr]; + + /* pci stuff (init, get irq/mmio, ... */ + btv->c.pci = dev; + btv->id = dev->device; + if (pci_enable_device(dev)) { + printk(KERN_WARNING "bttv%d: Can't enable device.\n", + btv->c.nr); + return -EIO; + } + if (pci_set_dma_mask(dev, 0xffffffff)) { + printk(KERN_WARNING "bttv%d: No suitable DMA available.\n", + btv->c.nr); + return -EIO; + } + if (!request_mem_region(pci_resource_start(dev,0), + pci_resource_len(dev,0), + btv->c.name)) { + printk(KERN_WARNING "bttv%d: can't request iomem (0x%lx).\n", + btv->c.nr, pci_resource_start(dev,0)); + return -EBUSY; + } + pci_set_master(dev); + pci_set_command(dev); + pci_set_drvdata(dev,btv); + if (!pci_dma_supported(dev,0xffffffff)) { + printk("bttv%d: Oops: no 32bit PCI DMA ???\n", btv->c.nr); + result = -EIO; + goto fail1; + } + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &btv->revision); + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + printk(KERN_INFO "bttv%d: Bt%d (rev %d) at %s, ", + bttv_num,btv->id, btv->revision, pci_name(dev)); + printk("irq: %d, latency: %d, mmio: 0x%lx\n", + btv->c.pci->irq, lat, pci_resource_start(dev,0)); + schedule(); + + btv->bt848_mmio=ioremap(pci_resource_start(dev,0), 0x1000); + if (NULL == ioremap(pci_resource_start(dev,0), 0x1000)) { + printk("bttv%d: ioremap() failed\n", btv->c.nr); + result = -EIO; + goto fail1; + } + + /* identify card */ + bttv_idcard(btv); + + /* disable irqs, register irq handler */ + btwrite(0, BT848_INT_MASK); + result = request_irq(btv->c.pci->irq, bttv_irq, + SA_SHIRQ | SA_INTERRUPT,btv->c.name,(void *)btv); + if (result < 0) { + printk(KERN_ERR "bttv%d: can't get IRQ %d\n", + bttv_num,btv->c.pci->irq); + goto fail1; + } + + if (0 != bttv_handle_chipset(btv)) { + result = -EIO; + goto fail2; + } + + /* init options from insmod args */ + btv->opt_combfilter = combfilter; + btv->opt_lumafilter = lumafilter; + btv->opt_automute = automute; + btv->opt_chroma_agc = chroma_agc; + btv->opt_adc_crush = adc_crush; + btv->opt_vcr_hack = vcr_hack; + btv->opt_whitecrush_upper = whitecrush_upper; + btv->opt_whitecrush_lower = whitecrush_lower; + + /* fill struct bttv with some useful defaults */ + btv->init.btv = btv; + btv->init.ov.w.width = 320; + btv->init.ov.w.height = 240; + btv->init.fmt = format_by_palette(VIDEO_PALETTE_RGB24); + btv->init.width = 320; + btv->init.height = 240; + btv->init.lines = 16; + btv->input = 0; + + /* initialize hardware */ + if (bttv_gpio) + bttv_gpio_tracking(btv,"pre-init"); + + bttv_risc_init_main(btv); + init_bt848(btv); + + /* gpio */ + btwrite(0x00, BT848_GPIO_REG_INP); + btwrite(0x00, BT848_GPIO_OUT_EN); + if (bttv_verbose) + bttv_gpio_tracking(btv,"init"); + + /* needs to be done before i2c is registered */ + bttv_init_card1(btv); + + /* register i2c + gpio */ + init_bttv_i2c(btv); + + /* some card-specific stuff (needs working i2c) */ + bttv_init_card2(btv); + init_irqreg(btv); + + /* register video4linux + input */ + if (!bttv_tvcards[btv->c.type].no_video) { + bttv_register_video(btv); + bt848_bright(btv,32768); + bt848_contrast(btv,32768); + bt848_hue(btv,32768); + bt848_sat(btv,32768); + audio_mux(btv,AUDIO_MUTE); + set_input(btv,0); + } + + /* add subdevices */ + if (btv->has_remote) + bttv_sub_add_device(&btv->c, "remote"); + if (bttv_tvcards[btv->c.type].has_dvb) + bttv_sub_add_device(&btv->c, "dvb"); + + /* everything is fine */ + bttv_num++; + return 0; + + fail2: + free_irq(btv->c.pci->irq,btv); + + fail1: + if (btv->bt848_mmio) + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + pci_set_drvdata(dev,NULL); + return result; +} + +static void __devexit bttv_remove(struct pci_dev *pci_dev) +{ + struct bttv *btv = pci_get_drvdata(pci_dev); + + if (bttv_verbose) + printk("bttv%d: unloading\n",btv->c.nr); + + /* shutdown everything (DMA+IRQs) */ + btand(~15, BT848_GPIO_DMA_CTL); + btwrite(0, BT848_INT_MASK); + btwrite(~0x0, BT848_INT_STAT); + btwrite(0x0, BT848_GPIO_OUT_EN); + if (bttv_gpio) + bttv_gpio_tracking(btv,"cleanup"); + + /* tell gpio modules we are leaving ... */ + btv->shutdown=1; + wake_up(&btv->gpioq); + bttv_sub_del_devices(&btv->c); + + /* unregister i2c_bus + input */ + fini_bttv_i2c(btv); + + /* unregister video4linux */ + bttv_unregister_video(btv); + + /* free allocated memory */ + btcx_riscmem_free(btv->c.pci,&btv->main); + + /* free ressources */ + free_irq(btv->c.pci->irq,btv); + iounmap(btv->bt848_mmio); + release_mem_region(pci_resource_start(btv->c.pci,0), + pci_resource_len(btv->c.pci,0)); + + pci_set_drvdata(pci_dev, NULL); + return; +} + +static int bttv_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct bttv *btv = pci_get_drvdata(pci_dev); + struct bttv_buffer_set idle; + unsigned long flags; + + dprintk("bttv%d: suspend %d\n", btv->c.nr, state); + + /* stop dma + irqs */ + spin_lock_irqsave(&btv->s_lock,flags); + memset(&idle, 0, sizeof(idle)); + btv->state.video = btv->curr; + btv->state.vbi = btv->cvbi; + btv->state.loop_irq = btv->loop_irq; + btv->curr = idle; + btv->loop_irq = 0; + bttv_buffer_activate_video(btv, &idle); + bttv_buffer_activate_vbi(btv, NULL); + bttv_set_dma(btv, 0); + btwrite(0, BT848_INT_MASK); + spin_unlock_irqrestore(&btv->s_lock,flags); + + /* save bt878 state */ + btv->state.gpio_enable = btread(BT848_GPIO_OUT_EN); + btv->state.gpio_data = gpio_read(); + + /* save pci state */ + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + btv->state.disabled = 1; + } + return 0; +} + +static int bttv_resume(struct pci_dev *pci_dev) +{ + struct bttv *btv = pci_get_drvdata(pci_dev); + unsigned long flags; + + dprintk("bttv%d: resume\n", btv->c.nr); + + /* restore pci state */ + if (btv->state.disabled) { + pci_enable_device(pci_dev); + btv->state.disabled = 0; + } + pci_set_power_state(pci_dev, PCI_D0); + pci_restore_state(pci_dev); + + /* restore bt878 state */ + bttv_reinit_bt848(btv); + gpio_inout(0xffffff, btv->state.gpio_enable); + gpio_write(btv->state.gpio_data); + + /* restart dma */ + spin_lock_irqsave(&btv->s_lock,flags); + btv->curr = btv->state.video; + btv->cvbi = btv->state.vbi; + btv->loop_irq = btv->state.loop_irq; + bttv_buffer_activate_video(btv, &btv->curr); + bttv_buffer_activate_vbi(btv, btv->cvbi); + bttv_set_dma(btv, 0); + spin_unlock_irqrestore(&btv->s_lock,flags); + return 0; +} + +static struct pci_device_id bttv_pci_tbl[] = { + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); + +static struct pci_driver bttv_pci_driver = { + .name = "bttv", + .id_table = bttv_pci_tbl, + .probe = bttv_probe, + .remove = __devexit_p(bttv_remove), + .suspend = bttv_suspend, + .resume = bttv_resume, +}; + +static int bttv_init_module(void) +{ + bttv_num = 0; + + printk(KERN_INFO "bttv: driver version %d.%d.%d loaded\n", + (BTTV_VERSION_CODE >> 16) & 0xff, + (BTTV_VERSION_CODE >> 8) & 0xff, + BTTV_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "bttv: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize < 0 || gbufsize > BTTV_MAX_FBUF) + gbufsize = BTTV_MAX_FBUF; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + if (bttv_verbose) + printk(KERN_INFO "bttv: using %d buffers with %dk (%d pages) each for capture\n", + gbuffers, gbufsize >> 10, gbufsize >> PAGE_SHIFT); + + bttv_check_chipset(); + + bus_register(&bttv_sub_bus_type); + return pci_module_init(&bttv_pci_driver); +} + +static void bttv_cleanup_module(void) +{ + pci_unregister_driver(&bttv_pci_driver); + bus_unregister(&bttv_sub_bus_type); + return; +} + +module_init(bttv_init_module); +module_exit(bttv_cleanup_module); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-gpio.c b/drivers/media/video/bttv-gpio.c new file mode 100644 index 000000000000..77320cdf205f --- /dev/null +++ b/drivers/media/video/bttv-gpio.c @@ -0,0 +1,190 @@ +/* + $Id: bttv-gpio.c,v 1.7 2005/02/16 12:14:10 kraxel Exp $ + + bttv-gpio.c -- gpio sub drivers + + sysfs-based sub driver interface for bttv + mainly intented for gpio access + + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <asm/io.h> + +#include "bttvp.h" + +/* ----------------------------------------------------------------------- */ +/* internal: the bttv "bus" */ + +static int bttv_sub_bus_match(struct device *dev, struct device_driver *drv) +{ + struct bttv_sub_driver *sub = to_bttv_sub_drv(drv); + int len = strlen(sub->wanted); + + if (0 == strncmp(dev->bus_id, sub->wanted, len)) + return 1; + return 0; +} + +struct bus_type bttv_sub_bus_type = { + .name = "bttv-sub", + .match = &bttv_sub_bus_match, +}; +EXPORT_SYMBOL(bttv_sub_bus_type); + +static void release_sub_device(struct device *dev) +{ + struct bttv_sub_device *sub = to_bttv_sub_dev(dev); + kfree(sub); +} + +int bttv_sub_add_device(struct bttv_core *core, char *name) +{ + struct bttv_sub_device *sub; + int err; + + sub = kmalloc(sizeof(*sub),GFP_KERNEL); + if (NULL == sub) + return -ENOMEM; + memset(sub,0,sizeof(*sub)); + + sub->core = core; + sub->dev.parent = &core->pci->dev; + sub->dev.bus = &bttv_sub_bus_type; + sub->dev.release = release_sub_device; + snprintf(sub->dev.bus_id,sizeof(sub->dev.bus_id),"%s%d", + name, core->nr); + + err = device_register(&sub->dev); + if (0 != err) { + kfree(sub); + return err; + } + printk("bttv%d: add subdevice \"%s\"\n", core->nr, sub->dev.bus_id); + list_add_tail(&sub->list,&core->subs); + return 0; +} + +int bttv_sub_del_devices(struct bttv_core *core) +{ + struct bttv_sub_device *sub; + struct list_head *item,*save; + + list_for_each_safe(item,save,&core->subs) { + sub = list_entry(item,struct bttv_sub_device,list); + list_del(&sub->list); + device_unregister(&sub->dev); + } + return 0; +} + +void bttv_gpio_irq(struct bttv_core *core) +{ + struct bttv_sub_driver *drv; + struct bttv_sub_device *dev; + struct list_head *item; + + list_for_each(item,&core->subs) { + dev = list_entry(item,struct bttv_sub_device,list); + drv = to_bttv_sub_drv(dev->dev.driver); + if (drv && drv->gpio_irq) + drv->gpio_irq(dev); + } +} + +/* ----------------------------------------------------------------------- */ +/* external: sub-driver register/unregister */ + +int bttv_sub_register(struct bttv_sub_driver *sub, char *wanted) +{ + sub->drv.bus = &bttv_sub_bus_type; + snprintf(sub->wanted,sizeof(sub->wanted),"%s",wanted); + return driver_register(&sub->drv); +} +EXPORT_SYMBOL(bttv_sub_register); + +int bttv_sub_unregister(struct bttv_sub_driver *sub) +{ + driver_unregister(&sub->drv); + return 0; +} +EXPORT_SYMBOL(bttv_sub_unregister); + +/* ----------------------------------------------------------------------- */ +/* external: gpio access functions */ + +void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits) +{ + struct bttv *btv = container_of(core, struct bttv, c); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&btv->gpio_lock,flags); + data = btread(BT848_GPIO_OUT_EN); + data = data & ~mask; + data = data | (mask & 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) +{ + struct bttv *btv = container_of(core, struct bttv, c); + u32 value; + + value = btread(BT848_GPIO_DATA); + return value; +} +EXPORT_SYMBOL(bttv_gpio_read); + +void bttv_gpio_write(struct bttv_core *core, u32 value) +{ + struct bttv *btv = container_of(core, struct bttv, c); + + btwrite(value,BT848_GPIO_DATA); +} +EXPORT_SYMBOL(bttv_gpio_write); + +void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits) +{ + struct bttv *btv = container_of(core, struct bttv, c); + unsigned long flags; + u32 data; + + spin_lock_irqsave(&btv->gpio_lock,flags); + data = btread(BT848_GPIO_DATA); + data = data & ~mask; + data = data | (mask & bits); + btwrite(data,BT848_GPIO_DATA); + spin_unlock_irqrestore(&btv->gpio_lock,flags); +} +EXPORT_SYMBOL(bttv_gpio_bits); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-i2c.c b/drivers/media/video/bttv-i2c.c new file mode 100644 index 000000000000..e42f1ec13f3e --- /dev/null +++ b/drivers/media/video/bttv-i2c.c @@ -0,0 +1,461 @@ +/* + $Id: bttv-i2c.c,v 1.18 2005/02/16 12:14:10 kraxel Exp $ + + bttv-i2c.c -- all the i2c code is here + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include "bttvp.h" + +static struct i2c_algo_bit_data bttv_i2c_algo_bit_template; +static struct i2c_adapter bttv_i2c_adap_sw_template; +static struct i2c_adapter bttv_i2c_adap_hw_template; +static struct i2c_client bttv_i2c_client_template; + +static int attach_inform(struct i2c_client *client); + +static int i2c_debug = 0; +static int i2c_hw = 0; +static int i2c_scan = 0; +module_param(i2c_debug, int, 0644); +module_param(i2c_hw, int, 0444); +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +/* ----------------------------------------------------------------------- */ +/* I2C functions - bitbanging adapter (software i2c) */ + +static void bttv_bit_setscl(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x02; + else + btv->i2c_state &= ~0x02; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +static void bttv_bit_setsda(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x01; + else + btv->i2c_state &= ~0x01; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +static int bttv_bit_getscl(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x02 ? 1 : 0; + return state; +} + +static int bttv_bit_getsda(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x01; + return state; +} + +static struct i2c_algo_bit_data bttv_i2c_algo_bit_template = { + .setsda = bttv_bit_setsda, + .setscl = bttv_bit_setscl, + .getsda = bttv_bit_getsda, + .getscl = bttv_bit_getscl, + .udelay = 16, + .mdelay = 10, + .timeout = 200, +}; + +static struct i2c_adapter bttv_i2c_adap_sw_template = { + .owner = THIS_MODULE, +#ifdef I2C_CLASS_TV_ANALOG + .class = I2C_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("bt848"), + .id = I2C_HW_B_BT848, + .client_register = attach_inform, +}; + +/* ----------------------------------------------------------------------- */ +/* I2C functions - hardware i2c */ + +static int algo_control(struct i2c_adapter *adapter, + unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static int +bttv_i2c_wait_done(struct bttv *btv) +{ + DECLARE_WAITQUEUE(wait, current); + int rc = 0; + + add_wait_queue(&btv->i2c_queue, &wait); + if (0 == btv->i2c_done) + msleep_interruptible(20); + remove_wait_queue(&btv->i2c_queue, &wait); + + if (0 == btv->i2c_done) + /* timeout */ + rc = -EIO; + if (btv->i2c_done & BT848_INT_RACK) + rc = 1; + btv->i2c_done = 0; + return rc; +} + +#define I2C_HW (BT878_I2C_MODE | BT848_I2C_SYNC |\ + BT848_I2C_SCL | BT848_I2C_SDA) + +static int +bttv_i2c_sendbytes(struct bttv *btv, const struct i2c_msg *msg, int last) +{ + u32 xmit; + int retval,cnt; + + /* sanity checks */ + if (0 == msg->len) + return -EINVAL; + + /* start, address + first byte */ + xmit = (msg->addr << 25) | (msg->buf[0] << 16) | I2C_HW; + if (msg->len > 1 || !last) + xmit |= BT878_I2C_NOSTOP; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + if (i2c_debug) { + printk(" <W %02x %02x", msg->addr << 1, msg->buf[0]); + if (!(xmit & BT878_I2C_NOSTOP)) + printk(" >\n"); + } + + for (cnt = 1; cnt < msg->len; cnt++ ) { + /* following bytes */ + xmit = (msg->buf[cnt] << 24) | I2C_HW | BT878_I2C_NOSTART; + if (cnt < msg->len-1 || !last) + xmit |= BT878_I2C_NOSTOP; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + if (i2c_debug) { + printk(" %02x", msg->buf[cnt]); + if (!(xmit & BT878_I2C_NOSTOP)) + printk(" >\n"); + } + } + return msg->len; + + eio: + retval = -EIO; + err: + if (i2c_debug) + printk(" ERR: %d\n",retval); + return retval; +} + +static int +bttv_i2c_readbytes(struct bttv *btv, const struct i2c_msg *msg, int last) +{ + u32 xmit; + u32 cnt; + int retval; + + for(cnt = 0; cnt < msg->len; cnt++) { + xmit = (msg->addr << 25) | (1 << 24) | I2C_HW; + if (cnt < msg->len-1) + xmit |= BT848_I2C_W3B; + if (cnt < msg->len-1 || !last) + xmit |= BT878_I2C_NOSTOP; + if (cnt) + xmit |= BT878_I2C_NOSTART; + btwrite(xmit, BT848_I2C); + retval = bttv_i2c_wait_done(btv); + if (retval < 0) + goto err; + if (retval == 0) + goto eio; + msg->buf[cnt] = ((u32)btread(BT848_I2C) >> 8) & 0xff; + if (i2c_debug) { + if (!(xmit & BT878_I2C_NOSTART)) + printk(" <R %02x", (msg->addr << 1) +1); + printk(" =%02x", msg->buf[cnt]); + if (!(xmit & BT878_I2C_NOSTOP)) + printk(" >\n"); + } + } + return msg->len; + + eio: + retval = -EIO; + err: + if (i2c_debug) + printk(" ERR: %d\n",retval); + return retval; +} + +static int bttv_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num) +{ + struct bttv *btv = i2c_get_adapdata(i2c_adap); + int retval = 0; + int i; + + if (i2c_debug) + printk("bt-i2c:"); + btwrite(BT848_INT_I2CDONE|BT848_INT_RACK, BT848_INT_STAT); + for (i = 0 ; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) { + /* read */ + retval = bttv_i2c_readbytes(btv, &msgs[i], i+1 == num); + if (retval < 0) + goto err; + } else { + /* write */ + retval = bttv_i2c_sendbytes(btv, &msgs[i], i+1 == num); + if (retval < 0) + goto err; + } + } + return num; + + err: + return retval; +} + +static struct i2c_algorithm bttv_algo = { + .name = "bt878", + .id = I2C_ALGO_BIT | I2C_HW_B_BT848 /* FIXME */, + .master_xfer = bttv_i2c_xfer, + .algo_control = algo_control, + .functionality = functionality, +}; + +static struct i2c_adapter bttv_i2c_adap_hw_template = { + .owner = THIS_MODULE, +#ifdef I2C_CLASS_TV_ANALOG + .class = I2C_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("bt878"), + .id = I2C_ALGO_BIT | I2C_HW_B_BT848 /* FIXME */, + .algo = &bttv_algo, + .client_register = attach_inform, +}; + +/* ----------------------------------------------------------------------- */ +/* I2C functions - common stuff */ + +static int attach_inform(struct i2c_client *client) +{ + struct bttv *btv = i2c_get_adapdata(client->adapter); + + if (btv->tuner_type != UNSET) + bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type); + if (btv->pinnacle_id != UNSET) + bttv_call_i2c_clients(btv,AUDC_CONFIG_PINNACLE, + &btv->pinnacle_id); + if (bttv_debug) + printk("bttv%d: i2c attach [client=%s]\n", + btv->c.nr, i2c_clientname(client)); + return 0; +} + +void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg) +{ + if (0 != btv->i2c_rc) + return; + i2c_clients_command(&btv->c.i2c_adap, cmd, arg); +} + +static struct i2c_client bttv_i2c_client_template = { + I2C_DEVNAME("bttv internal"), +}; + + +/* read I2C */ +int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for) +{ + unsigned char buffer = 0; + + if (0 != btv->i2c_rc) + return -1; + if (bttv_verbose && NULL != probe_for) + printk(KERN_INFO "bttv%d: i2c: checking for %s @ 0x%02x... ", + btv->c.nr,probe_for,addr); + btv->i2c_client.addr = addr >> 1; + if (1 != i2c_master_recv(&btv->i2c_client, &buffer, 1)) { + if (NULL != probe_for) { + if (bttv_verbose) + printk("not found\n"); + } else + printk(KERN_WARNING "bttv%d: i2c read 0x%x: error\n", + btv->c.nr,addr); + return -1; + } + if (bttv_verbose && NULL != probe_for) + printk("found\n"); + return buffer; +} + +/* write I2C */ +int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + unsigned char buffer[2]; + int bytes = both ? 2 : 1; + + if (0 != btv->i2c_rc) + return -1; + btv->i2c_client.addr = addr >> 1; + buffer[0] = b1; + buffer[1] = b2; + if (bytes != i2c_master_send(&btv->i2c_client, buffer, bytes)) + return -1; + return 0; +} + +/* read EEPROM content */ +void __devinit bttv_readee(struct bttv *btv, unsigned char *eedata, int addr) +{ + btv->i2c_client.addr = addr >> 1; + tveeprom_read(&btv->i2c_client, eedata, 256); +} + +static char *i2c_devs[128] = { + [ 0x30 >> 1 ] = "IR (hauppauge)", + [ 0x80 >> 1 ] = "msp34xx", + [ 0x86 >> 1 ] = "tda9887", + [ 0xa0 >> 1 ] = "eeprom", + [ 0xc0 >> 1 ] = "tuner (analog)", + [ 0xc2 >> 1 ] = "tuner (analog)", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i,rc; + + for (i = 0; i < 128; i++) { + c->addr = i; + rc = i2c_master_recv(c,&buf,0); + if (rc < 0) + continue; + printk("%s: i2c scan: found device @ 0x%x [%s]\n", + name, i << 1, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +/* init + register i2c algo-bit adapter */ +int __devinit init_bttv_i2c(struct bttv *btv) +{ + memcpy(&btv->i2c_client, &bttv_i2c_client_template, + sizeof(bttv_i2c_client_template)); + + if (i2c_hw) + btv->use_i2c_hw = 1; + if (btv->use_i2c_hw) { + /* bt878 */ + memcpy(&btv->c.i2c_adap, &bttv_i2c_adap_hw_template, + sizeof(bttv_i2c_adap_hw_template)); + } else { + /* bt848 */ + memcpy(&btv->c.i2c_adap, &bttv_i2c_adap_sw_template, + sizeof(bttv_i2c_adap_sw_template)); + memcpy(&btv->i2c_algo, &bttv_i2c_algo_bit_template, + sizeof(bttv_i2c_algo_bit_template)); + btv->i2c_algo.data = btv; + btv->c.i2c_adap.algo_data = &btv->i2c_algo; + } + + btv->c.i2c_adap.dev.parent = &btv->c.pci->dev; + snprintf(btv->c.i2c_adap.name, sizeof(btv->c.i2c_adap.name), + "bt%d #%d [%s]", btv->id, btv->c.nr, + btv->use_i2c_hw ? "hw" : "sw"); + + i2c_set_adapdata(&btv->c.i2c_adap, btv); + btv->i2c_client.adapter = &btv->c.i2c_adap; + +#ifdef I2C_CLASS_TV_ANALOG + if (bttv_tvcards[btv->c.type].no_video) + btv->c.i2c_adap.class &= ~I2C_CLASS_TV_ANALOG; + if (bttv_tvcards[btv->c.type].has_dvb) + btv->c.i2c_adap.class |= I2C_CLASS_TV_DIGITAL; +#endif + + if (btv->use_i2c_hw) { + btv->i2c_rc = i2c_add_adapter(&btv->c.i2c_adap); + } else { + bttv_bit_setscl(btv,1); + bttv_bit_setsda(btv,1); + btv->i2c_rc = i2c_bit_add_bus(&btv->c.i2c_adap); + } + if (0 == btv->i2c_rc && i2c_scan) + do_i2c_scan(btv->c.name,&btv->i2c_client); + return btv->i2c_rc; +} + +int __devexit fini_bttv_i2c(struct bttv *btv) +{ + if (0 != btv->i2c_rc) + return 0; + + if (btv->use_i2c_hw) { + return i2c_del_adapter(&btv->c.i2c_adap); + } else { + return i2c_bit_del_bus(&btv->c.i2c_adap); + } +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-if.c b/drivers/media/video/bttv-if.c new file mode 100644 index 000000000000..f7b5543a96a1 --- /dev/null +++ b/drivers/media/video/bttv-if.c @@ -0,0 +1,160 @@ +/* + $Id: bttv-if.c,v 1.4 2004/11/17 18:47:47 kraxel Exp $ + + bttv-if.c -- old gpio interface to other kernel modules + don't use in new code, will go away in 2.7 + have a look at bttv-gpio.c instead. + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <asm/io.h> + +#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) + return NULL; + 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) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + gpio_inout(mask,data); + if (bttv_gpio) + bttv_gpio_tracking(btv,"extern enable"); + return 0; +} + +int bttv_read_gpio(unsigned int card, unsigned long *data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + + if(btv->shutdown) { + return -ENODEV; + } + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because we set direct input on init */ + *data = gpio_read(); + return 0; +} + +int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because direct input is set on init */ + gpio_bits(mask,data); + if (bttv_gpio) + bttv_gpio_tracking(btv,"extern write"); + 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 + * End: + */ diff --git a/drivers/media/video/bttv-risc.c b/drivers/media/video/bttv-risc.c new file mode 100644 index 000000000000..bdc5ce6c43b9 --- /dev/null +++ b/drivers/media/video/bttv-risc.c @@ -0,0 +1,802 @@ +/* + $Id: bttv-risc.c,v 1.10 2004/11/19 18:07:12 kraxel Exp $ + + bttv-risc.c -- interfaces to other kernel modules + + bttv risc code handling + - memory management + - generation + + (c) 2000-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include "bttvp.h" + +#define VCR_HACK_LINES 4 + +/* ---------------------------------------------------------- */ +/* risc code generators */ + +int +bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int offset, unsigned int bpl, + unsigned int padding, unsigned int lines) +{ + u32 instructions,line,todo; + struct scatterlist *sg; + u32 *rp; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + sync + jump (all 2 dwords) */ + instructions = (bpl * lines) / PAGE_SIZE + lines; + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*8)) < 0) + return rc; + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(rp++) = cpu_to_le32(0); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + if ((btv->opt_vcr_hack) && + (line >= (lines - VCR_HACK_LINES))) + continue; + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg++; + } + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + BT848_RISC_EOL|bpl); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + offset+=bpl; + } else { + /* scanline needs to be splitted */ + todo = bpl; + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + (sg_dma_len(sg)-offset)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg++; + while (todo > sg_dma_len(sg)) { + *(rp++)=cpu_to_le32(BT848_RISC_WRITE| + sg_dma_len(sg)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg++; + } + *(rp++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL| + todo); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) / 4 > risc->size); + return 0; +} + +static int +bttv_risc_planar(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int yoffset, unsigned int ybpl, + unsigned int ypadding, unsigned int ylines, + unsigned int uoffset, unsigned int voffset, + unsigned int hshift, unsigned int vshift, + unsigned int cpadding) +{ + unsigned int instructions,line,todo,ylen,chroma; + u32 *rp,ri; + struct scatterlist *ysg; + struct scatterlist *usg; + struct scatterlist *vsg; + int topfield = (0 == yoffset); + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line (5 dwords) + plus sync + jump (2 dwords) */ + instructions = (ybpl * ylines * 2) / PAGE_SIZE + ylines; + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*4*5)) < 0) + return rc; + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3); + *(rp++) = cpu_to_le32(0); + + /* scan lines */ + ysg = sglist; + usg = sglist; + vsg = sglist; + for (line = 0; line < ylines; line++) { + if ((btv->opt_vcr_hack) && + (line >= (ylines - VCR_HACK_LINES))) + continue; + switch (vshift) { + case 0: + chroma = 1; + break; + case 1: + if (topfield) + chroma = ((line & 1) == 0); + else + chroma = ((line & 1) == 1); + break; + case 2: + if (topfield) + chroma = ((line & 3) == 0); + else + chroma = ((line & 3) == 2); + break; + default: + chroma = 0; + break; + } + + for (todo = ybpl; todo > 0; todo -= ylen) { + /* go to next sg entry if needed */ + while (yoffset && yoffset >= sg_dma_len(ysg)) { + yoffset -= sg_dma_len(ysg); + ysg++; + } + while (uoffset && uoffset >= sg_dma_len(usg)) { + uoffset -= sg_dma_len(usg); + usg++; + } + while (voffset && voffset >= sg_dma_len(vsg)) { + voffset -= sg_dma_len(vsg); + vsg++; + } + + /* calculate max number of bytes we can write */ + ylen = todo; + if (yoffset + ylen > sg_dma_len(ysg)) + ylen = sg_dma_len(ysg) - yoffset; + if (chroma) { + if (uoffset + (ylen>>hshift) > sg_dma_len(usg)) + ylen = (sg_dma_len(usg) - uoffset) << hshift; + if (voffset + (ylen>>hshift) > sg_dma_len(vsg)) + ylen = (sg_dma_len(vsg) - voffset) << hshift; + ri = BT848_RISC_WRITE123; + } else { + ri = BT848_RISC_WRITE1S23; + } + if (ybpl == todo) + ri |= BT848_RISC_SOL; + if (ylen == todo) + ri |= BT848_RISC_EOL; + + /* write risc instruction */ + *(rp++)=cpu_to_le32(ri | ylen); + *(rp++)=cpu_to_le32(((ylen >> hshift) << 16) | + (ylen >> hshift)); + *(rp++)=cpu_to_le32(sg_dma_address(ysg)+yoffset); + yoffset += ylen; + if (chroma) { + *(rp++)=cpu_to_le32(sg_dma_address(usg)+uoffset); + uoffset += ylen >> hshift; + *(rp++)=cpu_to_le32(sg_dma_address(vsg)+voffset); + voffset += ylen >> hshift; + } + } + yoffset += ypadding; + if (chroma) { + uoffset += cpadding; + voffset += cpadding; + } + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) / 4 > risc->size); + return 0; +} + +static int +bttv_risc_overlay(struct bttv *btv, struct btcx_riscmem *risc, + const struct bttv_format *fmt, struct bttv_overlay *ov, + int skip_even, int skip_odd) +{ + int instructions,rc,line,maxy,start,end,skip,nskips; + struct btcx_skiplist *skips; + u32 *rp,ri,ra; + u32 addr; + + /* skip list for window clipping */ + if (NULL == (skips = kmalloc(sizeof(*skips) * ov->nclips,GFP_KERNEL))) + return -ENOMEM; + + /* estimate risc mem: worst case is (clip+1) * lines instructions + + sync + jump (all 2 dwords) */ + instructions = (ov->nclips + 1) * + ((skip_even || skip_odd) ? ov->w.height>>1 : ov->w.height); + instructions += 2; + if ((rc = btcx_riscmem_alloc(btv->c.pci,risc,instructions*8)) < 0) { + kfree(skips); + return rc; + } + + /* sync instruction */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(rp++) = cpu_to_le32(0); + + addr = (unsigned long)btv->fbuf.base; + addr += btv->fbuf.fmt.bytesperline * ov->w.top; + addr += (fmt->depth >> 3) * ov->w.left; + + /* scan lines */ + for (maxy = -1, line = 0; line < ov->w.height; + line++, addr += btv->fbuf.fmt.bytesperline) { + if ((btv->opt_vcr_hack) && + (line >= (ov->w.height - VCR_HACK_LINES))) + continue; + if ((line%2) == 0 && skip_even) + continue; + if ((line%2) == 1 && skip_odd) + continue; + + /* calculate clipping */ + if (line > maxy) + btcx_calc_skips(line, ov->w.width, &maxy, + skips, &nskips, ov->clips, ov->nclips); + + /* write out risc code */ + for (start = 0, skip = 0; start < ov->w.width; start = end) { + if (skip >= nskips) { + ri = BT848_RISC_WRITE; + end = ov->w.width; + } else if (start < skips[skip].start) { + ri = BT848_RISC_WRITE; + end = skips[skip].start; + } else { + ri = BT848_RISC_SKIP; + end = skips[skip].end; + skip++; + } + if (BT848_RISC_WRITE == ri) + ra = addr + (fmt->depth>>3)*start; + else + ra = 0; + + if (0 == start) + ri |= BT848_RISC_SOL; + if (ov->w.width == end) + ri |= BT848_RISC_EOL; + ri |= (fmt->depth>>3) * (end-start); + + *(rp++)=cpu_to_le32(ri); + if (0 != ra) + *(rp++)=cpu_to_le32(ra); + } + } + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) / 4 > risc->size); + kfree(skips); + return 0; +} + +/* ---------------------------------------------------------- */ + +static void +bttv_calc_geo(struct bttv *btv, struct bttv_geometry *geo, + int width, int height, int interleaved, int norm) +{ + const struct bttv_tvnorm *tvnorm = &bttv_tvnorms[norm]; + u32 xsf, sr; + int vdelay; + + int swidth = tvnorm->swidth; + int totalwidth = tvnorm->totalwidth; + int scaledtwidth = tvnorm->scaledtwidth; + + if (bttv_tvcards[btv->c.type].muxsel[btv->input] < 0) { + swidth = 720; + totalwidth = 858; + scaledtwidth = 858; + } + + vdelay = tvnorm->vdelay; +#if 0 /* FIXME */ + if (vdelay < btv->vbi.lines*2) + vdelay = btv->vbi.lines*2; +#endif + + xsf = (width*scaledtwidth)/swidth; + geo->hscale = ((totalwidth*4096UL)/xsf-4096); + geo->hdelay = tvnorm->hdelayx1; + geo->hdelay = (geo->hdelay*width)/swidth; + geo->hdelay &= 0x3fe; + sr = ((tvnorm->sheight >> (interleaved?0:1))*512)/height - 512; + geo->vscale = (0x10000UL-sr) & 0x1fff; + geo->crop = ((width>>8)&0x03) | ((geo->hdelay>>6)&0x0c) | + ((tvnorm->sheight>>4)&0x30) | ((vdelay>>2)&0xc0); + geo->vscale |= interleaved ? (BT848_VSCALE_INT<<8) : 0; + geo->vdelay = vdelay; + geo->width = width; + geo->sheight = tvnorm->sheight; + geo->vtotal = tvnorm->vtotal; + + if (btv->opt_combfilter) { + geo->vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0); + geo->comb = (width < 769) ? 1 : 0; + } else { + geo->vtc = 0; + geo->comb = 0; + } +} + +static void +bttv_apply_geo(struct bttv *btv, struct bttv_geometry *geo, int odd) +{ + int off = odd ? 0x80 : 0x00; + + if (geo->comb) + btor(BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + else + btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + + btwrite(geo->vtc, BT848_E_VTC+off); + btwrite(geo->hscale >> 8, BT848_E_HSCALE_HI+off); + btwrite(geo->hscale & 0xff, BT848_E_HSCALE_LO+off); + btaor((geo->vscale>>8), 0xe0, BT848_E_VSCALE_HI+off); + btwrite(geo->vscale & 0xff, BT848_E_VSCALE_LO+off); + btwrite(geo->width & 0xff, BT848_E_HACTIVE_LO+off); + btwrite(geo->hdelay & 0xff, BT848_E_HDELAY_LO+off); + btwrite(geo->sheight & 0xff, BT848_E_VACTIVE_LO+off); + btwrite(geo->vdelay & 0xff, BT848_E_VDELAY_LO+off); + btwrite(geo->crop, BT848_E_CROP+off); + btwrite(geo->vtotal>>8, BT848_VTOTAL_HI); + btwrite(geo->vtotal & 0xff, BT848_VTOTAL_LO); +} + +/* ---------------------------------------------------------- */ +/* risc group / risc main loop / dma management */ + +void +bttv_set_dma(struct bttv *btv, int override) +{ + unsigned long cmd; + int capctl; + + btv->cap_ctl = 0; + if (NULL != btv->curr.top) btv->cap_ctl |= 0x02; + if (NULL != btv->curr.bottom) btv->cap_ctl |= 0x01; + if (NULL != btv->cvbi) btv->cap_ctl |= 0x0c; + + capctl = 0; + capctl |= (btv->cap_ctl & 0x03) ? 0x03 : 0x00; /* capture */ + capctl |= (btv->cap_ctl & 0x0c) ? 0x0c : 0x00; /* vbi data */ + capctl |= override; + + d2printk(KERN_DEBUG + "bttv%d: capctl=%x lirq=%d top=%08Lx/%08Lx even=%08Lx/%08Lx\n", + btv->c.nr,capctl,btv->loop_irq, + btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0, + btv->curr.top ? (unsigned long long)btv->curr.top->top.dma : 0, + btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0, + btv->curr.bottom ? (unsigned long long)btv->curr.bottom->bottom.dma : 0); + + cmd = BT848_RISC_JUMP; + if (btv->loop_irq) { + cmd |= BT848_RISC_IRQ; + cmd |= (btv->loop_irq & 0x0f) << 16; + cmd |= (~btv->loop_irq & 0x0f) << 20; + } + if (btv->curr.frame_irq || btv->loop_irq || btv->cvbi) { + mod_timer(&btv->timeout, jiffies+BTTV_TIMEOUT); + } else { + del_timer(&btv->timeout); + } + btv->main.cpu[RISC_SLOT_LOOP] = cpu_to_le32(cmd); + + btaor(capctl, ~0x0f, BT848_CAP_CTL); + if (capctl) { + if (btv->dma_on) + return; + btwrite(btv->main.dma, BT848_RISC_STRT_ADD); + btor(3, BT848_GPIO_DMA_CTL); + btv->dma_on = 1; + } else { + if (!btv->dma_on) + return; + btand(~3, BT848_GPIO_DMA_CTL); + btv->dma_on = 0; + } + return; +} + +int +bttv_risc_init_main(struct bttv *btv) +{ + int rc; + + if ((rc = btcx_riscmem_alloc(btv->c.pci,&btv->main,PAGE_SIZE)) < 0) + return rc; + dprintk(KERN_DEBUG "bttv%d: risc main @ %08Lx\n", + btv->c.nr,(unsigned long long)btv->main.dma); + + btv->main.cpu[0] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC | + BT848_FIFO_STATUS_VRE); + btv->main.cpu[1] = cpu_to_le32(0); + btv->main.cpu[2] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[3] = cpu_to_le32(btv->main.dma + (4<<2)); + + /* top field */ + btv->main.cpu[4] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[5] = cpu_to_le32(btv->main.dma + (6<<2)); + btv->main.cpu[6] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[7] = cpu_to_le32(btv->main.dma + (8<<2)); + + btv->main.cpu[8] = cpu_to_le32(BT848_RISC_SYNC | BT848_RISC_RESYNC | + BT848_FIFO_STATUS_VRO); + btv->main.cpu[9] = cpu_to_le32(0); + + /* bottom field */ + btv->main.cpu[10] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[11] = cpu_to_le32(btv->main.dma + (12<<2)); + btv->main.cpu[12] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[13] = cpu_to_le32(btv->main.dma + (14<<2)); + + /* jump back to top field */ + btv->main.cpu[14] = cpu_to_le32(BT848_RISC_JUMP); + btv->main.cpu[15] = cpu_to_le32(btv->main.dma + (0<<2)); + + return 0; +} + +int +bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc, + int irqflags) +{ + unsigned long cmd; + unsigned long next = btv->main.dma + ((slot+2) << 2); + + if (NULL == risc) { + d2printk(KERN_DEBUG "bttv%d: risc=%p slot[%d]=NULL\n", + btv->c.nr,risc,slot); + btv->main.cpu[slot+1] = cpu_to_le32(next); + } else { + d2printk(KERN_DEBUG "bttv%d: risc=%p slot[%d]=%08Lx irq=%d\n", + btv->c.nr,risc,slot,(unsigned long long)risc->dma,irqflags); + cmd = BT848_RISC_JUMP; + if (irqflags) { + cmd |= BT848_RISC_IRQ; + cmd |= (irqflags & 0x0f) << 16; + cmd |= (~irqflags & 0x0f) << 20; + } + risc->jmp[0] = cpu_to_le32(cmd); + risc->jmp[1] = cpu_to_le32(next); + btv->main.cpu[slot+1] = cpu_to_le32(risc->dma); + } + return 0; +} + +void +bttv_dma_free(struct bttv *btv, struct bttv_buffer *buf) +{ + if (in_interrupt()) + BUG(); + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(btv->c.pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + btcx_riscmem_free(btv->c.pci,&buf->bottom); + btcx_riscmem_free(btv->c.pci,&buf->top); + buf->vb.state = STATE_NEEDS_INIT; +} + +int +bttv_buffer_activate_vbi(struct bttv *btv, + struct bttv_buffer *vbi) +{ + /* vbi capture */ + if (vbi) { + vbi->vb.state = STATE_ACTIVE; + list_del(&vbi->vb.queue); + bttv_risc_hook(btv, RISC_SLOT_O_VBI, &vbi->top, 0); + bttv_risc_hook(btv, RISC_SLOT_E_VBI, &vbi->bottom, 4); + } else { + bttv_risc_hook(btv, RISC_SLOT_O_VBI, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_VBI, NULL, 0); + } + return 0; +} + +int +bttv_buffer_activate_video(struct bttv *btv, + struct bttv_buffer_set *set) +{ + /* video capture */ + if (NULL != set->top && NULL != set->bottom) { + if (set->top == set->bottom) { + set->top->vb.state = STATE_ACTIVE; + if (set->top->vb.queue.next) + list_del(&set->top->vb.queue); + } else { + set->top->vb.state = STATE_ACTIVE; + set->bottom->vb.state = STATE_ACTIVE; + if (set->top->vb.queue.next) + list_del(&set->top->vb.queue); + if (set->bottom->vb.queue.next) + list_del(&set->bottom->vb.queue); + } + bttv_apply_geo(btv, &set->top->geo, 1); + bttv_apply_geo(btv, &set->bottom->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top, + set->top_irq); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom, + set->frame_irq); + btaor((set->top->btformat & 0xf0) | (set->bottom->btformat & 0x0f), + ~0xff, BT848_COLOR_FMT); + btaor((set->top->btswap & 0x0a) | (set->bottom->btswap & 0x05), + ~0x0f, BT848_COLOR_CTL); + } else if (NULL != set->top) { + set->top->vb.state = STATE_ACTIVE; + if (set->top->vb.queue.next) + list_del(&set->top->vb.queue); + bttv_apply_geo(btv, &set->top->geo,1); + bttv_apply_geo(btv, &set->top->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, &set->top->top, + set->frame_irq); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0); + btaor(set->top->btformat & 0xff, ~0xff, BT848_COLOR_FMT); + btaor(set->top->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL); + } else if (NULL != set->bottom) { + set->bottom->vb.state = STATE_ACTIVE; + if (set->bottom->vb.queue.next) + list_del(&set->bottom->vb.queue); + bttv_apply_geo(btv, &set->bottom->geo,1); + bttv_apply_geo(btv, &set->bottom->geo,0); + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, &set->bottom->bottom, + set->frame_irq); + btaor(set->bottom->btformat & 0xff, ~0xff, BT848_COLOR_FMT); + btaor(set->bottom->btswap & 0x0f, ~0x0f, BT848_COLOR_CTL); + } else { + bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0); + bttv_risc_hook(btv, RISC_SLOT_E_FIELD, NULL, 0); + } + return 0; +} + +/* ---------------------------------------------------------- */ + +/* calculate geometry, build risc code */ +int +bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf) +{ + const struct bttv_tvnorm *tvnorm = bttv_tvnorms + buf->tvnorm; + + dprintk(KERN_DEBUG + "bttv%d: buffer field: %s format: %s size: %dx%d\n", + btv->c.nr, v4l2_field_names[buf->vb.field], + buf->fmt->name, buf->vb.width, buf->vb.height); + + /* packed pixel modes */ + if (buf->fmt->flags & FORMAT_FLAGS_PACKED) { + int bpl = (buf->fmt->depth >> 3) * buf->vb.width; + int bpf = bpl * (buf->vb.height >> 1); + + bttv_calc_geo(btv,&buf->geo,buf->vb.width,buf->vb.height, + V4L2_FIELD_HAS_BOTH(buf->vb.field),buf->tvnorm); + + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist, + 0,bpl,0,buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist, + 0,bpl,0,buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist, + 0,bpl,bpl,buf->vb.height >> 1); + bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist, + bpl,bpl,bpl,buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + bttv_risc_packed(btv,&buf->top,buf->vb.dma.sglist, + 0,bpl,0,buf->vb.height >> 1); + bttv_risc_packed(btv,&buf->bottom,buf->vb.dma.sglist, + bpf,bpl,0,buf->vb.height >> 1); + break; + default: + BUG(); + } + } + + /* planar modes */ + if (buf->fmt->flags & FORMAT_FLAGS_PLANAR) { + int uoffset, voffset; + int ypadding, cpadding, lines; + + /* calculate chroma offsets */ + uoffset = buf->vb.width * buf->vb.height; + voffset = buf->vb.width * buf->vb.height; + if (buf->fmt->flags & FORMAT_FLAGS_CrCb) { + /* Y-Cr-Cb plane order */ + uoffset >>= buf->fmt->hshift; + uoffset >>= buf->fmt->vshift; + uoffset += voffset; + } else { + /* Y-Cb-Cr plane order */ + voffset >>= buf->fmt->hshift; + voffset >>= buf->fmt->vshift; + voffset += uoffset; + } + + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,0,buf->tvnorm); + bttv_risc_planar(btv, &buf->top, buf->vb.dma.sglist, + 0,buf->vb.width,0,buf->vb.height, + uoffset,voffset,buf->fmt->hshift, + buf->fmt->vshift,0); + break; + case V4L2_FIELD_BOTTOM: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,0,buf->tvnorm); + bttv_risc_planar(btv, &buf->bottom, buf->vb.dma.sglist, + 0,buf->vb.width,0,buf->vb.height, + uoffset,voffset,buf->fmt->hshift, + buf->fmt->vshift,0); + break; + case V4L2_FIELD_INTERLACED: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,1,buf->tvnorm); + lines = buf->vb.height >> 1; + ypadding = buf->vb.width; + cpadding = buf->vb.width >> buf->fmt->hshift; + bttv_risc_planar(btv,&buf->top, + buf->vb.dma.sglist, + 0,buf->vb.width,ypadding,lines, + uoffset,voffset, + buf->fmt->hshift, + buf->fmt->vshift, + cpadding); + bttv_risc_planar(btv,&buf->bottom, + buf->vb.dma.sglist, + ypadding,buf->vb.width,ypadding,lines, + uoffset+cpadding, + voffset+cpadding, + buf->fmt->hshift, + buf->fmt->vshift, + cpadding); + break; + case V4L2_FIELD_SEQ_TB: + bttv_calc_geo(btv,&buf->geo,buf->vb.width, + buf->vb.height,1,buf->tvnorm); + lines = buf->vb.height >> 1; + ypadding = buf->vb.width; + cpadding = buf->vb.width >> buf->fmt->hshift; + bttv_risc_planar(btv,&buf->top, + buf->vb.dma.sglist, + 0,buf->vb.width,0,lines, + uoffset >> 1, + voffset >> 1, + buf->fmt->hshift, + buf->fmt->vshift, + 0); + bttv_risc_planar(btv,&buf->bottom, + buf->vb.dma.sglist, + lines * ypadding,buf->vb.width,0,lines, + lines * ypadding + (uoffset >> 1), + lines * ypadding + (voffset >> 1), + buf->fmt->hshift, + buf->fmt->vshift, + 0); + break; + default: + BUG(); + } + } + + /* raw data */ + if (buf->fmt->flags & FORMAT_FLAGS_RAW) { + /* build risc code */ + buf->vb.field = V4L2_FIELD_SEQ_TB; + bttv_calc_geo(btv,&buf->geo,tvnorm->swidth,tvnorm->sheight, + 1,buf->tvnorm); + bttv_risc_packed(btv, &buf->top, buf->vb.dma.sglist, + 0, RAW_BPL, 0, RAW_LINES); + bttv_risc_packed(btv, &buf->bottom, buf->vb.dma.sglist, + buf->vb.size/2 , RAW_BPL, 0, RAW_LINES); + } + + /* copy format info */ + buf->btformat = buf->fmt->btformat; + buf->btswap = buf->fmt->btswap; + return 0; +} + +/* ---------------------------------------------------------- */ + +/* calculate geometry, build risc code */ +int +bttv_overlay_risc(struct bttv *btv, + struct bttv_overlay *ov, + const struct bttv_format *fmt, + struct bttv_buffer *buf) +{ + /* check interleave, bottom+top fields */ + dprintk(KERN_DEBUG + "bttv%d: overlay fields: %s format: %s size: %dx%d\n", + btv->c.nr, v4l2_field_names[buf->vb.field], + fmt->name,ov->w.width,ov->w.height); + + /* calculate geometry */ + bttv_calc_geo(btv,&buf->geo,ov->w.width,ov->w.height, + V4L2_FIELD_HAS_BOTH(ov->field), ov->tvnorm); + + /* build risc code */ + switch (ov->field) { + case V4L2_FIELD_TOP: + bttv_risc_overlay(btv, &buf->top, fmt, ov, 0, 0); + break; + case V4L2_FIELD_BOTTOM: + bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 0, 0); + break; + case V4L2_FIELD_INTERLACED: +#if 0 + bttv_risc_overlay(btv, &buf->top, fmt, ov, 1, 0); + bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 0, 1); +#else + bttv_risc_overlay(btv, &buf->top, fmt, ov, 0, 1); + bttv_risc_overlay(btv, &buf->bottom, fmt, ov, 1, 0); +#endif + break; + default: + BUG(); + } + + /* copy format info */ + buf->btformat = fmt->btformat; + buf->btswap = fmt->btswap; + buf->vb.field = ov->field; + return 0; +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-vbi.c b/drivers/media/video/bttv-vbi.c new file mode 100644 index 000000000000..06f3e62b3e8d --- /dev/null +++ b/drivers/media/video/bttv-vbi.c @@ -0,0 +1,235 @@ +/* + $Id: bttv-vbi.c,v 1.9 2005/01/13 17:22:33 kraxel Exp $ + + bttv - Bt848 frame grabber driver + vbi interface + + (c) 2002 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/kdev_t.h> +#include <asm/io.h> +#include "bttvp.h" + +#define VBI_DEFLINES 16 +#define VBI_MAXLINES 32 + +static unsigned int vbibufs = 4; +static unsigned int vbi_debug = 0; + +module_param(vbibufs, int, 0444); +module_param(vbi_debug, int, 0644); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32, default 4"); +MODULE_PARM_DESC(vbi_debug,"vbi code debug messages, default is 0 (no)"); + +#ifdef dprintk +# undef dprintk +#endif +#define dprintk(fmt, arg...) if (vbi_debug) \ + printk(KERN_DEBUG "bttv%d/vbi: " fmt, btv->c.nr , ## arg) + +/* ----------------------------------------------------------------------- */ +/* vbi risc code + mm */ + +static int +vbi_buffer_risc(struct bttv *btv, struct bttv_buffer *buf, int lines) +{ + int bpl = 2048; + + bttv_risc_packed(btv, &buf->top, buf->vb.dma.sglist, + 0, bpl-4, 4, lines); + bttv_risc_packed(btv, &buf->bottom, buf->vb.dma.sglist, + lines * bpl, bpl-4, 4, lines); + return 0; +} + +static int vbi_buffer_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct bttv_fh *fh = q->priv_data; + struct bttv *btv = fh->btv; + + if (0 == *count) + *count = vbibufs; + *size = fh->lines * 2 * 2048; + dprintk("setup: lines=%d\n",fh->lines); + return 0; +} + +static int vbi_buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct bttv_fh *fh = q->priv_data; + struct bttv *btv = fh->btv; + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + int rc; + + buf->vb.size = fh->lines * 2 * 2048; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (STATE_NEEDS_INIT == buf->vb.state) { + if (0 != (rc = videobuf_iolock(btv->c.pci, &buf->vb, NULL))) + goto fail; + if (0 != (rc = vbi_buffer_risc(btv,buf,fh->lines))) + goto fail; + } + buf->vb.state = STATE_PREPARED; + buf->vb.field = field; + dprintk("buf prepare %p: top=%p bottom=%p field=%s\n", + vb, &buf->top, &buf->bottom, + v4l2_field_names[buf->vb.field]); + return 0; + + fail: + bttv_dma_free(btv,buf); + return rc; +} + +static void +vbi_buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct bttv_fh *fh = q->priv_data; + struct bttv *btv = fh->btv; + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + + dprintk("queue %p\n",vb); + buf->vb.state = STATE_QUEUED; + list_add_tail(&buf->vb.queue,&btv->vcapture); + if (NULL == btv->cvbi) { + fh->btv->loop_irq |= 4; + bttv_set_dma(btv,0x0c); + } +} + +static void vbi_buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct bttv_fh *fh = q->priv_data; + struct bttv *btv = fh->btv; + struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb); + + dprintk("free %p\n",vb); + bttv_dma_free(fh->btv,buf); +} + +struct videobuf_queue_ops bttv_vbi_qops = { + .buf_setup = vbi_buffer_setup, + .buf_prepare = vbi_buffer_prepare, + .buf_queue = vbi_buffer_queue, + .buf_release = vbi_buffer_release, +}; + +/* ----------------------------------------------------------------------- */ + +void bttv_vbi_setlines(struct bttv_fh *fh, struct bttv *btv, int lines) +{ + int vdelay; + + if (lines < 1) + lines = 1; + if (lines > VBI_MAXLINES) + lines = VBI_MAXLINES; + fh->lines = lines; + + vdelay = btread(BT848_E_VDELAY_LO); + if (vdelay < lines*2) { + vdelay = lines*2; + btwrite(vdelay,BT848_E_VDELAY_LO); + btwrite(vdelay,BT848_O_VDELAY_LO); + } +} + +void bttv_vbi_try_fmt(struct bttv_fh *fh, struct v4l2_format *f) +{ + const struct bttv_tvnorm *tvnorm; + u32 start0,start1; + s32 count0,count1,count; + + tvnorm = &bttv_tvnorms[fh->btv->tvnorm]; + f->type = V4L2_BUF_TYPE_VBI_CAPTURE; + f->fmt.vbi.sampling_rate = tvnorm->Fsc; + f->fmt.vbi.samples_per_line = 2048; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.flags = 0; + switch (fh->btv->tvnorm) { + case 1: /* NTSC */ + start0 = 10; + start1 = 273; + break; + case 0: /* PAL */ + case 2: /* SECAM */ + default: + start0 = 7; + start1 = 320; + } + + count0 = (f->fmt.vbi.start[0] + f->fmt.vbi.count[0]) - start0; + count1 = (f->fmt.vbi.start[1] + f->fmt.vbi.count[1]) - start1; + count = max(count0,count1); + if (count > VBI_MAXLINES) + count = VBI_MAXLINES; + if (count < 1) + count = 1; + + f->fmt.vbi.start[0] = start0; + f->fmt.vbi.start[1] = start1; + f->fmt.vbi.count[0] = count; + f->fmt.vbi.count[1] = count; +} + +void bttv_vbi_get_fmt(struct bttv_fh *fh, struct v4l2_format *f) +{ + const struct bttv_tvnorm *tvnorm; + + tvnorm = &bttv_tvnorms[fh->btv->tvnorm]; + memset(f,0,sizeof(*f)); + f->type = V4L2_BUF_TYPE_VBI_CAPTURE; + f->fmt.vbi.sampling_rate = tvnorm->Fsc; + f->fmt.vbi.samples_per_line = 2048; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.count[0] = fh->lines; + f->fmt.vbi.count[1] = fh->lines; + f->fmt.vbi.flags = 0; + switch (fh->btv->tvnorm) { + case 1: /* NTSC */ + f->fmt.vbi.start[0] = 10; + f->fmt.vbi.start[1] = 273; + break; + case 0: /* PAL */ + case 2: /* SECAM */ + default: + f->fmt.vbi.start[0] = 7; + f->fmt.vbi.start[1] = 319; + } +} + +/* ----------------------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv.h b/drivers/media/video/bttv.h new file mode 100644 index 000000000000..8322b66e0905 --- /dev/null +++ b/drivers/media/video/bttv.h @@ -0,0 +1,338 @@ +/* + * $Id: bttv.h,v 1.17 2005/02/22 14:06:32 kraxel Exp $ + * + * bttv - Bt848 frame grabber driver + * + * card ID's and external interfaces of the bttv driver + * basically stuff needed by other drivers (i2c, lirc, ...) + * and is supported not to change much over time. + * + * Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de) + * (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + */ + +#ifndef _BTTV_H_ +#define _BTTV_H_ + +#include <linux/videodev.h> +#include <linux/i2c.h> + +/* ---------------------------------------------------------- */ +/* exported by bttv-cards.c */ + +#define BTTV_UNKNOWN 0x00 +#define BTTV_MIRO 0x01 +#define BTTV_HAUPPAUGE 0x02 +#define BTTV_STB 0x03 +#define BTTV_INTEL 0x04 +#define BTTV_DIAMOND 0x05 +#define BTTV_AVERMEDIA 0x06 +#define BTTV_MATRIX_VISION 0x07 +#define BTTV_FLYVIDEO 0x08 +#define BTTV_TURBOTV 0x09 +#define BTTV_HAUPPAUGE878 0x0a +#define BTTV_MIROPRO 0x0b +#define BTTV_ADSTECH_TV 0x0c +#define BTTV_AVERMEDIA98 0x0d +#define BTTV_VHX 0x0e +#define BTTV_ZOLTRIX 0x0f +#define BTTV_PIXVIEWPLAYTV 0x10 +#define BTTV_WINVIEW_601 0x11 +#define BTTV_AVEC_INTERCAP 0x12 +#define BTTV_LIFE_FLYKIT 0x13 +#define BTTV_CEI_RAFFLES 0x14 +#define BTTV_CONFERENCETV 0x15 +#define BTTV_PHOEBE_TVMAS 0x16 +#define BTTV_MODTEC_205 0x17 +#define BTTV_MAGICTVIEW061 0x18 +#define BTTV_VOBIS_BOOSTAR 0x19 +#define BTTV_HAUPPAUG_WCAM 0x1a +#define BTTV_MAXI 0x1b +#define BTTV_TERRATV 0x1c +#define BTTV_PXC200 0x1d +#define BTTV_FLYVIDEO_98 0x1e +#define BTTV_IPROTV 0x1f +#define BTTV_INTEL_C_S_PCI 0x20 +#define BTTV_TERRATVALUE 0x21 +#define BTTV_WINFAST2000 0x22 +#define BTTV_CHRONOS_VS2 0x23 +#define BTTV_TYPHOON_TVIEW 0x24 +#define BTTV_PXELVWPLTVPRO 0x25 +#define BTTV_MAGICTVIEW063 0x26 +#define BTTV_PINNACLE 0x27 +#define BTTV_STB2 0x28 +#define BTTV_AVPHONE98 0x29 +#define BTTV_PV951 0x2a +#define BTTV_ONAIR_TV 0x2b +#define BTTV_SIGMA_TVII_FM 0x2c +#define BTTV_MATRIX_VISION2 0x2d +#define BTTV_ZOLTRIX_GENIE 0x2e +#define BTTV_TERRATVRADIO 0x2f +#define BTTV_DYNALINK 0x30 +#define BTTV_GVBCTV3PCI 0x31 +#define BTTV_PXELVWPLTVPAK 0x32 +#define BTTV_EAGLE 0x33 +#define BTTV_PINNACLEPRO 0x34 +#define BTTV_TVIEW_RDS_FM 0x35 +#define BTTV_LIFETEC_9415 0x36 +#define BTTV_BESTBUY_EASYTV 0x37 +#define BTTV_FLYVIDEO_98FM 0x38 +#define BTTV_GMV1 0x3d +#define BTTV_BESTBUY_EASYTV2 0x3e +#define BTTV_ATI_TVWONDER 0x3f +#define BTTV_ATI_TVWONDERVE 0x40 +#define BTTV_FLYVIDEO2000 0x41 +#define BTTV_TERRATVALUER 0x42 +#define BTTV_GVBCTV4PCI 0x43 +#define BTTV_VOODOOTV_FM 0x44 +#define BTTV_AIMMS 0x45 +#define BTTV_PV_BT878P_PLUS 0x46 +#define BTTV_FLYVIDEO98EZ 0x47 +#define BTTV_PV_BT878P_9B 0x48 +#define BTTV_SENSORAY311 0x49 +#define BTTV_RV605 0x4a +#define BTTV_WINDVR 0x4c +#define BTTV_GRANDTEC 0x4d +#define BTTV_KWORLD 0x4e +#define BTTV_HAUPPAUGEPVR 0x50 +#define BTTV_GVBCTV5PCI 0x51 +#define BTTV_OSPREY1x0 0x52 +#define BTTV_OSPREY1x0_848 0x53 +#define BTTV_OSPREY101_848 0x54 +#define BTTV_OSPREY1x1 0x55 +#define BTTV_OSPREY1x1_SVID 0x56 +#define BTTV_OSPREY2xx 0x57 +#define BTTV_OSPREY2x0_SVID 0x58 +#define BTTV_OSPREY2x0 0x59 +#define BTTV_OSPREY500 0x5a +#define BTTV_OSPREY540 0x5b +#define BTTV_OSPREY2000 0x5c +#define BTTV_IDS_EAGLE 0x5d +#define BTTV_PINNACLESAT 0x5e +#define BTTV_FORMAC_PROTV 0x5f +#define BTTV_EURESYS_PICOLO 0x61 +#define BTTV_PV150 0x62 +#define BTTV_AD_TVK503 0x63 +#define BTTV_IVC200 0x66 +#define BTTV_XGUARD 0x67 +#define BTTV_NEBULA_DIGITV 0x68 +#define BTTV_PV143 0x69 +#define BTTV_IVC100 0x6e +#define BTTV_IVC120 0x6f +#define BTTV_PC_HDTV 0x70 +#define BTTV_TWINHAN_DST 0x71 +#define BTTV_WINFASTVC100 0x72 +#define BTTV_SIMUS_GVC1100 0x74 +#define BTTV_NGSTV_PLUS 0x75 +#define BTTV_LMLBT4 0x76 +#define BTTV_PICOLO_TETRA_CHIP 0x79 +#define BTTV_AVDVBT_771 0x7b +#define BTTV_AVDVBT_761 0x7c +#define BTTV_MATRIX_VISIONSQ 0x7d +#define BTTV_MATRIX_VISIONSLC 0x7e +#define BTTV_APAC_VIEWCOMP 0x7f +#define BTTV_DVICO_DVBT_LITE 0x80 +#define BTTV_TIBET_CS16 0x83 +#define BTTV_KODICOM_4400R 0x84 + +/* i2c address list */ +#define I2C_TSA5522 0xc2 +#define I2C_TDA7432 0x8a +#define I2C_BT832_ALT1 0x88 +#define I2C_BT832_ALT2 0x8a // alternate setting +#define I2C_TDA8425 0x82 +#define I2C_TDA9840 0x84 +#define I2C_TDA9850 0xb6 /* also used by 9855,9873 */ +#define I2C_TDA9874 0xb0 /* also used by 9875 */ +#define I2C_TDA9875 0xb0 +#define I2C_HAUPEE 0xa0 +#define I2C_STBEE 0xae +#define I2C_VHX 0xc0 +#define I2C_MSP3400 0x80 +#define I2C_MSP3400_ALT 0x88 +#define I2C_TEA6300 0x80 /* also used by 6320 */ +#define I2C_DPL3518 0x84 +#define I2C_TDA9887 0x86 + +/* more card-specific defines */ +#define PT2254_L_CHANNEL 0x10 +#define PT2254_R_CHANNEL 0x08 +#define PT2254_DBS_IN_2 0x400 +#define PT2254_DBS_IN_10 0x20000 +#define WINVIEW_PT2254_CLK 0x40 +#define WINVIEW_PT2254_DATA 0x20 +#define WINVIEW_PT2254_STROBE 0x80 + +/* digital_mode */ +#define DIGITAL_MODE_VIDEO 1 +#define DIGITAL_MODE_CAMERA 2 + +struct bttv_core { + /* device structs */ + struct pci_dev *pci; + struct i2c_adapter i2c_adap; + struct list_head subs; /* struct bttv_sub_device */ + + /* device config */ + unsigned int nr; /* dev nr (for printk("bttv%d: ..."); */ + unsigned int type; /* card type (pointer into tvcards[]) */ + char name[8]; /* dev name */ +}; + +struct bttv; + +struct tvcard +{ + char *name; + unsigned int video_inputs; + unsigned int audio_inputs; + unsigned int tuner; + unsigned int svhs; + unsigned int digital_mode; // DIGITAL_MODE_CAMERA or DIGITAL_MODE_VIDEO + u32 gpiomask; + u32 muxsel[16]; + u32 audiomux[6]; /* Tuner, Radio, external, internal, mute, stereo */ + u32 gpiomask2; /* GPIO MUX mask */ + + /* i2c audio flags */ + unsigned int no_msp34xx:1; + unsigned int no_tda9875:1; + unsigned int no_tda7432:1; + unsigned int needs_tvaudio:1; + unsigned int msp34xx_alt:1; + + /* flag: video pci function is unused */ + unsigned int no_video:1; + unsigned int has_dvb:1; + unsigned int has_remote:1; + unsigned int no_gpioirq:1; + + /* other settings */ + unsigned int pll; +#define PLL_NONE 0 +#define PLL_28 1 +#define PLL_35 2 + + unsigned int tuner_type; + unsigned int has_radio; + void (*audio_hook)(struct bttv *btv, struct video_audio *v, int set); + void (*muxsel_hook)(struct bttv *btv, unsigned int input); +}; + +extern struct tvcard bttv_tvcards[]; + +/* identification / initialization of the card */ +extern void bttv_idcard(struct bttv *btv); +extern void bttv_init_card1(struct bttv *btv); +extern void bttv_init_card2(struct bttv *btv); + +/* card-specific funtions */ +extern void tea5757_set_freq(struct bttv *btv, unsigned short freq); +extern void bttv_tda9880_setnorm(struct bttv *btv, int norm); + +/* extra tweaks for some chipsets */ +extern void bttv_check_chipset(void); +extern int bttv_handle_chipset(struct bttv *btv); + +/* ---------------------------------------------------------- */ +/* exported by bttv-if.c */ + +/* 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_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 +*/ +extern int bttv_gpio_enable(unsigned int card, + unsigned long mask, unsigned long data); + +/* fills data with GPDATA register contents + returns negative value if error occurred +*/ +extern int bttv_read_gpio(unsigned int card, unsigned long *data); + +/* sets GPDATA register to new value: + (data & mask) | (current_GPDATA_value & ~mask) + returns negative value if error occurred +*/ +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); + + + +/* ---------------------------------------------------------- */ +/* sysfs/driver-moded based gpio access interface */ + + +struct bttv_sub_device { + struct device dev; + struct bttv_core *core; + struct list_head list; +}; +#define to_bttv_sub_dev(x) container_of((x), struct bttv_sub_device, dev) + +struct bttv_sub_driver { + struct device_driver drv; + char wanted[BUS_ID_SIZE]; + void (*gpio_irq)(struct bttv_sub_device *sub); +}; +#define to_bttv_sub_drv(x) container_of((x), struct bttv_sub_driver, drv) + +int bttv_sub_register(struct bttv_sub_driver *drv, char *wanted); +int bttv_sub_unregister(struct bttv_sub_driver *drv); + +/* gpio access functions */ +void bttv_gpio_inout(struct bttv_core *core, u32 mask, u32 outbits); +u32 bttv_gpio_read(struct bttv_core *core); +void bttv_gpio_write(struct bttv_core *core, u32 value); +void bttv_gpio_bits(struct bttv_core *core, u32 mask, u32 bits); + +#define gpio_inout(mask,bits) bttv_gpio_inout(&btv->c, mask, bits) +#define gpio_read() bttv_gpio_read(&btv->c) +#define gpio_write(value) bttv_gpio_write(&btv->c, value) +#define gpio_bits(mask,bits) bttv_gpio_bits(&btv->c, mask, bits) + + +/* ---------------------------------------------------------- */ +/* i2c */ + +extern void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg); +extern int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for); +extern int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both); +extern void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr); + +#endif /* _BTTV_H_ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttvp.h b/drivers/media/video/bttvp.h new file mode 100644 index 000000000000..1a9ba7e1cf51 --- /dev/null +++ b/drivers/media/video/bttvp.h @@ -0,0 +1,399 @@ +/* + $Id: bttvp.h,v 1.17 2005/02/16 12:14:10 kraxel Exp $ + + bttv - Bt848 frame grabber driver + + bttv's *private* header file -- nobody other than bttv itself + should ever include this file. + + (c) 2000-2002 Gerd Knorr <kraxel@bytesex.org> + + 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. +*/ + +#ifndef _BTTVP_H_ +#define _BTTVP_H_ + +#include <linux/version.h> +#define BTTV_VERSION_CODE KERNEL_VERSION(0,9,15) + +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/pci.h> +#include <linux/input.h> +#include <asm/scatterlist.h> +#include <asm/io.h> + +#include <linux/device.h> +#include <media/video-buf.h> +#include <media/audiochip.h> +#include <media/tuner.h> +#include <media/tveeprom.h> +#include <media/ir-common.h> + +#include "bt848.h" +#include "bttv.h" +#include "btcx-risc.h" + +#ifdef __KERNEL__ + +#define FORMAT_FLAGS_DITHER 0x01 +#define FORMAT_FLAGS_PACKED 0x02 +#define FORMAT_FLAGS_PLANAR 0x04 +#define FORMAT_FLAGS_RAW 0x08 +#define FORMAT_FLAGS_CrCb 0x10 + +#define RISC_SLOT_O_VBI 4 +#define RISC_SLOT_O_FIELD 6 +#define RISC_SLOT_E_VBI 10 +#define RISC_SLOT_E_FIELD 12 +#define RISC_SLOT_LOOP 14 + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define RAW_LINES 640 +#define RAW_BPL 1024 + +#define UNSET (-1U) + +/* ---------------------------------------------------------- */ + +struct bttv_tvnorm { + int v4l2_id; + char *name; + u32 Fsc; + u16 swidth, sheight; /* scaled standard width, height */ + u16 totalwidth; + u8 adelay, bdelay, iform; + u32 scaledtwidth; + u16 hdelayx1, hactivex1; + u16 vdelay; + u8 vbipack; + u16 vtotal; + int sram; +}; +extern const struct bttv_tvnorm bttv_tvnorms[]; + +struct bttv_format { + char *name; + int palette; /* video4linux 1 */ + int fourcc; /* video4linux 2 */ + int btformat; /* BT848_COLOR_FMT_* */ + int btswap; /* BT848_COLOR_CTL_* */ + int depth; /* bit/pixel */ + int flags; + int hshift,vshift; /* for planar modes */ +}; + +/* ---------------------------------------------------------- */ + +struct bttv_geometry { + u8 vtc,crop,comb; + u16 width,hscale,hdelay; + u16 sheight,vscale,vdelay,vtotal; +}; + +struct bttv_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* bttv specific */ + const struct bttv_format *fmt; + int tvnorm; + int btformat; + int btswap; + struct bttv_geometry geo; + struct btcx_riscmem top; + struct btcx_riscmem bottom; +}; + +struct bttv_buffer_set { + struct bttv_buffer *top; /* top field buffer */ + struct bttv_buffer *bottom; /* bottom field buffer */ + unsigned int top_irq; + unsigned int frame_irq; +}; + +struct bttv_overlay { + int tvnorm; + struct v4l2_rect w; + enum v4l2_field field; + struct v4l2_clip *clips; + int nclips; + int setup_ok; +}; + +struct bttv_fh { + struct bttv *btv; + int resources; +#ifdef VIDIOC_G_PRIORITY + enum v4l2_priority prio; +#endif + enum v4l2_buf_type type; + + /* video capture */ + struct videobuf_queue cap; + const struct bttv_format *fmt; + int width; + int height; + + /* current settings */ + const struct bttv_format *ovfmt; + struct bttv_overlay ov; + + /* video overlay */ + struct videobuf_queue vbi; + int lines; +}; + +/* ---------------------------------------------------------- */ +/* bttv-risc.c */ + +/* risc code generators - capture */ +int bttv_risc_packed(struct bttv *btv, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int offset, unsigned int bpl, + unsigned int pitch, unsigned int lines); + +/* control dma register + risc main loop */ +void bttv_set_dma(struct bttv *btv, int override); +int bttv_risc_init_main(struct bttv *btv); +int bttv_risc_hook(struct bttv *btv, int slot, struct btcx_riscmem *risc, + int irqflags); + +/* capture buffer handling */ +int bttv_buffer_risc(struct bttv *btv, struct bttv_buffer *buf); +int bttv_buffer_activate_video(struct bttv *btv, + struct bttv_buffer_set *set); +int bttv_buffer_activate_vbi(struct bttv *btv, + struct bttv_buffer *vbi); +void bttv_dma_free(struct bttv *btv, struct bttv_buffer *buf); + +/* overlay handling */ +int bttv_overlay_risc(struct bttv *btv, struct bttv_overlay *ov, + const struct bttv_format *fmt, + struct bttv_buffer *buf); + + +/* ---------------------------------------------------------- */ +/* bttv-vbi.c */ + +void bttv_vbi_try_fmt(struct bttv_fh *fh, struct v4l2_format *f); +void bttv_vbi_get_fmt(struct bttv_fh *fh, struct v4l2_format *f); +void bttv_vbi_setlines(struct bttv_fh *fh, struct bttv *btv, int lines); + +extern struct videobuf_queue_ops bttv_vbi_qops; + +/* ---------------------------------------------------------- */ +/* bttv-gpio.c */ + + +extern struct bus_type bttv_sub_bus_type; +int bttv_sub_add_device(struct bttv_core *core, char *name); +int bttv_sub_del_devices(struct bttv_core *core); +void bttv_gpio_irq(struct bttv_core *core); + + +/* ---------------------------------------------------------- */ +/* bttv-driver.c */ + +/* insmod options */ +extern unsigned int bttv_verbose; +extern unsigned int bttv_debug; +extern unsigned int bttv_gpio; +extern void bttv_gpio_tracking(struct bttv *btv, char *comment); +extern int init_bttv_i2c(struct bttv *btv); +extern int fini_bttv_i2c(struct bttv *btv); + +#define vprintk if (bttv_verbose) printk +#define dprintk if (bttv_debug >= 1) printk +#define d2printk if (bttv_debug >= 2) printk + +/* our devices */ +#define BTTV_MAX 16 +extern unsigned int bttv_num; + +#define BTTV_MAX_FBUF 0x208000 +#define VBIBUF_SIZE (2048*VBI_MAXLINES*2) +#define BTTV_TIMEOUT (HZ/2) /* 0.5 seconds */ +#define BTTV_FREE_IDLE (HZ) /* one second */ + + +struct bttv_pll_info { + unsigned int pll_ifreq; /* PLL input frequency */ + unsigned int pll_ofreq; /* PLL output frequency */ + unsigned int pll_crystal; /* Crystal used for input */ + unsigned int pll_current; /* Currently programmed ofreq */ +}; + +/* for gpio-connected remote control */ +struct bttv_input { + struct input_dev dev; + struct ir_input_state ir; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; +}; + +struct bttv_suspend_state { + u32 gpio_enable; + u32 gpio_data; + int disabled; + int loop_irq; + struct bttv_buffer_set video; + struct bttv_buffer *vbi; +}; + +struct bttv { + struct bttv_core c; + + /* pci device config */ + unsigned short id; + unsigned char revision; + unsigned char __iomem *bt848_mmio; /* pointer to mmio */ + + /* card configuration info */ + unsigned int cardid; /* pci subsystem id (bt878 based ones) */ + unsigned int tuner_type; /* tuner chip type */ + unsigned int pinnacle_id; + unsigned int svhs; + struct bttv_pll_info pll; + int triton1; + int gpioirq; + int use_i2c_hw; + + /* old gpio interface */ + wait_queue_head_t gpioq; + int shutdown; + void (*audio_hook)(struct bttv *btv, struct video_audio *v, int set); + + /* new gpio interface */ + spinlock_t gpio_lock; + + /* i2c layer */ + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + int i2c_state, i2c_rc; + int i2c_done; + wait_queue_head_t i2c_queue; + + /* video4linux (1) */ + struct video_device *video_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + + /* infrared remote */ + int has_remote; + struct bttv_input *remote; + + /* locking */ + spinlock_t s_lock; + struct semaphore lock; + int resources; + struct semaphore reslock; +#ifdef VIDIOC_G_PRIORITY + struct v4l2_prio_state prio; +#endif + + /* video state */ + unsigned int input; + unsigned int audio; + unsigned long freq; + int tvnorm,hue,contrast,bright,saturation; + struct v4l2_framebuffer fbuf; + unsigned int field_count; + + /* various options */ + int opt_combfilter; + int opt_lumafilter; + int opt_automute; + int opt_chroma_agc; + int opt_adc_crush; + int opt_vcr_hack; + int opt_whitecrush_upper; + int opt_whitecrush_lower; + + /* radio data/state */ + int has_radio; + int radio_user; + + /* miro/pinnacle + Aimslab VHX + philips matchbox (tea5757 radio tuner) support */ + int has_matchbox; + int mbox_we; + int mbox_data; + int mbox_clk; + int mbox_most; + int mbox_mask; + + /* ISA stuff (Terratec Active Radio Upgrade) */ + int mbox_ior; + int mbox_iow; + int mbox_csel; + + /* risc memory management data + - must aquire s_lock before changing these + - only the irq handler is supported to touch top + bottom + vcurr */ + struct btcx_riscmem main; + struct bttv_buffer *screen; /* overlay */ + struct list_head capture; /* video capture queue */ + struct list_head vcapture; /* vbi capture queue */ + struct bttv_buffer_set curr; /* active buffers */ + struct bttv_buffer *cvbi; /* active vbi buffer */ + int loop_irq; + int new_input; + + unsigned long cap_ctl; + unsigned long dma_on; + struct timer_list timeout; + struct bttv_suspend_state state; + + /* stats */ + unsigned int errors; + unsigned int framedrop; + unsigned int irq_total; + unsigned int irq_me; + + unsigned int users; + struct bttv_fh init; +}; +extern struct bttv bttvs[BTTV_MAX]; + +/* private ioctls */ +#define BTTV_VERSION _IOR('v' , BASE_VIDIOCPRIVATE+6, int) +#define BTTV_VBISIZE _IOR('v' , BASE_VIDIOCPRIVATE+8, int) + +#endif + +#define btwrite(dat,adr) writel((dat), btv->bt848_mmio+(adr)) +#define btread(adr) readl(btv->bt848_mmio+(adr)) + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#endif /* _BTTVP_H_ */ + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bw-qcam.c b/drivers/media/video/bw-qcam.c new file mode 100644 index 000000000000..0065d0c240d1 --- /dev/null +++ b/drivers/media/video/bw-qcam.c @@ -0,0 +1,1027 @@ +/* + * QuickCam Driver For Video4Linux. + * + * Video4Linux conversion work by Alan Cox. + * Parport compatibility by Phil Blundell. + * Busy loop avoidance by Mark Cooke. + * + * Module parameters: + * + * maxpoll=<1 - 5000> + * + * When polling the QuickCam for a response, busy-wait for a + * maximum of this many loops. The default of 250 gives little + * impact on interactive response. + * + * NOTE: If this parameter is set too high, the processor + * will busy wait until this loop times out, and then + * slowly poll for a further 5 seconds before failing + * the transaction. You have been warned. + * + * yieldlines=<1 - 250> + * + * When acquiring a frame from the camera, the data gathering + * loop will yield back to the scheduler after completing + * this many lines. The default of 4 provides a trade-off + * between increased frame acquisition time and impact on + * interactive response. + */ + +/* qcam-lib.c -- Library for programming with the Connectix QuickCam. + * See the included documentation for usage instructions and details + * of the protocol involved. */ + + +/* Version 0.5, August 4, 1996 */ +/* Version 0.7, August 27, 1996 */ +/* Version 0.9, November 17, 1996 */ + + +/****************************************************************** + +Copyright (C) 1996 by Scott Laird + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/parport.h> +#include <linux/sched.h> +#include <linux/videodev.h> +#include <asm/semaphore.h> +#include <asm/uaccess.h> + +#include "bw-qcam.h" + +static unsigned int maxpoll=250; /* Maximum busy-loop count for qcam I/O */ +static unsigned int yieldlines=4; /* Yield after this many during capture */ +static int video_nr = -1; + +module_param(maxpoll, int, 0); +module_param(yieldlines, int, 0); +module_param(video_nr, int, 0); + +static inline int read_lpstatus(struct qcam_device *q) +{ + return parport_read_status(q->pport); +} + +static inline int read_lpdata(struct qcam_device *q) +{ + return parport_read_data(q->pport); +} + +static inline void write_lpdata(struct qcam_device *q, int d) +{ + parport_write_data(q->pport, d); +} + +static inline void write_lpcontrol(struct qcam_device *q, int d) +{ + parport_write_control(q->pport, d); +} + +static int qc_waithand(struct qcam_device *q, int val); +static int qc_command(struct qcam_device *q, int command); +static int qc_readparam(struct qcam_device *q); +static int qc_setscanmode(struct qcam_device *q); +static int qc_readbytes(struct qcam_device *q, char buffer[]); + +static struct video_device qcam_template; + +static int qc_calibrate(struct qcam_device *q) +{ + /* + * Bugfix by Hanno Mueller hmueller@kabel.de, Mai 21 96 + * The white balance is an individiual value for each + * quickcam. + */ + + int value; + int count = 0; + + qc_command(q, 27); /* AutoAdjustOffset */ + qc_command(q, 0); /* Dummy Parameter, ignored by the camera */ + + /* GetOffset (33) will read 255 until autocalibration */ + /* is finished. After that, a value of 1-254 will be */ + /* returned. */ + + do { + qc_command(q, 33); + value = qc_readparam(q); + mdelay(1); + schedule(); + count++; + } while (value == 0xff && count<2048); + + q->whitebal = value; + return value; +} + +/* Initialize the QuickCam driver control structure. This is where + * defaults are set for people who don't have a config file.*/ + +static struct qcam_device *qcam_init(struct parport *port) +{ + struct qcam_device *q; + + q = kmalloc(sizeof(struct qcam_device), GFP_KERNEL); + if(q==NULL) + return NULL; + + q->pport = port; + q->pdev = parport_register_device(port, "bw-qcam", NULL, NULL, + NULL, 0, NULL); + if (q->pdev == NULL) + { + printk(KERN_ERR "bw-qcam: couldn't register for %s.\n", + port->name); + kfree(q); + return NULL; + } + + memcpy(&q->vdev, &qcam_template, sizeof(qcam_template)); + + init_MUTEX(&q->lock); + + q->port_mode = (QC_ANY | QC_NOTSET); + q->width = 320; + q->height = 240; + q->bpp = 4; + q->transfer_scale = 2; + q->contrast = 192; + q->brightness = 180; + q->whitebal = 105; + q->top = 1; + q->left = 14; + q->mode = -1; + q->status = QC_PARAM_CHANGE; + return q; +} + + +/* qc_command is probably a bit of a misnomer -- it's used to send + * bytes *to* the camera. Generally, these bytes are either commands + * or arguments to commands, so the name fits, but it still bugs me a + * bit. See the documentation for a list of commands. */ + +static int qc_command(struct qcam_device *q, int command) +{ + int n1, n2; + int cmd; + + write_lpdata(q, command); + write_lpcontrol(q, 6); + + n1 = qc_waithand(q, 1); + + write_lpcontrol(q, 0xe); + n2 = qc_waithand(q, 0); + + cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4); + return cmd; +} + +static int qc_readparam(struct qcam_device *q) +{ + int n1, n2; + int cmd; + + write_lpcontrol(q, 6); + n1 = qc_waithand(q, 1); + + write_lpcontrol(q, 0xe); + n2 = qc_waithand(q, 0); + + cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4); + return cmd; +} + +/* qc_waithand busy-waits for a handshake signal from the QuickCam. + * Almost all communication with the camera requires handshaking. */ + +static int qc_waithand(struct qcam_device *q, int val) +{ + int status; + int runs=0; + + if (val) + { + while (!((status = read_lpstatus(q)) & 8)) + { + /* 1000 is enough spins on the I/O for all normal + cases, at that point we start to poll slowly + until the camera wakes up. However, we are + busy blocked until the camera responds, so + setting it lower is much better for interactive + response. */ + + if(runs++>maxpoll) + { + msleep_interruptible(5); + } + if(runs>(maxpoll+1000)) /* 5 seconds */ + return -1; + } + } + else + { + while (((status = read_lpstatus(q)) & 8)) + { + /* 1000 is enough spins on the I/O for all normal + cases, at that point we start to poll slowly + until the camera wakes up. However, we are + busy blocked until the camera responds, so + setting it lower is much better for interactive + response. */ + + if(runs++>maxpoll) + { + msleep_interruptible(5); + } + if(runs++>(maxpoll+1000)) /* 5 seconds */ + return -1; + } + } + + return status; +} + +/* Waithand2 is used when the qcam is in bidirectional mode, and the + * handshaking signal is CamRdy2 (bit 0 of data reg) instead of CamRdy1 + * (bit 3 of status register). It also returns the last value read, + * since this data is useful. */ + +static unsigned int qc_waithand2(struct qcam_device *q, int val) +{ + unsigned int status; + int runs=0; + + do + { + status = read_lpdata(q); + /* 1000 is enough spins on the I/O for all normal + cases, at that point we start to poll slowly + until the camera wakes up. However, we are + busy blocked until the camera responds, so + setting it lower is much better for interactive + response. */ + + if(runs++>maxpoll) + { + msleep_interruptible(5); + } + if(runs++>(maxpoll+1000)) /* 5 seconds */ + return 0; + } + while ((status & 1) != val); + + return status; +} + + +/* Try to detect a QuickCam. It appears to flash the upper 4 bits of + the status register at 5-10 Hz. This is only used in the autoprobe + code. Be aware that this isn't the way Connectix detects the + camera (they send a reset and try to handshake), but this should be + almost completely safe, while their method screws up my printer if + I plug it in before the camera. */ + +static int qc_detect(struct qcam_device *q) +{ + int reg, lastreg; + int count = 0; + int i; + + lastreg = reg = read_lpstatus(q) & 0xf0; + + for (i = 0; i < 500; i++) + { + reg = read_lpstatus(q) & 0xf0; + if (reg != lastreg) + count++; + lastreg = reg; + mdelay(2); + } + + +#if 0 + /* Force camera detection during testing. Sometimes the camera + won't be flashing these bits. Possibly unloading the module + in the middle of a grab? Or some timeout condition? + I've seen this parameter as low as 19 on my 450Mhz box - mpc */ + printk("Debugging: QCam detection counter <30-200 counts as detected>: %d\n", count); + return 1; +#endif + + /* Be (even more) liberal in what you accept... */ + +/* if (count > 30 && count < 200) */ + if (count > 20 && count < 300) + return 1; /* found */ + else + return 0; /* not found */ +} + + +/* Reset the QuickCam. This uses the same sequence the Windows + * QuickPic program uses. Someone with a bi-directional port should + * check that bi-directional mode is detected right, and then + * implement bi-directional mode in qc_readbyte(). */ + +static void qc_reset(struct qcam_device *q) +{ + switch (q->port_mode & QC_FORCE_MASK) + { + case QC_FORCE_UNIDIR: + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR; + break; + + case QC_FORCE_BIDIR: + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR; + break; + + case QC_ANY: + write_lpcontrol(q, 0x20); + write_lpdata(q, 0x75); + + if (read_lpdata(q) != 0x75) { + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR; + } else { + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR; + } + break; + } + + write_lpcontrol(q, 0xb); + udelay(250); + write_lpcontrol(q, 0xe); + qc_setscanmode(q); /* in case port_mode changed */ +} + + +/* Decide which scan mode to use. There's no real requirement that + * the scanmode match the resolution in q->height and q-> width -- the + * camera takes the picture at the resolution specified in the + * "scanmode" and then returns the image at the resolution specified + * with the resolution commands. If the scan is bigger than the + * requested resolution, the upper-left hand corner of the scan is + * returned. If the scan is smaller, then the rest of the image + * returned contains garbage. */ + +static int qc_setscanmode(struct qcam_device *q) +{ + int old_mode = q->mode; + + switch (q->transfer_scale) + { + case 1: + q->mode = 0; + break; + case 2: + q->mode = 4; + break; + case 4: + q->mode = 8; + break; + } + + switch (q->bpp) + { + case 4: + break; + case 6: + q->mode += 2; + break; + } + + switch (q->port_mode & QC_MODE_MASK) + { + case QC_BIDIR: + q->mode += 1; + break; + case QC_NOTSET: + case QC_UNIDIR: + break; + } + + if (q->mode != old_mode) + q->status |= QC_PARAM_CHANGE; + + return 0; +} + + +/* Reset the QuickCam and program for brightness, contrast, + * white-balance, and resolution. */ + +static void qc_set(struct qcam_device *q) +{ + int val; + int val2; + + qc_reset(q); + + /* Set the brightness. Yes, this is repetitive, but it works. + * Shorter versions seem to fail subtly. Feel free to try :-). */ + /* I think the problem was in qc_command, not here -- bls */ + + qc_command(q, 0xb); + qc_command(q, q->brightness); + + val = q->height / q->transfer_scale; + qc_command(q, 0x11); + qc_command(q, val); + if ((q->port_mode & QC_MODE_MASK) == QC_UNIDIR && q->bpp == 6) { + /* The normal "transfers per line" calculation doesn't seem to work + as expected here (and yet it works fine in qc_scan). No idea + why this case is the odd man out. Fortunately, Laird's original + working version gives me a good way to guess at working values. + -- bls */ + val = q->width; + val2 = q->transfer_scale * 4; + } else { + val = q->width * q->bpp; + val2 = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) * + q->transfer_scale; + } + val = (val + val2 - 1) / val2; + qc_command(q, 0x13); + qc_command(q, val); + + /* Setting top and left -- bls */ + qc_command(q, 0xd); + qc_command(q, q->top); + qc_command(q, 0xf); + qc_command(q, q->left / 2); + + qc_command(q, 0x19); + qc_command(q, q->contrast); + qc_command(q, 0x1f); + qc_command(q, q->whitebal); + + /* Clear flag that we must update the grabbing parameters on the camera + before we grab the next frame */ + q->status &= (~QC_PARAM_CHANGE); +} + +/* Qc_readbytes reads some bytes from the QC and puts them in + the supplied buffer. It returns the number of bytes read, + or -1 on error. */ + +static inline int qc_readbytes(struct qcam_device *q, char buffer[]) +{ + int ret=1; + unsigned int hi, lo; + unsigned int hi2, lo2; + static int state = 0; + + if (buffer == NULL) + { + state = 0; + return 0; + } + + switch (q->port_mode & QC_MODE_MASK) + { + case QC_BIDIR: /* Bi-directional Port */ + write_lpcontrol(q, 0x26); + lo = (qc_waithand2(q, 1) >> 1); + hi = (read_lpstatus(q) >> 3) & 0x1f; + write_lpcontrol(q, 0x2e); + lo2 = (qc_waithand2(q, 0) >> 1); + hi2 = (read_lpstatus(q) >> 3) & 0x1f; + switch (q->bpp) + { + case 4: + buffer[0] = lo & 0xf; + buffer[1] = ((lo & 0x70) >> 4) | ((hi & 1) << 3); + buffer[2] = (hi & 0x1e) >> 1; + buffer[3] = lo2 & 0xf; + buffer[4] = ((lo2 & 0x70) >> 4) | ((hi2 & 1) << 3); + buffer[5] = (hi2 & 0x1e) >> 1; + ret = 6; + break; + case 6: + buffer[0] = lo & 0x3f; + buffer[1] = ((lo & 0x40) >> 6) | (hi << 1); + buffer[2] = lo2 & 0x3f; + buffer[3] = ((lo2 & 0x40) >> 6) | (hi2 << 1); + ret = 4; + break; + } + break; + + case QC_UNIDIR: /* Unidirectional Port */ + write_lpcontrol(q, 6); + lo = (qc_waithand(q, 1) & 0xf0) >> 4; + write_lpcontrol(q, 0xe); + hi = (qc_waithand(q, 0) & 0xf0) >> 4; + + switch (q->bpp) + { + case 4: + buffer[0] = lo; + buffer[1] = hi; + ret = 2; + break; + case 6: + switch (state) + { + case 0: + buffer[0] = (lo << 2) | ((hi & 0xc) >> 2); + q->saved_bits = (hi & 3) << 4; + state = 1; + ret = 1; + break; + case 1: + buffer[0] = lo | q->saved_bits; + q->saved_bits = hi << 2; + state = 2; + ret = 1; + break; + case 2: + buffer[0] = ((lo & 0xc) >> 2) | q->saved_bits; + buffer[1] = ((lo & 3) << 4) | hi; + state = 0; + ret = 2; + break; + } + break; + } + break; + } + return ret; +} + +/* requests a scan from the camera. It sends the correct instructions + * to the camera and then reads back the correct number of bytes. In + * previous versions of this routine the return structure contained + * the raw output from the camera, and there was a 'qc_convertscan' + * function that converted that to a useful format. In version 0.3 I + * rolled qc_convertscan into qc_scan and now I only return the + * converted scan. The format is just an one-dimensional array of + * characters, one for each pixel, with 0=black up to n=white, where + * n=2^(bit depth)-1. Ask me for more details if you don't understand + * this. */ + +static long qc_capture(struct qcam_device * q, char __user *buf, unsigned long len) +{ + int i, j, k, yield; + int bytes; + int linestotrans, transperline; + int divisor; + int pixels_per_line; + int pixels_read = 0; + int got=0; + char buffer[6]; + int shift=8-q->bpp; + char invert; + + if (q->mode == -1) + return -ENXIO; + + qc_command(q, 0x7); + qc_command(q, q->mode); + + if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR) + { + write_lpcontrol(q, 0x2e); /* turn port around */ + write_lpcontrol(q, 0x26); + (void) qc_waithand(q, 1); + write_lpcontrol(q, 0x2e); + (void) qc_waithand(q, 0); + } + + /* strange -- should be 15:63 below, but 4bpp is odd */ + invert = (q->bpp == 4) ? 16 : 63; + + linestotrans = q->height / q->transfer_scale; + pixels_per_line = q->width / q->transfer_scale; + transperline = q->width * q->bpp; + divisor = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) * + q->transfer_scale; + transperline = (transperline + divisor - 1) / divisor; + + for (i = 0, yield = yieldlines; i < linestotrans; i++) + { + for (pixels_read = j = 0; j < transperline; j++) + { + bytes = qc_readbytes(q, buffer); + for (k = 0; k < bytes && (pixels_read + k) < pixels_per_line; k++) + { + int o; + if (buffer[k] == 0 && invert == 16) + { + /* 4bpp is odd (again) -- inverter is 16, not 15, but output + must be 0-15 -- bls */ + buffer[k] = 16; + } + o=i*pixels_per_line + pixels_read + k; + if(o<len) + { + got++; + put_user((invert - buffer[k])<<shift, buf+o); + } + } + pixels_read += bytes; + } + (void) qc_readbytes(q, NULL); /* reset state machine */ + + /* Grabbing an entire frame from the quickcam is a lengthy + process. We don't (usually) want to busy-block the + processor for the entire frame. yieldlines is a module + parameter. If we yield every line, the minimum frame + time will be 240 / 200 = 1.2 seconds. The compile-time + default is to yield every 4 lines. */ + if (i >= yield) { + msleep_interruptible(5); + yield = i + yieldlines; + } + } + + if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR) + { + write_lpcontrol(q, 2); + write_lpcontrol(q, 6); + udelay(3); + write_lpcontrol(q, 0xe); + } + if(got<len) + return got; + return len; +} + +/* + * Video4linux interfacing + */ + +static int qcam_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + strcpy(b->name, "Quickcam"); + b->type = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_MONOCHROME; + b->channels = 1; + b->audios = 0; + b->maxwidth = 320; + b->maxheight = 240; + b->minwidth = 80; + b->minheight = 60; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + if(v->channel!=0) + return -EINVAL; + v->flags=0; + v->tuners=0; + /* Good question.. its composite or SVHS so.. */ + v->type = VIDEO_TYPE_CAMERA; + strcpy(v->name, "Camera"); + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if(v->channel!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + strcpy(v->name, "Format"); + v->rangelow=0; + v->rangehigh=0; + v->flags= 0; + v->mode = VIDEO_MODE_AUTO; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + if(v->mode!=VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + p->colour=0x8000; + p->hue=0x8000; + p->brightness=qcam->brightness<<8; + p->contrast=qcam->contrast<<8; + p->whiteness=qcam->whitebal<<8; + p->depth=qcam->bpp; + p->palette=VIDEO_PALETTE_GREY; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + if(p->palette!=VIDEO_PALETTE_GREY) + return -EINVAL; + if(p->depth!=4 && p->depth!=6) + return -EINVAL; + + /* + * Now load the camera. + */ + + qcam->brightness = p->brightness>>8; + qcam->contrast = p->contrast>>8; + qcam->whitebal = p->whiteness>>8; + qcam->bpp = p->depth; + + down(&qcam->lock); + qc_setscanmode(qcam); + up(&qcam->lock); + qcam->status |= QC_PARAM_CHANGE; + + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + if(vw->flags) + return -EINVAL; + if(vw->clipcount) + return -EINVAL; + if(vw->height<60||vw->height>240) + return -EINVAL; + if(vw->width<80||vw->width>320) + return -EINVAL; + + qcam->width = 320; + qcam->height = 240; + qcam->transfer_scale = 4; + + if(vw->width>=160 && vw->height>=120) + { + qcam->transfer_scale = 2; + } + if(vw->width>=320 && vw->height>=240) + { + qcam->width = 320; + qcam->height = 240; + qcam->transfer_scale = 1; + } + down(&qcam->lock); + qc_setscanmode(qcam); + up(&qcam->lock); + + /* We must update the camera before we grab. We could + just have changed the grab size */ + qcam->status |= QC_PARAM_CHANGE; + + /* Ok we figured out what to use from our wide choice */ + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + memset(vw, 0, sizeof(*vw)); + vw->width=qcam->width/qcam->transfer_scale; + vw->height=qcam->height/qcam->transfer_scale; + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int qcam_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, qcam_do_ioctl); +} + +static ssize_t qcam_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)v; + int len; + parport_claim_or_block(qcam->pdev); + + down(&qcam->lock); + + qc_reset(qcam); + + /* Update the camera parameters if we need to */ + if (qcam->status & QC_PARAM_CHANGE) + qc_set(qcam); + + len=qc_capture(qcam, buf,count); + + up(&qcam->lock); + + parport_release(qcam->pdev); + return len; +} + +static struct file_operations qcam_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = qcam_ioctl, + .read = qcam_read, + .llseek = no_llseek, +}; +static struct video_device qcam_template= +{ + .owner = THIS_MODULE, + .name = "Connectix Quickcam", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_QCAM_BW, + .fops = &qcam_fops, +}; + +#define MAX_CAMS 4 +static struct qcam_device *qcams[MAX_CAMS]; +static unsigned int num_cams = 0; + +static int init_bwqcam(struct parport *port) +{ + struct qcam_device *qcam; + + if (num_cams == MAX_CAMS) + { + printk(KERN_ERR "Too many Quickcams (max %d)\n", MAX_CAMS); + return -ENOSPC; + } + + qcam=qcam_init(port); + if(qcam==NULL) + return -ENODEV; + + parport_claim_or_block(qcam->pdev); + + qc_reset(qcam); + + if(qc_detect(qcam)==0) + { + parport_release(qcam->pdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + qc_calibrate(qcam); + + parport_release(qcam->pdev); + + printk(KERN_INFO "Connectix Quickcam on %s\n", qcam->pport->name); + + if(video_register_device(&qcam->vdev, VFL_TYPE_GRABBER, video_nr)==-1) + { + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + qcams[num_cams++] = qcam; + + return 0; +} + +static void close_bwqcam(struct qcam_device *qcam) +{ + video_unregister_device(&qcam->vdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); +} + +/* The parport parameter controls which parports will be scanned. + * Scanning all parports causes some printers to print a garbage page. + * -- March 14, 1999 Billy Donahue <billy@escape.com> */ +#ifdef MODULE +static char *parport[MAX_CAMS] = { NULL, }; +module_param_array(parport, charp, NULL, 0); +#endif + +static int accept_bwqcam(struct parport *port) +{ +#ifdef MODULE + int n; + + if (parport[0] && strncmp(parport[0], "auto", 4) != 0) { + /* user gave parport parameters */ + for(n=0; parport[n] && n<MAX_CAMS; n++){ + char *ep; + unsigned long r; + r = simple_strtoul(parport[n], &ep, 0); + if (ep == parport[n]) { + printk(KERN_ERR + "bw-qcam: bad port specifier \"%s\"\n", + parport[n]); + continue; + } + if (r == port->number) + return 1; + } + return 0; + } +#endif + return 1; +} + +static void bwqcam_attach(struct parport *port) +{ + if (accept_bwqcam(port)) + init_bwqcam(port); +} + +static void bwqcam_detach(struct parport *port) +{ + int i; + for (i = 0; i < num_cams; i++) { + struct qcam_device *qcam = qcams[i]; + if (qcam && qcam->pdev->port == port) { + qcams[i] = NULL; + close_bwqcam(qcam); + } + } +} + +static struct parport_driver bwqcam_driver = { + .name = "bw-qcam", + .attach = bwqcam_attach, + .detach = bwqcam_detach, +}; + +static void __exit exit_bw_qcams(void) +{ + parport_unregister_driver(&bwqcam_driver); +} + +static int __init init_bw_qcams(void) +{ +#ifdef MODULE + /* Do some sanity checks on the module parameters. */ + if (maxpoll > 5000) { + printk("Connectix Quickcam max-poll was above 5000. Using 5000.\n"); + maxpoll = 5000; + } + + if (yieldlines < 1) { + printk("Connectix Quickcam yieldlines was less than 1. Using 1.\n"); + yieldlines = 1; + } +#endif + return parport_register_driver(&bwqcam_driver); +} + +module_init(init_bw_qcams); +module_exit(exit_bw_qcams); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/bw-qcam.h b/drivers/media/video/bw-qcam.h new file mode 100644 index 000000000000..723e8ad9e56a --- /dev/null +++ b/drivers/media/video/bw-qcam.h @@ -0,0 +1,68 @@ +/* + * Video4Linux bw-qcam driver + * + * Derived from code.. + */ + +/****************************************************************** + +Copyright (C) 1996 by Scott Laird + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + +/* One from column A... */ +#define QC_NOTSET 0 +#define QC_UNIDIR 1 +#define QC_BIDIR 2 +#define QC_SERIAL 3 + +/* ... and one from column B */ +#define QC_ANY 0x00 +#define QC_FORCE_UNIDIR 0x10 +#define QC_FORCE_BIDIR 0x20 +#define QC_FORCE_SERIAL 0x30 +/* in the port_mode member */ + +#define QC_MODE_MASK 0x07 +#define QC_FORCE_MASK 0x70 + +#define MAX_HEIGHT 243 +#define MAX_WIDTH 336 + +/* Bit fields for status flags */ +#define QC_PARAM_CHANGE 0x01 /* Camera status change has occurred */ + +struct qcam_device { + struct video_device vdev; + struct pardevice *pdev; + struct parport *pport; + struct semaphore lock; + int width, height; + int bpp; + int mode; + int contrast, brightness, whitebal; + int port_mode; + int transfer_scale; + int top, left; + int status; + unsigned int saved_bits; +}; diff --git a/drivers/media/video/c-qcam.c b/drivers/media/video/c-qcam.c new file mode 100644 index 000000000000..75442ec49f35 --- /dev/null +++ b/drivers/media/video/c-qcam.c @@ -0,0 +1,855 @@ +/* + * Video4Linux Colour QuickCam driver + * Copyright 1997-2000 Philip Blundell <philb@gnu.org> + * + * Module parameters: + * + * parport=auto -- probe all parports (default) + * parport=0 -- parport0 becomes qcam1 + * parport=2,0,1 -- parports 2,0,1 are tried in that order + * + * probe=0 -- do no probing, assume camera is present + * probe=1 -- use IEEE-1284 autoprobe data only (default) + * probe=2 -- probe aggressively for cameras + * + * force_rgb=1 -- force data format to RGB (default is BGR) + * + * The parport parameter controls which parports will be scanned. + * Scanning all parports causes some printers to print a garbage page. + * -- March 14, 1999 Billy Donahue <billy@escape.com> + * + * Fixed data format to BGR, added force_rgb parameter. Added missing + * parport_unregister_driver() on module removal. + * -- May 28, 2000 Claudio Matsuoka <claudio@conectiva.com> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/parport.h> +#include <linux/sched.h> +#include <linux/videodev.h> +#include <asm/semaphore.h> +#include <asm/uaccess.h> + +struct qcam_device { + struct video_device vdev; + struct pardevice *pdev; + struct parport *pport; + int width, height; + int ccd_width, ccd_height; + int mode; + int contrast, brightness, whitebal; + int top, left; + unsigned int bidirectional; + struct semaphore lock; +}; + +/* cameras maximum */ +#define MAX_CAMS 4 + +/* The three possible QuickCam modes */ +#define QC_MILLIONS 0x18 +#define QC_BILLIONS 0x10 +#define QC_THOUSANDS 0x08 /* with VIDEC compression (not supported) */ + +/* The three possible decimations */ +#define QC_DECIMATION_1 0 +#define QC_DECIMATION_2 2 +#define QC_DECIMATION_4 4 + +#define BANNER "Colour QuickCam for Video4Linux v0.05" + +static int parport[MAX_CAMS] = { [1 ... MAX_CAMS-1] = -1 }; +static int probe = 2; +static int force_rgb = 0; +static int video_nr = -1; + +static inline void qcam_set_ack(struct qcam_device *qcam, unsigned int i) +{ + /* note: the QC specs refer to the PCAck pin by voltage, not + software level. PC ports have builtin inverters. */ + parport_frob_control(qcam->pport, 8, i?8:0); +} + +static inline unsigned int qcam_ready1(struct qcam_device *qcam) +{ + return (parport_read_status(qcam->pport) & 0x8)?1:0; +} + +static inline unsigned int qcam_ready2(struct qcam_device *qcam) +{ + return (parport_read_data(qcam->pport) & 0x1)?1:0; +} + +static unsigned int qcam_await_ready1(struct qcam_device *qcam, + int value) +{ + unsigned long oldjiffies = jiffies; + unsigned int i; + + for (oldjiffies = jiffies; (jiffies - oldjiffies) < (HZ/25); ) + if (qcam_ready1(qcam) == value) + return 0; + + /* If the camera didn't respond within 1/25 second, poll slowly + for a while. */ + for (i = 0; i < 50; i++) + { + if (qcam_ready1(qcam) == value) + return 0; + msleep_interruptible(100); + } + + /* Probably somebody pulled the plug out. Not much we can do. */ + printk(KERN_ERR "c-qcam: ready1 timeout (%d) %x %x\n", value, + parport_read_status(qcam->pport), + parport_read_control(qcam->pport)); + return 1; +} + +static unsigned int qcam_await_ready2(struct qcam_device *qcam, int value) +{ + unsigned long oldjiffies = jiffies; + unsigned int i; + + for (oldjiffies = jiffies; (jiffies - oldjiffies) < (HZ/25); ) + if (qcam_ready2(qcam) == value) + return 0; + + /* If the camera didn't respond within 1/25 second, poll slowly + for a while. */ + for (i = 0; i < 50; i++) + { + if (qcam_ready2(qcam) == value) + return 0; + msleep_interruptible(100); + } + + /* Probably somebody pulled the plug out. Not much we can do. */ + printk(KERN_ERR "c-qcam: ready2 timeout (%d) %x %x %x\n", value, + parport_read_status(qcam->pport), + parport_read_control(qcam->pport), + parport_read_data(qcam->pport)); + return 1; +} + +static int qcam_read_data(struct qcam_device *qcam) +{ + unsigned int idata; + qcam_set_ack(qcam, 0); + if (qcam_await_ready1(qcam, 1)) return -1; + idata = parport_read_status(qcam->pport) & 0xf0; + qcam_set_ack(qcam, 1); + if (qcam_await_ready1(qcam, 0)) return -1; + idata |= (parport_read_status(qcam->pport) >> 4); + return idata; +} + +static int qcam_write_data(struct qcam_device *qcam, unsigned int data) +{ + unsigned int idata; + parport_write_data(qcam->pport, data); + idata = qcam_read_data(qcam); + if (data != idata) + { + printk(KERN_WARNING "cqcam: sent %x but received %x\n", data, + idata); + return 1; + } + return 0; +} + +static inline int qcam_set(struct qcam_device *qcam, unsigned int cmd, unsigned int data) +{ + if (qcam_write_data(qcam, cmd)) + return -1; + if (qcam_write_data(qcam, data)) + return -1; + return 0; +} + +static inline int qcam_get(struct qcam_device *qcam, unsigned int cmd) +{ + if (qcam_write_data(qcam, cmd)) + return -1; + return qcam_read_data(qcam); +} + +static int qc_detect(struct qcam_device *qcam) +{ + unsigned int stat, ostat, i, count = 0; + + /* The probe routine below is not very reliable. The IEEE-1284 + probe takes precedence. */ + /* XXX Currently parport provides no way to distinguish between + "the IEEE probe was not done" and "the probe was done, but + no device was found". Fix this one day. */ + if (qcam->pport->probe_info[0].class == PARPORT_CLASS_MEDIA + && qcam->pport->probe_info[0].model + && !strcmp(qcam->pdev->port->probe_info[0].model, + "Color QuickCam 2.0")) { + printk(KERN_DEBUG "QuickCam: Found by IEEE1284 probe.\n"); + return 1; + } + + if (probe < 2) + return 0; + + parport_write_control(qcam->pport, 0xc); + + /* look for a heartbeat */ + ostat = stat = parport_read_status(qcam->pport); + for (i=0; i<250; i++) + { + mdelay(1); + stat = parport_read_status(qcam->pport); + if (ostat != stat) + { + if (++count >= 3) return 1; + ostat = stat; + } + } + + /* Reset the camera and try again */ + parport_write_control(qcam->pport, 0xc); + parport_write_control(qcam->pport, 0x8); + mdelay(1); + parport_write_control(qcam->pport, 0xc); + mdelay(1); + count = 0; + + ostat = stat = parport_read_status(qcam->pport); + for (i=0; i<250; i++) + { + mdelay(1); + stat = parport_read_status(qcam->pport); + if (ostat != stat) + { + if (++count >= 3) return 1; + ostat = stat; + } + } + + /* no (or flatline) camera, give up */ + return 0; +} + +static void qc_reset(struct qcam_device *qcam) +{ + parport_write_control(qcam->pport, 0xc); + parport_write_control(qcam->pport, 0x8); + mdelay(1); + parport_write_control(qcam->pport, 0xc); + mdelay(1); +} + +/* Reset the QuickCam and program for brightness, contrast, + * white-balance, and resolution. */ + +static void qc_setup(struct qcam_device *q) +{ + qc_reset(q); + + /* Set the brightness. */ + qcam_set(q, 11, q->brightness); + + /* Set the height and width. These refer to the actual + CCD area *before* applying the selected decimation. */ + qcam_set(q, 17, q->ccd_height); + qcam_set(q, 19, q->ccd_width / 2); + + /* Set top and left. */ + qcam_set(q, 0xd, q->top); + qcam_set(q, 0xf, q->left); + + /* Set contrast and white balance. */ + qcam_set(q, 0x19, q->contrast); + qcam_set(q, 0x1f, q->whitebal); + + /* Set the speed. */ + qcam_set(q, 45, 2); +} + +/* Read some bytes from the camera and put them in the buffer. + nbytes should be a multiple of 3, because bidirectional mode gives + us three bytes at a time. */ + +static unsigned int qcam_read_bytes(struct qcam_device *q, unsigned char *buf, unsigned int nbytes) +{ + unsigned int bytes = 0; + + qcam_set_ack(q, 0); + if (q->bidirectional) + { + /* It's a bidirectional port */ + while (bytes < nbytes) + { + unsigned int lo1, hi1, lo2, hi2; + unsigned char r, g, b; + + if (qcam_await_ready2(q, 1)) return bytes; + lo1 = parport_read_data(q->pport) >> 1; + hi1 = ((parport_read_status(q->pport) >> 3) & 0x1f) ^ 0x10; + qcam_set_ack(q, 1); + if (qcam_await_ready2(q, 0)) return bytes; + lo2 = parport_read_data(q->pport) >> 1; + hi2 = ((parport_read_status(q->pport) >> 3) & 0x1f) ^ 0x10; + qcam_set_ack(q, 0); + r = (lo1 | ((hi1 & 1)<<7)); + g = ((hi1 & 0x1e)<<3) | ((hi2 & 0x1e)>>1); + b = (lo2 | ((hi2 & 1)<<7)); + if (force_rgb) { + buf[bytes++] = r; + buf[bytes++] = g; + buf[bytes++] = b; + } else { + buf[bytes++] = b; + buf[bytes++] = g; + buf[bytes++] = r; + } + } + } + else + { + /* It's a unidirectional port */ + int i = 0, n = bytes; + unsigned char rgb[3]; + + while (bytes < nbytes) + { + unsigned int hi, lo; + + if (qcam_await_ready1(q, 1)) return bytes; + hi = (parport_read_status(q->pport) & 0xf0); + qcam_set_ack(q, 1); + if (qcam_await_ready1(q, 0)) return bytes; + lo = (parport_read_status(q->pport) & 0xf0); + qcam_set_ack(q, 0); + /* flip some bits */ + rgb[(i = bytes++ % 3)] = (hi | (lo >> 4)) ^ 0x88; + if (i >= 2) { +get_fragment: + if (force_rgb) { + buf[n++] = rgb[0]; + buf[n++] = rgb[1]; + buf[n++] = rgb[2]; + } else { + buf[n++] = rgb[2]; + buf[n++] = rgb[1]; + buf[n++] = rgb[0]; + } + } + } + if (i) { + i = 0; + goto get_fragment; + } + } + return bytes; +} + +#define BUFSZ 150 + +static long qc_capture(struct qcam_device *q, char __user *buf, unsigned long len) +{ + unsigned lines, pixelsperline, bitsperxfer; + unsigned int is_bi_dir = q->bidirectional; + size_t wantlen, outptr = 0; + char tmpbuf[BUFSZ]; + + if (!access_ok(VERIFY_WRITE, buf, len)) + return -EFAULT; + + /* Wait for camera to become ready */ + for (;;) + { + int i = qcam_get(q, 41); + if (i == -1) { + qc_setup(q); + return -EIO; + } + if ((i & 0x80) == 0) + break; + else + schedule(); + } + + if (qcam_set(q, 7, (q->mode | (is_bi_dir?1:0)) + 1)) + return -EIO; + + lines = q->height; + pixelsperline = q->width; + bitsperxfer = (is_bi_dir) ? 24 : 8; + + if (is_bi_dir) + { + /* Turn the port around */ + parport_data_reverse(q->pport); + mdelay(3); + qcam_set_ack(q, 0); + if (qcam_await_ready1(q, 1)) { + qc_setup(q); + return -EIO; + } + qcam_set_ack(q, 1); + if (qcam_await_ready1(q, 0)) { + qc_setup(q); + return -EIO; + } + } + + wantlen = lines * pixelsperline * 24 / 8; + + while (wantlen) + { + size_t t, s; + s = (wantlen > BUFSZ)?BUFSZ:wantlen; + t = qcam_read_bytes(q, tmpbuf, s); + if (outptr < len) + { + size_t sz = len - outptr; + if (sz > t) sz = t; + if (__copy_to_user(buf+outptr, tmpbuf, sz)) + break; + outptr += sz; + } + wantlen -= t; + if (t < s) + break; + cond_resched(); + } + + len = outptr; + + if (wantlen) + { + printk("qcam: short read.\n"); + if (is_bi_dir) + parport_data_forward(q->pport); + qc_setup(q); + return len; + } + + if (is_bi_dir) + { + int l; + do { + l = qcam_read_bytes(q, tmpbuf, 3); + cond_resched(); + } while (l && (tmpbuf[0] == 0x7e || tmpbuf[1] == 0x7e || tmpbuf[2] == 0x7e)); + if (force_rgb) { + if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf) + printk("qcam: bad EOF\n"); + } else { + if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe) + printk("qcam: bad EOF\n"); + } + qcam_set_ack(q, 0); + if (qcam_await_ready1(q, 1)) + { + printk("qcam: no ack after EOF\n"); + parport_data_forward(q->pport); + qc_setup(q); + return len; + } + parport_data_forward(q->pport); + mdelay(3); + qcam_set_ack(q, 1); + if (qcam_await_ready1(q, 0)) + { + printk("qcam: no ack to port turnaround\n"); + qc_setup(q); + return len; + } + } + else + { + int l; + do { + l = qcam_read_bytes(q, tmpbuf, 1); + cond_resched(); + } while (l && tmpbuf[0] == 0x7e); + l = qcam_read_bytes(q, tmpbuf+1, 2); + if (force_rgb) { + if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf) + printk("qcam: bad EOF\n"); + } else { + if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe) + printk("qcam: bad EOF\n"); + } + } + + qcam_write_data(q, 0); + return len; +} + +/* + * Video4linux interfacing + */ + +static int qcam_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + strcpy(b->name, "Quickcam"); + b->type = VID_TYPE_CAPTURE|VID_TYPE_SCALES; + b->channels = 1; + b->audios = 0; + b->maxwidth = 320; + b->maxheight = 240; + b->minwidth = 80; + b->minheight = 60; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + if(v->channel!=0) + return -EINVAL; + v->flags=0; + v->tuners=0; + /* Good question.. its composite or SVHS so.. */ + v->type = VIDEO_TYPE_CAMERA; + strcpy(v->name, "Camera"); + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if(v->channel!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + memset(v,0,sizeof(*v)); + strcpy(v->name, "Format"); + v->mode = VIDEO_MODE_AUTO; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + if(v->mode!=VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + p->colour=0x8000; + p->hue=0x8000; + p->brightness=qcam->brightness<<8; + p->contrast=qcam->contrast<<8; + p->whiteness=qcam->whitebal<<8; + p->depth=24; + p->palette=VIDEO_PALETTE_RGB24; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + + /* + * Sanity check args + */ + if (p->depth != 24 || p->palette != VIDEO_PALETTE_RGB24) + return -EINVAL; + + /* + * Now load the camera. + */ + qcam->brightness = p->brightness>>8; + qcam->contrast = p->contrast>>8; + qcam->whitebal = p->whiteness>>8; + + down(&qcam->lock); + parport_claim_or_block(qcam->pdev); + qc_setup(qcam); + parport_release(qcam->pdev); + up(&qcam->lock); + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + + if(vw->flags) + return -EINVAL; + if(vw->clipcount) + return -EINVAL; + if(vw->height<60||vw->height>240) + return -EINVAL; + if(vw->width<80||vw->width>320) + return -EINVAL; + + qcam->width = 80; + qcam->height = 60; + qcam->mode = QC_DECIMATION_4; + + if(vw->width>=160 && vw->height>=120) + { + qcam->width = 160; + qcam->height = 120; + qcam->mode = QC_DECIMATION_2; + } + if(vw->width>=320 && vw->height>=240) + { + qcam->width = 320; + qcam->height = 240; + qcam->mode = QC_DECIMATION_1; + } + qcam->mode |= QC_MILLIONS; +#if 0 + if(vw->width>=640 && vw->height>=480) + { + qcam->width = 640; + qcam->height = 480; + qcam->mode = QC_BILLIONS | QC_DECIMATION_1; + } +#endif + /* Ok we figured out what to use from our + wide choice */ + down(&qcam->lock); + parport_claim_or_block(qcam->pdev); + qc_setup(qcam); + parport_release(qcam->pdev); + up(&qcam->lock); + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + memset(vw, 0, sizeof(*vw)); + vw->width=qcam->width; + vw->height=qcam->height; + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int qcam_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, qcam_do_ioctl); +} + +static ssize_t qcam_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)v; + int len; + + down(&qcam->lock); + parport_claim_or_block(qcam->pdev); + /* Probably should have a semaphore against multiple users */ + len = qc_capture(qcam, buf,count); + parport_release(qcam->pdev); + up(&qcam->lock); + return len; +} + +/* video device template */ +static struct file_operations qcam_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = qcam_ioctl, + .read = qcam_read, + .llseek = no_llseek, +}; + +static struct video_device qcam_template= +{ + .owner = THIS_MODULE, + .name = "Colour QuickCam", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_QCAM_C, + .fops = &qcam_fops, +}; + +/* Initialize the QuickCam driver control structure. */ + +static struct qcam_device *qcam_init(struct parport *port) +{ + struct qcam_device *q; + + q = kmalloc(sizeof(struct qcam_device), GFP_KERNEL); + if(q==NULL) + return NULL; + + q->pport = port; + q->pdev = parport_register_device(port, "c-qcam", NULL, NULL, + NULL, 0, NULL); + + q->bidirectional = (q->pport->modes & PARPORT_MODE_TRISTATE)?1:0; + + if (q->pdev == NULL) + { + printk(KERN_ERR "c-qcam: couldn't register for %s.\n", + port->name); + kfree(q); + return NULL; + } + + memcpy(&q->vdev, &qcam_template, sizeof(qcam_template)); + + init_MUTEX(&q->lock); + q->width = q->ccd_width = 320; + q->height = q->ccd_height = 240; + q->mode = QC_MILLIONS | QC_DECIMATION_1; + q->contrast = 192; + q->brightness = 240; + q->whitebal = 128; + q->top = 1; + q->left = 14; + return q; +} + +static struct qcam_device *qcams[MAX_CAMS]; +static unsigned int num_cams = 0; + +static int init_cqcam(struct parport *port) +{ + struct qcam_device *qcam; + + if (parport[0] != -1) + { + /* The user gave specific instructions */ + int i, found = 0; + for (i = 0; i < MAX_CAMS && parport[i] != -1; i++) + { + if (parport[0] == port->number) + found = 1; + } + if (!found) + return -ENODEV; + } + + if (num_cams == MAX_CAMS) + return -ENOSPC; + + qcam = qcam_init(port); + if (qcam==NULL) + return -ENODEV; + + parport_claim_or_block(qcam->pdev); + + qc_reset(qcam); + + if (probe && qc_detect(qcam)==0) + { + parport_release(qcam->pdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + qc_setup(qcam); + + parport_release(qcam->pdev); + + if (video_register_device(&qcam->vdev, VFL_TYPE_GRABBER, video_nr)==-1) + { + printk(KERN_ERR "Unable to register Colour QuickCam on %s\n", + qcam->pport->name); + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + printk(KERN_INFO "video%d: Colour QuickCam found on %s\n", + qcam->vdev.minor, qcam->pport->name); + + qcams[num_cams++] = qcam; + + return 0; +} + +static void close_cqcam(struct qcam_device *qcam) +{ + video_unregister_device(&qcam->vdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); +} + +static void cq_attach(struct parport *port) +{ + init_cqcam(port); +} + +static void cq_detach(struct parport *port) +{ + /* Write this some day. */ +} + +static struct parport_driver cqcam_driver = { + .name = "cqcam", + .attach = cq_attach, + .detach = cq_detach, +}; + +static int __init cqcam_init (void) +{ + printk(BANNER "\n"); + + return parport_register_driver(&cqcam_driver); +} + +static void __exit cqcam_cleanup (void) +{ + unsigned int i; + + for (i = 0; i < num_cams; i++) + close_cqcam(qcams[i]); + + parport_unregister_driver(&cqcam_driver); +} + +MODULE_AUTHOR("Philip Blundell <philb@gnu.org>"); +MODULE_DESCRIPTION(BANNER); +MODULE_LICENSE("GPL"); + +/* FIXME: parport=auto would never have worked, surely? --RR */ +MODULE_PARM_DESC(parport ,"parport=<auto|n[,n]...> for port detection method\n\ +probe=<0|1|2> for camera detection method\n\ +force_rgb=<0|1> for RGB data format (default BGR)"); +module_param_array(parport, int, NULL, 0); +module_param(probe, int, 0); +module_param(force_rgb, bool, 0); +module_param(video_nr, int, 0); + +module_init(cqcam_init); +module_exit(cqcam_cleanup); diff --git a/drivers/media/video/cpia.c b/drivers/media/video/cpia.c new file mode 100644 index 000000000000..8c08b7f1ad23 --- /dev/null +++ b/drivers/media/video/cpia.c @@ -0,0 +1,4073 @@ +/* + * cpia CPiA driver + * + * Supports CPiA based Video Camera's. + * + * (C) Copyright 1999-2000 Peter Pregler + * (C) Copyright 1999-2000 Scott J. Bertin + * (C) Copyright 1999-2000 Johannes Erdfelt <johannes@erdfelt.com> + * (C) Copyright 2000 STMicroelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* define _CPIA_DEBUG_ for verbose debug output (see cpia.h) */ +/* #define _CPIA_DEBUG_ 1 */ + +#include <linux/config.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/semaphore.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +#include "cpia.h" + +#ifdef CONFIG_VIDEO_CPIA_PP +extern int cpia_pp_init(void); +#endif +#ifdef CONFIG_VIDEO_CPIA_USB +extern int cpia_usb_init(void); +#endif + +static int video_nr = -1; + +#ifdef MODULE +module_param(video_nr, int, 0); +MODULE_AUTHOR("Scott J. Bertin <sbertin@securenym.net> & Peter Pregler <Peter_Pregler@email.com> & Johannes Erdfelt <johannes@erdfeld.com>"); +MODULE_DESCRIPTION("V4L-driver for Vision CPiA based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); +#endif + +static unsigned short colorspace_conv = 0; +module_param(colorspace_conv, ushort, 0444); +MODULE_PARM_DESC(colorspace_conv, + "\n<n> Colorspace conversion:" + "\n0 = disable" + "\n1 = enable" + "\nDefault value is 0" + "\n"); + +#define ABOUT "V4L-Driver for Vision CPiA based cameras" + +#ifndef VID_HARDWARE_CPIA +#define VID_HARDWARE_CPIA 24 /* FIXME -> from linux/videodev.h */ +#endif + +#define CPIA_MODULE_CPIA (0<<5) +#define CPIA_MODULE_SYSTEM (1<<5) +#define CPIA_MODULE_VP_CTRL (5<<5) +#define CPIA_MODULE_CAPTURE (6<<5) +#define CPIA_MODULE_DEBUG (7<<5) + +#define INPUT (DATA_IN << 8) +#define OUTPUT (DATA_OUT << 8) + +#define CPIA_COMMAND_GetCPIAVersion (INPUT | CPIA_MODULE_CPIA | 1) +#define CPIA_COMMAND_GetPnPID (INPUT | CPIA_MODULE_CPIA | 2) +#define CPIA_COMMAND_GetCameraStatus (INPUT | CPIA_MODULE_CPIA | 3) +#define CPIA_COMMAND_GotoHiPower (OUTPUT | CPIA_MODULE_CPIA | 4) +#define CPIA_COMMAND_GotoLoPower (OUTPUT | CPIA_MODULE_CPIA | 5) +#define CPIA_COMMAND_GotoSuspend (OUTPUT | CPIA_MODULE_CPIA | 7) +#define CPIA_COMMAND_GotoPassThrough (OUTPUT | CPIA_MODULE_CPIA | 8) +#define CPIA_COMMAND_ModifyCameraStatus (OUTPUT | CPIA_MODULE_CPIA | 10) + +#define CPIA_COMMAND_ReadVCRegs (INPUT | CPIA_MODULE_SYSTEM | 1) +#define CPIA_COMMAND_WriteVCReg (OUTPUT | CPIA_MODULE_SYSTEM | 2) +#define CPIA_COMMAND_ReadMCPorts (INPUT | CPIA_MODULE_SYSTEM | 3) +#define CPIA_COMMAND_WriteMCPort (OUTPUT | CPIA_MODULE_SYSTEM | 4) +#define CPIA_COMMAND_SetBaudRate (OUTPUT | CPIA_MODULE_SYSTEM | 5) +#define CPIA_COMMAND_SetECPTiming (OUTPUT | CPIA_MODULE_SYSTEM | 6) +#define CPIA_COMMAND_ReadIDATA (INPUT | CPIA_MODULE_SYSTEM | 7) +#define CPIA_COMMAND_WriteIDATA (OUTPUT | CPIA_MODULE_SYSTEM | 8) +#define CPIA_COMMAND_GenericCall (OUTPUT | CPIA_MODULE_SYSTEM | 9) +#define CPIA_COMMAND_I2CStart (OUTPUT | CPIA_MODULE_SYSTEM | 10) +#define CPIA_COMMAND_I2CStop (OUTPUT | CPIA_MODULE_SYSTEM | 11) +#define CPIA_COMMAND_I2CWrite (OUTPUT | CPIA_MODULE_SYSTEM | 12) +#define CPIA_COMMAND_I2CRead (INPUT | CPIA_MODULE_SYSTEM | 13) + +#define CPIA_COMMAND_GetVPVersion (INPUT | CPIA_MODULE_VP_CTRL | 1) +#define CPIA_COMMAND_ResetFrameCounter (INPUT | CPIA_MODULE_VP_CTRL | 2) +#define CPIA_COMMAND_SetColourParams (OUTPUT | CPIA_MODULE_VP_CTRL | 3) +#define CPIA_COMMAND_SetExposure (OUTPUT | CPIA_MODULE_VP_CTRL | 4) +#define CPIA_COMMAND_SetColourBalance (OUTPUT | CPIA_MODULE_VP_CTRL | 6) +#define CPIA_COMMAND_SetSensorFPS (OUTPUT | CPIA_MODULE_VP_CTRL | 7) +#define CPIA_COMMAND_SetVPDefaults (OUTPUT | CPIA_MODULE_VP_CTRL | 8) +#define CPIA_COMMAND_SetApcor (OUTPUT | CPIA_MODULE_VP_CTRL | 9) +#define CPIA_COMMAND_SetFlickerCtrl (OUTPUT | CPIA_MODULE_VP_CTRL | 10) +#define CPIA_COMMAND_SetVLOffset (OUTPUT | CPIA_MODULE_VP_CTRL | 11) +#define CPIA_COMMAND_GetColourParams (INPUT | CPIA_MODULE_VP_CTRL | 16) +#define CPIA_COMMAND_GetColourBalance (INPUT | CPIA_MODULE_VP_CTRL | 17) +#define CPIA_COMMAND_GetExposure (INPUT | CPIA_MODULE_VP_CTRL | 18) +#define CPIA_COMMAND_SetSensorMatrix (OUTPUT | CPIA_MODULE_VP_CTRL | 19) +#define CPIA_COMMAND_ColourBars (OUTPUT | CPIA_MODULE_VP_CTRL | 25) +#define CPIA_COMMAND_ReadVPRegs (INPUT | CPIA_MODULE_VP_CTRL | 30) +#define CPIA_COMMAND_WriteVPReg (OUTPUT | CPIA_MODULE_VP_CTRL | 31) + +#define CPIA_COMMAND_GrabFrame (OUTPUT | CPIA_MODULE_CAPTURE | 1) +#define CPIA_COMMAND_UploadFrame (OUTPUT | CPIA_MODULE_CAPTURE | 2) +#define CPIA_COMMAND_SetGrabMode (OUTPUT | CPIA_MODULE_CAPTURE | 3) +#define CPIA_COMMAND_InitStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 4) +#define CPIA_COMMAND_FiniStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 5) +#define CPIA_COMMAND_StartStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 6) +#define CPIA_COMMAND_EndStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 7) +#define CPIA_COMMAND_SetFormat (OUTPUT | CPIA_MODULE_CAPTURE | 8) +#define CPIA_COMMAND_SetROI (OUTPUT | CPIA_MODULE_CAPTURE | 9) +#define CPIA_COMMAND_SetCompression (OUTPUT | CPIA_MODULE_CAPTURE | 10) +#define CPIA_COMMAND_SetCompressionTarget (OUTPUT | CPIA_MODULE_CAPTURE | 11) +#define CPIA_COMMAND_SetYUVThresh (OUTPUT | CPIA_MODULE_CAPTURE | 12) +#define CPIA_COMMAND_SetCompressionParams (OUTPUT | CPIA_MODULE_CAPTURE | 13) +#define CPIA_COMMAND_DiscardFrame (OUTPUT | CPIA_MODULE_CAPTURE | 14) +#define CPIA_COMMAND_GrabReset (OUTPUT | CPIA_MODULE_CAPTURE | 15) + +#define CPIA_COMMAND_OutputRS232 (OUTPUT | CPIA_MODULE_DEBUG | 1) +#define CPIA_COMMAND_AbortProcess (OUTPUT | CPIA_MODULE_DEBUG | 4) +#define CPIA_COMMAND_SetDramPage (OUTPUT | CPIA_MODULE_DEBUG | 5) +#define CPIA_COMMAND_StartDramUpload (OUTPUT | CPIA_MODULE_DEBUG | 6) +#define CPIA_COMMAND_StartDummyDtream (OUTPUT | CPIA_MODULE_DEBUG | 8) +#define CPIA_COMMAND_AbortStream (OUTPUT | CPIA_MODULE_DEBUG | 9) +#define CPIA_COMMAND_DownloadDRAM (OUTPUT | CPIA_MODULE_DEBUG | 10) +#define CPIA_COMMAND_Null (OUTPUT | CPIA_MODULE_DEBUG | 11) + +enum { + FRAME_READY, /* Ready to grab into */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_UNUSED, /* Unused (no MCAPTURE) */ +}; + +#define COMMAND_NONE 0x0000 +#define COMMAND_SETCOMPRESSION 0x0001 +#define COMMAND_SETCOMPRESSIONTARGET 0x0002 +#define COMMAND_SETCOLOURPARAMS 0x0004 +#define COMMAND_SETFORMAT 0x0008 +#define COMMAND_PAUSE 0x0010 +#define COMMAND_RESUME 0x0020 +#define COMMAND_SETYUVTHRESH 0x0040 +#define COMMAND_SETECPTIMING 0x0080 +#define COMMAND_SETCOMPRESSIONPARAMS 0x0100 +#define COMMAND_SETEXPOSURE 0x0200 +#define COMMAND_SETCOLOURBALANCE 0x0400 +#define COMMAND_SETSENSORFPS 0x0800 +#define COMMAND_SETAPCOR 0x1000 +#define COMMAND_SETFLICKERCTRL 0x2000 +#define COMMAND_SETVLOFFSET 0x4000 +#define COMMAND_SETLIGHTS 0x8000 + +#define ROUND_UP_EXP_FOR_FLICKER 15 + +/* Constants for automatic frame rate adjustment */ +#define MAX_EXP 302 +#define MAX_EXP_102 255 +#define LOW_EXP 140 +#define VERY_LOW_EXP 70 +#define TC 94 +#define EXP_ACC_DARK 50 +#define EXP_ACC_LIGHT 90 +#define HIGH_COMP_102 160 +#define MAX_COMP 239 +#define DARK_TIME 3 +#define LIGHT_TIME 3 + +/* Maximum number of 10ms loops to wait for the stream to become ready */ +#define READY_TIMEOUT 100 + +/* Developer's Guide Table 5 p 3-34 + * indexed by [mains][sensorFps.baserate][sensorFps.divisor]*/ +static u8 flicker_jumps[2][2][4] = +{ { { 76, 38, 19, 9 }, { 92, 46, 23, 11 } }, + { { 64, 32, 16, 8 }, { 76, 38, 19, 9} } +}; + +/* forward declaration of local function */ +static void reset_camera_struct(struct cam_data *cam); +static int find_over_exposure(int brightness); +static void set_flicker(struct cam_params *params, volatile u32 *command_flags, + int on); + + +/********************************************************************** + * + * Memory management + * + **********************************************************************/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +/********************************************************************** + * + * /proc interface + * + **********************************************************************/ +#ifdef CONFIG_PROC_FS +static struct proc_dir_entry *cpia_proc_root=NULL; + +static int cpia_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out = page; + int len, tmp; + struct cam_data *cam = data; + char tmpstr[29]; + + /* IMPORTANT: This output MUST be kept under PAGE_SIZE + * or we need to get more sophisticated. */ + + out += sprintf(out, "read-only\n-----------------------\n"); + out += sprintf(out, "V4L Driver version: %d.%d.%d\n", + CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER); + out += sprintf(out, "CPIA Version: %d.%02d (%d.%d)\n", + cam->params.version.firmwareVersion, + cam->params.version.firmwareRevision, + cam->params.version.vcVersion, + cam->params.version.vcRevision); + out += sprintf(out, "CPIA PnP-ID: %04x:%04x:%04x\n", + cam->params.pnpID.vendor, cam->params.pnpID.product, + cam->params.pnpID.deviceRevision); + out += sprintf(out, "VP-Version: %d.%d %04x\n", + cam->params.vpVersion.vpVersion, + cam->params.vpVersion.vpRevision, + cam->params.vpVersion.cameraHeadID); + + out += sprintf(out, "system_state: %#04x\n", + cam->params.status.systemState); + out += sprintf(out, "grab_state: %#04x\n", + cam->params.status.grabState); + out += sprintf(out, "stream_state: %#04x\n", + cam->params.status.streamState); + out += sprintf(out, "fatal_error: %#04x\n", + cam->params.status.fatalError); + out += sprintf(out, "cmd_error: %#04x\n", + cam->params.status.cmdError); + out += sprintf(out, "debug_flags: %#04x\n", + cam->params.status.debugFlags); + out += sprintf(out, "vp_status: %#04x\n", + cam->params.status.vpStatus); + out += sprintf(out, "error_code: %#04x\n", + cam->params.status.errorCode); + /* QX3 specific entries */ + if (cam->params.qx3.qx3_detected) { + out += sprintf(out, "button: %4d\n", + cam->params.qx3.button); + out += sprintf(out, "cradled: %4d\n", + cam->params.qx3.cradled); + } + out += sprintf(out, "video_size: %s\n", + cam->params.format.videoSize == VIDEOSIZE_CIF ? + "CIF " : "QCIF"); + out += sprintf(out, "roi: (%3d, %3d) to (%3d, %3d)\n", + cam->params.roi.colStart*8, + cam->params.roi.rowStart*4, + cam->params.roi.colEnd*8, + cam->params.roi.rowEnd*4); + out += sprintf(out, "actual_fps: %3d\n", cam->fps); + out += sprintf(out, "transfer_rate: %4dkB/s\n", + cam->transfer_rate); + + out += sprintf(out, "\nread-write\n"); + out += sprintf(out, "----------------------- current min" + " max default comment\n"); + out += sprintf(out, "brightness: %8d %8d %8d %8d\n", + cam->params.colourParams.brightness, 0, 100, 50); + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits contrast to 80 */ + tmp = 80; + else + tmp = 96; + + out += sprintf(out, "contrast: %8d %8d %8d %8d" + " steps of 8\n", + cam->params.colourParams.contrast, 0, tmp, 48); + out += sprintf(out, "saturation: %8d %8d %8d %8d\n", + cam->params.colourParams.saturation, 0, 100, 50); + tmp = (25000+5000*cam->params.sensorFps.baserate)/ + (1<<cam->params.sensorFps.divisor); + out += sprintf(out, "sensor_fps: %4d.%03d %8d %8d %8d\n", + tmp/1000, tmp%1000, 3, 30, 15); + out += sprintf(out, "stream_start_line: %8d %8d %8d %8d\n", + 2*cam->params.streamStartLine, 0, + cam->params.format.videoSize == VIDEOSIZE_CIF ? 288:144, + cam->params.format.videoSize == VIDEOSIZE_CIF ? 240:120); + out += sprintf(out, "sub_sample: %8s %8s %8s %8s\n", + cam->params.format.subSample == SUBSAMPLE_420 ? + "420" : "422", "420", "422", "422"); + out += sprintf(out, "yuv_order: %8s %8s %8s %8s\n", + cam->params.format.yuvOrder == YUVORDER_YUYV ? + "YUYV" : "UYVY", "YUYV" , "UYVY", "YUYV"); + out += sprintf(out, "ecp_timing: %8s %8s %8s %8s\n", + cam->params.ecpTiming ? "slow" : "normal", "slow", + "normal", "normal"); + + if (cam->params.colourBalance.balanceMode == 2) { + sprintf(tmpstr, "auto"); + } else { + sprintf(tmpstr, "manual"); + } + out += sprintf(out, "color_balance_mode: %8s %8s %8s" + " %8s\n", tmpstr, "manual", "auto", "auto"); + out += sprintf(out, "red_gain: %8d %8d %8d %8d\n", + cam->params.colourBalance.redGain, 0, 212, 32); + out += sprintf(out, "green_gain: %8d %8d %8d %8d\n", + cam->params.colourBalance.greenGain, 0, 212, 6); + out += sprintf(out, "blue_gain: %8d %8d %8d %8d\n", + cam->params.colourBalance.blueGain, 0, 212, 92); + + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits gain to 2 */ + sprintf(tmpstr, "%8d %8d %8d", 1, 2, 2); + else + sprintf(tmpstr, "%8d %8d %8d", 1, 8, 2); + + if (cam->params.exposure.gainMode == 0) + out += sprintf(out, "max_gain: unknown %28s" + " powers of 2\n", tmpstr); + else + out += sprintf(out, "max_gain: %8d %28s" + " 1,2,4 or 8 \n", + 1<<(cam->params.exposure.gainMode-1), tmpstr); + + switch(cam->params.exposure.expMode) { + case 1: + case 3: + sprintf(tmpstr, "manual"); + break; + case 2: + sprintf(tmpstr, "auto"); + break; + default: + sprintf(tmpstr, "unknown"); + break; + } + out += sprintf(out, "exposure_mode: %8s %8s %8s" + " %8s\n", tmpstr, "manual", "auto", "auto"); + out += sprintf(out, "centre_weight: %8s %8s %8s %8s\n", + (2-cam->params.exposure.centreWeight) ? "on" : "off", + "off", "on", "on"); + out += sprintf(out, "gain: %8d %8d max_gain %8d 1,2,4,8 possible\n", + 1<<cam->params.exposure.gain, 1, 1); + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits fineExp/2 to 127 */ + tmp = 254; + else + tmp = 510; + + out += sprintf(out, "fine_exp: %8d %8d %8d %8d\n", + cam->params.exposure.fineExp*2, 0, tmp, 0); + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits coarseExpHi to 0 */ + tmp = MAX_EXP_102; + else + tmp = MAX_EXP; + + out += sprintf(out, "coarse_exp: %8d %8d %8d" + " %8d\n", cam->params.exposure.coarseExpLo+ + 256*cam->params.exposure.coarseExpHi, 0, tmp, 185); + out += sprintf(out, "red_comp: %8d %8d %8d %8d\n", + cam->params.exposure.redComp, COMP_RED, 255, COMP_RED); + out += sprintf(out, "green1_comp: %8d %8d %8d %8d\n", + cam->params.exposure.green1Comp, COMP_GREEN1, 255, + COMP_GREEN1); + out += sprintf(out, "green2_comp: %8d %8d %8d %8d\n", + cam->params.exposure.green2Comp, COMP_GREEN2, 255, + COMP_GREEN2); + out += sprintf(out, "blue_comp: %8d %8d %8d %8d\n", + cam->params.exposure.blueComp, COMP_BLUE, 255, COMP_BLUE); + + out += sprintf(out, "apcor_gain1: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain1, 0, 0xff, 0x1c); + out += sprintf(out, "apcor_gain2: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain2, 0, 0xff, 0x1a); + out += sprintf(out, "apcor_gain4: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain4, 0, 0xff, 0x2d); + out += sprintf(out, "apcor_gain8: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain8, 0, 0xff, 0x2a); + out += sprintf(out, "vl_offset_gain1: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain1, 0, 255, 24); + out += sprintf(out, "vl_offset_gain2: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain2, 0, 255, 28); + out += sprintf(out, "vl_offset_gain4: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain4, 0, 255, 30); + out += sprintf(out, "vl_offset_gain8: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain8, 0, 255, 30); + out += sprintf(out, "flicker_control: %8s %8s %8s %8s\n", + cam->params.flickerControl.flickerMode ? "on" : "off", + "off", "on", "off"); + out += sprintf(out, "mains_frequency: %8d %8d %8d %8d" + " only 50/60\n", + cam->mainsFreq ? 60 : 50, 50, 60, 50); + if(cam->params.flickerControl.allowableOverExposure < 0) + out += sprintf(out, "allowable_overexposure: %4dauto auto %8d auto\n", + -cam->params.flickerControl.allowableOverExposure, + 255); + else + out += sprintf(out, "allowable_overexposure: %8d auto %8d auto\n", + cam->params.flickerControl.allowableOverExposure, + 255); + out += sprintf(out, "compression_mode: "); + switch(cam->params.compression.mode) { + case CPIA_COMPRESSION_NONE: + out += sprintf(out, "%8s", "none"); + break; + case CPIA_COMPRESSION_AUTO: + out += sprintf(out, "%8s", "auto"); + break; + case CPIA_COMPRESSION_MANUAL: + out += sprintf(out, "%8s", "manual"); + break; + default: + out += sprintf(out, "%8s", "unknown"); + break; + } + out += sprintf(out, " none,auto,manual auto\n"); + out += sprintf(out, "decimation_enable: %8s %8s %8s %8s\n", + cam->params.compression.decimation == + DECIMATION_ENAB ? "on":"off", "off", "on", + "off"); + out += sprintf(out, "compression_target: %9s %9s %9s %9s\n", + cam->params.compressionTarget.frTargeting == + CPIA_COMPRESSION_TARGET_FRAMERATE ? + "framerate":"quality", + "framerate", "quality", "quality"); + out += sprintf(out, "target_framerate: %8d %8d %8d %8d\n", + cam->params.compressionTarget.targetFR, 1, 30, 15); + out += sprintf(out, "target_quality: %8d %8d %8d %8d\n", + cam->params.compressionTarget.targetQ, 1, 64, 5); + out += sprintf(out, "y_threshold: %8d %8d %8d %8d\n", + cam->params.yuvThreshold.yThreshold, 0, 31, 6); + out += sprintf(out, "uv_threshold: %8d %8d %8d %8d\n", + cam->params.yuvThreshold.uvThreshold, 0, 31, 6); + out += sprintf(out, "hysteresis: %8d %8d %8d %8d\n", + cam->params.compressionParams.hysteresis, 0, 255, 3); + out += sprintf(out, "threshold_max: %8d %8d %8d %8d\n", + cam->params.compressionParams.threshMax, 0, 255, 11); + out += sprintf(out, "small_step: %8d %8d %8d %8d\n", + cam->params.compressionParams.smallStep, 0, 255, 1); + out += sprintf(out, "large_step: %8d %8d %8d %8d\n", + cam->params.compressionParams.largeStep, 0, 255, 3); + out += sprintf(out, "decimation_hysteresis: %8d %8d %8d %8d\n", + cam->params.compressionParams.decimationHysteresis, + 0, 255, 2); + out += sprintf(out, "fr_diff_step_thresh: %8d %8d %8d %8d\n", + cam->params.compressionParams.frDiffStepThresh, + 0, 255, 5); + out += sprintf(out, "q_diff_step_thresh: %8d %8d %8d %8d\n", + cam->params.compressionParams.qDiffStepThresh, + 0, 255, 3); + out += sprintf(out, "decimation_thresh_mod: %8d %8d %8d %8d\n", + cam->params.compressionParams.decimationThreshMod, + 0, 255, 2); + /* QX3 specific entries */ + if (cam->params.qx3.qx3_detected) { + out += sprintf(out, "toplight: %8s %8s %8s %8s\n", + cam->params.qx3.toplight ? "on" : "off", + "off", "on", "off"); + out += sprintf(out, "bottomlight: %8s %8s %8s %8s\n", + cam->params.qx3.bottomlight ? "on" : "off", + "off", "on", "off"); + } + + len = out - page; + len -= off; + if (len < count) { + *eof = 1; + if (len <= 0) return 0; + } else + len = count; + + *start = page + off; + return len; +} + + +static int match(char *checkstr, char **buffer, unsigned long *count, + int *find_colon, int *err) +{ + int ret, colon_found = 1; + int len = strlen(checkstr); + ret = (len <= *count && strncmp(*buffer, checkstr, len) == 0); + if (ret) { + *buffer += len; + *count -= len; + if (*find_colon) { + colon_found = 0; + while (*count && (**buffer == ' ' || **buffer == '\t' || + (!colon_found && **buffer == ':'))) { + if (**buffer == ':') + colon_found = 1; + --*count; + ++*buffer; + } + if (!*count || !colon_found) + *err = -EINVAL; + *find_colon = 0; + } + } + return ret; +} + +static unsigned long int value(char **buffer, unsigned long *count, int *err) +{ + char *p; + unsigned long int ret; + ret = simple_strtoul(*buffer, &p, 0); + if (p == *buffer) + *err = -EINVAL; + else { + *count -= p - *buffer; + *buffer = p; + } + return ret; +} + +static int cpia_write_proc(struct file *file, const char __user *buf, + unsigned long count, void *data) +{ + struct cam_data *cam = data; + struct cam_params new_params; + char *page, *buffer; + int retval, find_colon; + int size = count; + unsigned long val = 0; + u32 command_flags = 0; + u8 new_mains; + + /* + * This code to copy from buf to page is shamelessly copied + * from the comx driver + */ + if (count > PAGE_SIZE) { + printk(KERN_ERR "count is %lu > %d!!!\n", count, (int)PAGE_SIZE); + return -ENOSPC; + } + + if (!(page = (char *)__get_free_page(GFP_KERNEL))) return -ENOMEM; + + if(copy_from_user(page, buf, count)) + { + retval = -EFAULT; + goto out; + } + + if (page[count-1] == '\n') + page[count-1] = '\0'; + else if (count < PAGE_SIZE) + page[count] = '\0'; + else if (page[count]) { + retval = -EINVAL; + goto out; + } + + buffer = page; + + if (down_interruptible(&cam->param_lock)) + return -ERESTARTSYS; + + /* + * Skip over leading whitespace + */ + while (count && isspace(*buffer)) { + --count; + ++buffer; + } + + memcpy(&new_params, &cam->params, sizeof(struct cam_params)); + new_mains = cam->mainsFreq; + +#define MATCH(x) (match(x, &buffer, &count, &find_colon, &retval)) +#define VALUE (value(&buffer,&count, &retval)) +#define FIRMWARE_VERSION(x,y) (new_params.version.firmwareVersion == (x) && \ + new_params.version.firmwareRevision == (y)) + + retval = 0; + while (count && !retval) { + find_colon = 1; + if (MATCH("brightness")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 100) + new_params.colourParams.brightness = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURPARAMS; + if(new_params.flickerControl.allowableOverExposure < 0) + new_params.flickerControl.allowableOverExposure = + -find_over_exposure(new_params.colourParams.brightness); + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + + } else if (MATCH("contrast")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 100) { + /* contrast is in steps of 8, so round*/ + val = ((val + 3) / 8) * 8; + /* 1-02 firmware limits contrast to 80*/ + if (FIRMWARE_VERSION(1,2) && val > 80) + val = 80; + + new_params.colourParams.contrast = val; + } else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURPARAMS; + } else if (MATCH("saturation")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 100) + new_params.colourParams.saturation = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURPARAMS; + } else if (MATCH("sensor_fps")) { + if (!retval) + val = VALUE; + + if (!retval) { + /* find values so that sensorFPS is minimized, + * but >= val */ + if (val > 30) + retval = -EINVAL; + else if (val > 25) { + new_params.sensorFps.divisor = 0; + new_params.sensorFps.baserate = 1; + } else if (val > 15) { + new_params.sensorFps.divisor = 0; + new_params.sensorFps.baserate = 0; + } else if (val > 12) { + new_params.sensorFps.divisor = 1; + new_params.sensorFps.baserate = 1; + } else if (val > 7) { + new_params.sensorFps.divisor = 1; + new_params.sensorFps.baserate = 0; + } else if (val > 6) { + new_params.sensorFps.divisor = 2; + new_params.sensorFps.baserate = 1; + } else if (val > 3) { + new_params.sensorFps.divisor = 2; + new_params.sensorFps.baserate = 0; + } else { + new_params.sensorFps.divisor = 3; + /* Either base rate would work here */ + new_params.sensorFps.baserate = 1; + } + new_params.flickerControl.coarseJump = + flicker_jumps[new_mains] + [new_params.sensorFps.baserate] + [new_params.sensorFps.divisor]; + if (new_params.flickerControl.flickerMode) + command_flags |= COMMAND_SETFLICKERCTRL; + } + command_flags |= COMMAND_SETSENSORFPS; + cam->exposure_status = EXPOSURE_NORMAL; + } else if (MATCH("stream_start_line")) { + if (!retval) + val = VALUE; + + if (!retval) { + int max_line = 288; + + if (new_params.format.videoSize == VIDEOSIZE_QCIF) + max_line = 144; + if (val <= max_line) + new_params.streamStartLine = val/2; + else + retval = -EINVAL; + } + } else if (MATCH("sub_sample")) { + if (!retval && MATCH("420")) + new_params.format.subSample = SUBSAMPLE_420; + else if (!retval && MATCH("422")) + new_params.format.subSample = SUBSAMPLE_422; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETFORMAT; + } else if (MATCH("yuv_order")) { + if (!retval && MATCH("YUYV")) + new_params.format.yuvOrder = YUVORDER_YUYV; + else if (!retval && MATCH("UYVY")) + new_params.format.yuvOrder = YUVORDER_UYVY; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETFORMAT; + } else if (MATCH("ecp_timing")) { + if (!retval && MATCH("normal")) + new_params.ecpTiming = 0; + else if (!retval && MATCH("slow")) + new_params.ecpTiming = 1; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETECPTIMING; + } else if (MATCH("color_balance_mode")) { + if (!retval && MATCH("manual")) + new_params.colourBalance.balanceMode = 3; + else if (!retval && MATCH("auto")) + new_params.colourBalance.balanceMode = 2; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("red_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 212) { + new_params.colourBalance.redGain = val; + new_params.colourBalance.balanceMode = 1; + } else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("green_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 212) { + new_params.colourBalance.greenGain = val; + new_params.colourBalance.balanceMode = 1; + } else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("blue_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 212) { + new_params.colourBalance.blueGain = val; + new_params.colourBalance.balanceMode = 1; + } else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("max_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + /* 1-02 firmware limits gain to 2 */ + if (FIRMWARE_VERSION(1,2) && val > 2) + val = 2; + switch(val) { + case 1: + new_params.exposure.gainMode = 1; + break; + case 2: + new_params.exposure.gainMode = 2; + break; + case 4: + new_params.exposure.gainMode = 3; + break; + case 8: + new_params.exposure.gainMode = 4; + break; + default: + retval = -EINVAL; + break; + } + } + command_flags |= COMMAND_SETEXPOSURE; + } else if (MATCH("exposure_mode")) { + if (!retval && MATCH("auto")) + new_params.exposure.expMode = 2; + else if (!retval && MATCH("manual")) { + if (new_params.exposure.expMode == 2) + new_params.exposure.expMode = 3; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + new_params.flickerControl.flickerMode = 0; + } else + retval = -EINVAL; + + command_flags |= COMMAND_SETEXPOSURE; + } else if (MATCH("centre_weight")) { + if (!retval && MATCH("on")) + new_params.exposure.centreWeight = 1; + else if (!retval && MATCH("off")) + new_params.exposure.centreWeight = 2; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETEXPOSURE; + } else if (MATCH("gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + switch(val) { + case 1: + new_params.exposure.gain = 0; + break; + case 2: + new_params.exposure.gain = 1; + break; + case 4: + new_params.exposure.gain = 2; + break; + case 8: + new_params.exposure.gain = 3; + break; + default: + retval = -EINVAL; + break; + } + new_params.exposure.expMode = 1; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETEXPOSURE; + if (new_params.exposure.gain > + new_params.exposure.gainMode-1) + retval = -EINVAL; + } + } else if (MATCH("fine_exp")) { + if (!retval) + val = VALUE/2; + + if (!retval) { + if (val < 256) { + /* 1-02 firmware limits fineExp/2 to 127*/ + if (FIRMWARE_VERSION(1,2) && val > 127) + val = 127; + new_params.exposure.fineExp = val; + new_params.exposure.expMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } + } else if (MATCH("coarse_exp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= MAX_EXP) { + if (FIRMWARE_VERSION(1,2) && + val > MAX_EXP_102) + val = MAX_EXP_102; + new_params.exposure.coarseExpLo = + val & 0xff; + new_params.exposure.coarseExpHi = + val >> 8; + new_params.exposure.expMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } + } else if (MATCH("red_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_RED && val <= 255) { + new_params.exposure.redComp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("green1_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_GREEN1 && val <= 255) { + new_params.exposure.green1Comp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("green2_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_GREEN2 && val <= 255) { + new_params.exposure.green2Comp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("blue_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_BLUE && val <= 255) { + new_params.exposure.blueComp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain1")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain1 = val; + else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain2")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain2 = val; + else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain4")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain4 = val; + else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain8")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain8 = val; + else + retval = -EINVAL; + } + } else if (MATCH("vl_offset_gain1")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain1 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("vl_offset_gain2")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain2 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("vl_offset_gain4")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain4 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("vl_offset_gain8")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain8 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("flicker_control")) { + if (!retval && MATCH("on")) { + set_flicker(&new_params, &command_flags, 1); + } else if (!retval && MATCH("off")) { + set_flicker(&new_params, &command_flags, 0); + } else + retval = -EINVAL; + + command_flags |= COMMAND_SETFLICKERCTRL; + } else if (MATCH("mains_frequency")) { + if (!retval && MATCH("50")) { + new_mains = 0; + new_params.flickerControl.coarseJump = + flicker_jumps[new_mains] + [new_params.sensorFps.baserate] + [new_params.sensorFps.divisor]; + if (new_params.flickerControl.flickerMode) + command_flags |= COMMAND_SETFLICKERCTRL; + } else if (!retval && MATCH("60")) { + new_mains = 1; + new_params.flickerControl.coarseJump = + flicker_jumps[new_mains] + [new_params.sensorFps.baserate] + [new_params.sensorFps.divisor]; + if (new_params.flickerControl.flickerMode) + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } else if (MATCH("allowable_overexposure")) { + if (!retval && MATCH("auto")) { + new_params.flickerControl.allowableOverExposure = + -find_over_exposure(new_params.colourParams.brightness); + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + } else { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) { + new_params.flickerControl. + allowableOverExposure = val; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } + } + } else if (MATCH("compression_mode")) { + if (!retval && MATCH("none")) + new_params.compression.mode = + CPIA_COMPRESSION_NONE; + else if (!retval && MATCH("auto")) + new_params.compression.mode = + CPIA_COMPRESSION_AUTO; + else if (!retval && MATCH("manual")) + new_params.compression.mode = + CPIA_COMPRESSION_MANUAL; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOMPRESSION; + } else if (MATCH("decimation_enable")) { + if (!retval && MATCH("off")) + new_params.compression.decimation = 0; + else if (!retval && MATCH("on")) + new_params.compression.decimation = 1; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOMPRESSION; + } else if (MATCH("compression_target")) { + if (!retval && MATCH("quality")) + new_params.compressionTarget.frTargeting = + CPIA_COMPRESSION_TARGET_QUALITY; + else if (!retval && MATCH("framerate")) + new_params.compressionTarget.frTargeting = + CPIA_COMPRESSION_TARGET_FRAMERATE; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("target_framerate")) { + if (!retval) + val = VALUE; + + if (!retval) { + if(val > 0 && val <= 30) + new_params.compressionTarget.targetFR = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("target_quality")) { + if (!retval) + val = VALUE; + + if (!retval) { + if(val > 0 && val <= 64) + new_params.compressionTarget.targetQ = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("y_threshold")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val < 32) + new_params.yuvThreshold.yThreshold = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETYUVTHRESH; + } else if (MATCH("uv_threshold")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val < 32) + new_params.yuvThreshold.uvThreshold = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETYUVTHRESH; + } else if (MATCH("hysteresis")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.hysteresis = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("threshold_max")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.threshMax = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("small_step")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.smallStep = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("large_step")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.largeStep = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("decimation_hysteresis")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.decimationHysteresis = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("fr_diff_step_thresh")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.frDiffStepThresh = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("q_diff_step_thresh")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.qDiffStepThresh = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("decimation_thresh_mod")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.decimationThreshMod = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("toplight")) { + if (!retval && MATCH("on")) + new_params.qx3.toplight = 1; + else if (!retval && MATCH("off")) + new_params.qx3.toplight = 0; + else + retval = -EINVAL; + command_flags |= COMMAND_SETLIGHTS; + } else if (MATCH("bottomlight")) { + if (!retval && MATCH("on")) + new_params.qx3.bottomlight = 1; + else if (!retval && MATCH("off")) + new_params.qx3.bottomlight = 0; + else + retval = -EINVAL; + command_flags |= COMMAND_SETLIGHTS; + } else { + DBG("No match found\n"); + retval = -EINVAL; + } + + if (!retval) { + while (count && isspace(*buffer) && *buffer != '\n') { + --count; + ++buffer; + } + if (count) { + if (*buffer == '\0' && count != 1) + retval = -EINVAL; + else if (*buffer != '\n' && *buffer != ';' && + *buffer != '\0') + retval = -EINVAL; + else { + --count; + ++buffer; + } + } + } + } +#undef MATCH +#undef VALUE +#undef FIRMWARE_VERSION + if (!retval) { + if (command_flags & COMMAND_SETCOLOURPARAMS) { + /* Adjust cam->vp to reflect these changes */ + cam->vp.brightness = + new_params.colourParams.brightness*65535/100; + cam->vp.contrast = + new_params.colourParams.contrast*65535/100; + cam->vp.colour = + new_params.colourParams.saturation*65535/100; + } + if((command_flags & COMMAND_SETEXPOSURE) && + new_params.exposure.expMode == 2) + cam->exposure_status = EXPOSURE_NORMAL; + + memcpy(&cam->params, &new_params, sizeof(struct cam_params)); + cam->mainsFreq = new_mains; + cam->cmd_queue |= command_flags; + retval = size; + } else + DBG("error: %d\n", retval); + + up(&cam->param_lock); + +out: + free_page((unsigned long)page); + return retval; +} + +static void create_proc_cpia_cam(struct cam_data *cam) +{ + char name[7]; + struct proc_dir_entry *ent; + + if (!cpia_proc_root || !cam) + return; + + sprintf(name, "video%d", cam->vdev.minor); + + ent = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, cpia_proc_root); + if (!ent) + return; + + ent->data = cam; + ent->read_proc = cpia_read_proc; + ent->write_proc = cpia_write_proc; + /* + size of the proc entry is 3736 bytes for the standard webcam; + the extra features of the QX3 microscope add 189 bytes. + (we have not yet probed the camera to see which type it is). + */ + ent->size = 3736 + 189; + cam->proc_entry = ent; +} + +static void destroy_proc_cpia_cam(struct cam_data *cam) +{ + char name[7]; + + if (!cam || !cam->proc_entry) + return; + + sprintf(name, "video%d", cam->vdev.minor); + remove_proc_entry(name, cpia_proc_root); + cam->proc_entry = NULL; +} + +static void proc_cpia_create(void) +{ + cpia_proc_root = create_proc_entry("cpia", S_IFDIR, NULL); + + if (cpia_proc_root) + cpia_proc_root->owner = THIS_MODULE; + else + LOG("Unable to initialise /proc/cpia\n"); +} + +static void __exit proc_cpia_destroy(void) +{ + remove_proc_entry("cpia", NULL); +} +#endif /* CONFIG_PROC_FS */ + +/* ----------------------- debug functions ---------------------- */ + +#define printstatus(cam) \ + DBG("%02x %02x %02x %02x %02x %02x %02x %02x\n",\ + cam->params.status.systemState, cam->params.status.grabState, \ + cam->params.status.streamState, cam->params.status.fatalError, \ + cam->params.status.cmdError, cam->params.status.debugFlags, \ + cam->params.status.vpStatus, cam->params.status.errorCode); + +/* ----------------------- v4l helpers -------------------------- */ + +/* supported frame palettes and depths */ +static inline int valid_mode(u16 palette, u16 depth) +{ + if ((palette == VIDEO_PALETTE_YUV422 && depth == 16) || + (palette == VIDEO_PALETTE_YUYV && depth == 16)) + return 1; + + if (colorspace_conv) + return (palette == VIDEO_PALETTE_GREY && depth == 8) || + (palette == VIDEO_PALETTE_RGB555 && depth == 16) || + (palette == VIDEO_PALETTE_RGB565 && depth == 16) || + (palette == VIDEO_PALETTE_RGB24 && depth == 24) || + (palette == VIDEO_PALETTE_RGB32 && depth == 32) || + (palette == VIDEO_PALETTE_UYVY && depth == 16); + + return 0; +} + +static int match_videosize( int width, int height ) +{ + /* return the best match, where 'best' is as always + * the largest that is not bigger than what is requested. */ + if (width>=352 && height>=288) + return VIDEOSIZE_352_288; /* CIF */ + + if (width>=320 && height>=240) + return VIDEOSIZE_320_240; /* SIF */ + + if (width>=288 && height>=216) + return VIDEOSIZE_288_216; + + if (width>=256 && height>=192) + return VIDEOSIZE_256_192; + + if (width>=224 && height>=168) + return VIDEOSIZE_224_168; + + if (width>=192 && height>=144) + return VIDEOSIZE_192_144; + + if (width>=176 && height>=144) + return VIDEOSIZE_176_144; /* QCIF */ + + if (width>=160 && height>=120) + return VIDEOSIZE_160_120; /* QSIF */ + + if (width>=128 && height>=96) + return VIDEOSIZE_128_96; + + if (width>=88 && height>=72) + return VIDEOSIZE_88_72; + + if (width>=64 && height>=48) + return VIDEOSIZE_64_48; + + if (width>=48 && height>=48) + return VIDEOSIZE_48_48; + + return -1; +} + +/* these are the capture sizes we support */ +static void set_vw_size(struct cam_data *cam) +{ + /* the col/row/start/end values are the result of simple math */ + /* study the SetROI-command in cpia developers guide p 2-22 */ + /* streamStartLine is set to the recommended value in the cpia */ + /* developers guide p 3-37 */ + switch(cam->video_size) { + case VIDEOSIZE_CIF: + cam->vw.width = 352; + cam->vw.height = 288; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=0; + cam->params.roi.rowStart=0; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_SIF: + cam->vw.width = 320; + cam->vw.height = 240; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=2; + cam->params.roi.rowStart=6; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_288_216: + cam->vw.width = 288; + cam->vw.height = 216; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=4; + cam->params.roi.rowStart=9; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_256_192: + cam->vw.width = 256; + cam->vw.height = 192; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=6; + cam->params.roi.rowStart=12; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_224_168: + cam->vw.width = 224; + cam->vw.height = 168; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=8; + cam->params.roi.rowStart=15; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_192_144: + cam->vw.width = 192; + cam->vw.height = 144; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=10; + cam->params.roi.rowStart=18; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_QCIF: + cam->vw.width = 176; + cam->vw.height = 144; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=0; + cam->params.roi.rowStart=0; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_QSIF: + cam->vw.width = 160; + cam->vw.height = 120; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=1; + cam->params.roi.rowStart=3; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_128_96: + cam->vw.width = 128; + cam->vw.height = 96; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=3; + cam->params.roi.rowStart=6; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_88_72: + cam->vw.width = 88; + cam->vw.height = 72; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=5; + cam->params.roi.rowStart=9; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_64_48: + cam->vw.width = 64; + cam->vw.height = 48; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=7; + cam->params.roi.rowStart=12; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_48_48: + cam->vw.width = 48; + cam->vw.height = 48; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=8; + cam->params.roi.rowStart=6; + cam->params.streamStartLine = 60; + break; + default: + LOG("bad videosize value: %d\n", cam->video_size); + return; + } + + if(cam->vc.width == 0) + cam->vc.width = cam->vw.width; + if(cam->vc.height == 0) + cam->vc.height = cam->vw.height; + + cam->params.roi.colStart += cam->vc.x >> 3; + cam->params.roi.colEnd = cam->params.roi.colStart + + (cam->vc.width >> 3); + cam->params.roi.rowStart += cam->vc.y >> 2; + cam->params.roi.rowEnd = cam->params.roi.rowStart + + (cam->vc.height >> 2); + + return; +} + +static int allocate_frame_buf(struct cam_data *cam) +{ + int i; + + cam->frame_buf = rvmalloc(FRAME_NUM * CPIA_MAX_FRAME_SIZE); + if (!cam->frame_buf) + return -ENOBUFS; + + for (i = 0; i < FRAME_NUM; i++) + cam->frame[i].data = cam->frame_buf + i * CPIA_MAX_FRAME_SIZE; + + return 0; +} + +static int free_frame_buf(struct cam_data *cam) +{ + int i; + + rvfree(cam->frame_buf, FRAME_NUM*CPIA_MAX_FRAME_SIZE); + cam->frame_buf = NULL; + for (i=0; i < FRAME_NUM; i++) + cam->frame[i].data = NULL; + + return 0; +} + + +static inline void free_frames(struct cpia_frame frame[FRAME_NUM]) +{ + int i; + + for (i=0; i < FRAME_NUM; i++) + frame[i].state = FRAME_UNUSED; + return; +} + +/********************************************************************** + * + * General functions + * + **********************************************************************/ +/* send an arbitrary command to the camera */ +static int do_command(struct cam_data *cam, u16 command, u8 a, u8 b, u8 c, u8 d) +{ + int retval, datasize; + u8 cmd[8], data[8]; + + switch(command) { + case CPIA_COMMAND_GetCPIAVersion: + case CPIA_COMMAND_GetPnPID: + case CPIA_COMMAND_GetCameraStatus: + case CPIA_COMMAND_GetVPVersion: + datasize=8; + break; + case CPIA_COMMAND_GetColourParams: + case CPIA_COMMAND_GetColourBalance: + case CPIA_COMMAND_GetExposure: + down(&cam->param_lock); + datasize=8; + break; + case CPIA_COMMAND_ReadMCPorts: + case CPIA_COMMAND_ReadVCRegs: + datasize = 4; + break; + default: + datasize=0; + break; + } + + cmd[0] = command>>8; + cmd[1] = command&0xff; + cmd[2] = a; + cmd[3] = b; + cmd[4] = c; + cmd[5] = d; + cmd[6] = datasize; + cmd[7] = 0; + + retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data); + if (retval) { + DBG("%x - failed, retval=%d\n", command, retval); + if (command == CPIA_COMMAND_GetColourParams || + command == CPIA_COMMAND_GetColourBalance || + command == CPIA_COMMAND_GetExposure) + up(&cam->param_lock); + } else { + switch(command) { + case CPIA_COMMAND_GetCPIAVersion: + cam->params.version.firmwareVersion = data[0]; + cam->params.version.firmwareRevision = data[1]; + cam->params.version.vcVersion = data[2]; + cam->params.version.vcRevision = data[3]; + break; + case CPIA_COMMAND_GetPnPID: + cam->params.pnpID.vendor = data[0]+(((u16)data[1])<<8); + cam->params.pnpID.product = data[2]+(((u16)data[3])<<8); + cam->params.pnpID.deviceRevision = + data[4]+(((u16)data[5])<<8); + break; + case CPIA_COMMAND_GetCameraStatus: + cam->params.status.systemState = data[0]; + cam->params.status.grabState = data[1]; + cam->params.status.streamState = data[2]; + cam->params.status.fatalError = data[3]; + cam->params.status.cmdError = data[4]; + cam->params.status.debugFlags = data[5]; + cam->params.status.vpStatus = data[6]; + cam->params.status.errorCode = data[7]; + break; + case CPIA_COMMAND_GetVPVersion: + cam->params.vpVersion.vpVersion = data[0]; + cam->params.vpVersion.vpRevision = data[1]; + cam->params.vpVersion.cameraHeadID = + data[2]+(((u16)data[3])<<8); + break; + case CPIA_COMMAND_GetColourParams: + cam->params.colourParams.brightness = data[0]; + cam->params.colourParams.contrast = data[1]; + cam->params.colourParams.saturation = data[2]; + up(&cam->param_lock); + break; + case CPIA_COMMAND_GetColourBalance: + cam->params.colourBalance.redGain = data[0]; + cam->params.colourBalance.greenGain = data[1]; + cam->params.colourBalance.blueGain = data[2]; + up(&cam->param_lock); + break; + case CPIA_COMMAND_GetExposure: + cam->params.exposure.gain = data[0]; + cam->params.exposure.fineExp = data[1]; + cam->params.exposure.coarseExpLo = data[2]; + cam->params.exposure.coarseExpHi = data[3]; + cam->params.exposure.redComp = data[4]; + cam->params.exposure.green1Comp = data[5]; + cam->params.exposure.green2Comp = data[6]; + cam->params.exposure.blueComp = data[7]; + up(&cam->param_lock); + break; + + case CPIA_COMMAND_ReadMCPorts: + if (!cam->params.qx3.qx3_detected) + break; + /* test button press */ + cam->params.qx3.button = ((data[1] & 0x02) == 0); + if (cam->params.qx3.button) { + /* button pressed - unlock the latch */ + do_command(cam,CPIA_COMMAND_WriteMCPort,3,0xDF,0xDF,0); + do_command(cam,CPIA_COMMAND_WriteMCPort,3,0xFF,0xFF,0); + } + + /* test whether microscope is cradled */ + cam->params.qx3.cradled = ((data[2] & 0x40) == 0); + break; + + default: + break; + } + } + return retval; +} + +/* send a command to the camera with an additional data transaction */ +static int do_command_extended(struct cam_data *cam, u16 command, + u8 a, u8 b, u8 c, u8 d, + u8 e, u8 f, u8 g, u8 h, + u8 i, u8 j, u8 k, u8 l) +{ + int retval; + u8 cmd[8], data[8]; + + cmd[0] = command>>8; + cmd[1] = command&0xff; + cmd[2] = a; + cmd[3] = b; + cmd[4] = c; + cmd[5] = d; + cmd[6] = 8; + cmd[7] = 0; + data[0] = e; + data[1] = f; + data[2] = g; + data[3] = h; + data[4] = i; + data[5] = j; + data[6] = k; + data[7] = l; + + retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data); + if (retval) + DBG("%x - failed\n", command); + + return retval; +} + +/********************************************************************** + * + * Colorspace conversion + * + **********************************************************************/ +#define LIMIT(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16) + +static int convert420(unsigned char *yuv, unsigned char *rgb, int out_fmt, + int linesize, int mmap_kludge) +{ + int y, u, v, r, g, b, y1; + + /* Odd lines use the same u and v as the previous line. + * Because of compression, it is necessary to get this + * information from the decoded image. */ + switch(out_fmt) { + case VIDEO_PALETTE_RGB555: + y = (*yuv++ - 16) * 76310; + y1 = (*yuv - 16) * 76310; + r = ((*(rgb+1-linesize)) & 0x7c) << 1; + g = ((*(rgb-linesize)) & 0xe0) >> 4 | + ((*(rgb+1-linesize)) & 0x03) << 6; + b = ((*(rgb-linesize)) & 0x1f) << 3; + u = (-53294 * r - 104635 * g + 157929 * b) / 5756495; + v = (157968 * r - 132278 * g - 25690 * b) / 5366159; + r = 104635 * v; + g = -25690 * u - 53294 * v; + b = 132278 * u; + *rgb++ = ((LIMIT(g+y) & 0xf8) << 2) | (LIMIT(b+y) >> 3); + *rgb++ = ((LIMIT(r+y) & 0xf8) >> 1) | (LIMIT(g+y) >> 6); + *rgb++ = ((LIMIT(g+y1) & 0xf8) << 2) | (LIMIT(b+y1) >> 3); + *rgb = ((LIMIT(r+y1) & 0xf8) >> 1) | (LIMIT(g+y1) >> 6); + return 4; + case VIDEO_PALETTE_RGB565: + y = (*yuv++ - 16) * 76310; + y1 = (*yuv - 16) * 76310; + r = (*(rgb+1-linesize)) & 0xf8; + g = ((*(rgb-linesize)) & 0xe0) >> 3 | + ((*(rgb+1-linesize)) & 0x07) << 5; + b = ((*(rgb-linesize)) & 0x1f) << 3; + u = (-53294 * r - 104635 * g + 157929 * b) / 5756495; + v = (157968 * r - 132278 * g - 25690 * b) / 5366159; + r = 104635 * v; + g = -25690 * u - 53294 * v; + b = 132278 * u; + *rgb++ = ((LIMIT(g+y) & 0xfc) << 3) | (LIMIT(b+y) >> 3); + *rgb++ = (LIMIT(r+y) & 0xf8) | (LIMIT(g+y) >> 5); + *rgb++ = ((LIMIT(g+y1) & 0xfc) << 3) | (LIMIT(b+y1) >> 3); + *rgb = (LIMIT(r+y1) & 0xf8) | (LIMIT(g+y1) >> 5); + return 4; + break; + case VIDEO_PALETTE_RGB24: + case VIDEO_PALETTE_RGB32: + y = (*yuv++ - 16) * 76310; + y1 = (*yuv - 16) * 76310; + if (mmap_kludge) { + r = *(rgb+2-linesize); + g = *(rgb+1-linesize); + b = *(rgb-linesize); + } else { + r = *(rgb-linesize); + g = *(rgb+1-linesize); + b = *(rgb+2-linesize); + } + u = (-53294 * r - 104635 * g + 157929 * b) / 5756495; + v = (157968 * r - 132278 * g - 25690 * b) / 5366159; + r = 104635 * v; + g = -25690 * u + -53294 * v; + b = 132278 * u; + if (mmap_kludge) { + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(r+y); + if(out_fmt == VIDEO_PALETTE_RGB32) + rgb++; + *rgb++ = LIMIT(b+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(r+y1); + } else { + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(b+y); + if(out_fmt == VIDEO_PALETTE_RGB32) + rgb++; + *rgb++ = LIMIT(r+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(b+y1); + } + if(out_fmt == VIDEO_PALETTE_RGB32) + return 8; + return 6; + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + y = *yuv++; + u = *(rgb+1-linesize); + y1 = *yuv; + v = *(rgb+3-linesize); + *rgb++ = y; + *rgb++ = u; + *rgb++ = y1; + *rgb = v; + return 4; + case VIDEO_PALETTE_UYVY: + u = *(rgb-linesize); + y = *yuv++; + v = *(rgb+2-linesize); + y1 = *yuv; + *rgb++ = u; + *rgb++ = y; + *rgb++ = v; + *rgb = y1; + return 4; + case VIDEO_PALETTE_GREY: + *rgb++ = *yuv++; + *rgb = *yuv; + return 2; + default: + DBG("Empty: %d\n", out_fmt); + return 0; + } +} + + +static int yuvconvert(unsigned char *yuv, unsigned char *rgb, int out_fmt, + int in_uyvy, int mmap_kludge) +{ + int y, u, v, r, g, b, y1; + + switch(out_fmt) { + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_RGB24: + case VIDEO_PALETTE_RGB32: + if (in_uyvy) { + u = *yuv++ - 128; + y = (*yuv++ - 16) * 76310; + v = *yuv++ - 128; + y1 = (*yuv - 16) * 76310; + } else { + y = (*yuv++ - 16) * 76310; + u = *yuv++ - 128; + y1 = (*yuv++ - 16) * 76310; + v = *yuv - 128; + } + r = 104635 * v; + g = -25690 * u + -53294 * v; + b = 132278 * u; + break; + default: + y = *yuv++; + u = *yuv++; + y1 = *yuv++; + v = *yuv; + /* Just to avoid compiler warnings */ + r = 0; + g = 0; + b = 0; + break; + } + switch(out_fmt) { + case VIDEO_PALETTE_RGB555: + *rgb++ = ((LIMIT(g+y) & 0xf8) << 2) | (LIMIT(b+y) >> 3); + *rgb++ = ((LIMIT(r+y) & 0xf8) >> 1) | (LIMIT(g+y) >> 6); + *rgb++ = ((LIMIT(g+y1) & 0xf8) << 2) | (LIMIT(b+y1) >> 3); + *rgb = ((LIMIT(r+y1) & 0xf8) >> 1) | (LIMIT(g+y1) >> 6); + return 4; + case VIDEO_PALETTE_RGB565: + *rgb++ = ((LIMIT(g+y) & 0xfc) << 3) | (LIMIT(b+y) >> 3); + *rgb++ = (LIMIT(r+y) & 0xf8) | (LIMIT(g+y) >> 5); + *rgb++ = ((LIMIT(g+y1) & 0xfc) << 3) | (LIMIT(b+y1) >> 3); + *rgb = (LIMIT(r+y1) & 0xf8) | (LIMIT(g+y1) >> 5); + return 4; + case VIDEO_PALETTE_RGB24: + if (mmap_kludge) { + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(b+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(r+y1); + } else { + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(r+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(b+y1); + } + return 6; + case VIDEO_PALETTE_RGB32: + if (mmap_kludge) { + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(r+y); + rgb++; + *rgb++ = LIMIT(b+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(r+y1); + } else { + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(b+y); + rgb++; + *rgb++ = LIMIT(r+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(b+y1); + } + return 8; + case VIDEO_PALETTE_GREY: + *rgb++ = y; + *rgb = y1; + return 2; + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + *rgb++ = y; + *rgb++ = u; + *rgb++ = y1; + *rgb = v; + return 4; + case VIDEO_PALETTE_UYVY: + *rgb++ = u; + *rgb++ = y; + *rgb++ = v; + *rgb = y1; + return 4; + default: + DBG("Empty: %d\n", out_fmt); + return 0; + } +} + +static int skipcount(int count, int fmt) +{ + switch(fmt) { + case VIDEO_PALETTE_GREY: + return count; + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + case VIDEO_PALETTE_UYVY: + return 2*count; + case VIDEO_PALETTE_RGB24: + return 3*count; + case VIDEO_PALETTE_RGB32: + return 4*count; + default: + return 0; + } +} + +static int parse_picture(struct cam_data *cam, int size) +{ + u8 *obuf, *ibuf, *end_obuf; + int ll, in_uyvy, compressed, decimation, even_line, origsize, out_fmt; + int rows, cols, linesize, subsample_422; + + /* make sure params don't change while we are decoding */ + down(&cam->param_lock); + + obuf = cam->decompressed_frame.data; + end_obuf = obuf+CPIA_MAX_FRAME_SIZE; + ibuf = cam->raw_image; + origsize = size; + out_fmt = cam->vp.palette; + + if ((ibuf[0] != MAGIC_0) || (ibuf[1] != MAGIC_1)) { + LOG("header not found\n"); + up(&cam->param_lock); + return -1; + } + + if ((ibuf[16] != VIDEOSIZE_QCIF) && (ibuf[16] != VIDEOSIZE_CIF)) { + LOG("wrong video size\n"); + up(&cam->param_lock); + return -1; + } + + if (ibuf[17] != SUBSAMPLE_420 && ibuf[17] != SUBSAMPLE_422) { + LOG("illegal subtype %d\n",ibuf[17]); + up(&cam->param_lock); + return -1; + } + subsample_422 = ibuf[17] == SUBSAMPLE_422; + + if (ibuf[18] != YUVORDER_YUYV && ibuf[18] != YUVORDER_UYVY) { + LOG("illegal yuvorder %d\n",ibuf[18]); + up(&cam->param_lock); + return -1; + } + in_uyvy = ibuf[18] == YUVORDER_UYVY; + + if ((ibuf[24] != cam->params.roi.colStart) || + (ibuf[25] != cam->params.roi.colEnd) || + (ibuf[26] != cam->params.roi.rowStart) || + (ibuf[27] != cam->params.roi.rowEnd)) { + LOG("ROI mismatch\n"); + up(&cam->param_lock); + return -1; + } + cols = 8*(ibuf[25] - ibuf[24]); + rows = 4*(ibuf[27] - ibuf[26]); + + + if ((ibuf[28] != NOT_COMPRESSED) && (ibuf[28] != COMPRESSED)) { + LOG("illegal compression %d\n",ibuf[28]); + up(&cam->param_lock); + return -1; + } + compressed = (ibuf[28] == COMPRESSED); + + if (ibuf[29] != NO_DECIMATION && ibuf[29] != DECIMATION_ENAB) { + LOG("illegal decimation %d\n",ibuf[29]); + up(&cam->param_lock); + return -1; + } + decimation = (ibuf[29] == DECIMATION_ENAB); + + cam->params.yuvThreshold.yThreshold = ibuf[30]; + cam->params.yuvThreshold.uvThreshold = ibuf[31]; + cam->params.status.systemState = ibuf[32]; + cam->params.status.grabState = ibuf[33]; + cam->params.status.streamState = ibuf[34]; + cam->params.status.fatalError = ibuf[35]; + cam->params.status.cmdError = ibuf[36]; + cam->params.status.debugFlags = ibuf[37]; + cam->params.status.vpStatus = ibuf[38]; + cam->params.status.errorCode = ibuf[39]; + cam->fps = ibuf[41]; + up(&cam->param_lock); + + linesize = skipcount(cols, out_fmt); + ibuf += FRAME_HEADER_SIZE; + size -= FRAME_HEADER_SIZE; + ll = ibuf[0] | (ibuf[1] << 8); + ibuf += 2; + even_line = 1; + + while (size > 0) { + size -= (ll+2); + if (size < 0) { + LOG("Insufficient data in buffer\n"); + return -1; + } + + while (ll > 1) { + if (!compressed || (compressed && !(*ibuf & 1))) { + if(subsample_422 || even_line) { + obuf += yuvconvert(ibuf, obuf, out_fmt, + in_uyvy, cam->mmap_kludge); + ibuf += 4; + ll -= 4; + } else { + /* SUBSAMPLE_420 on an odd line */ + obuf += convert420(ibuf, obuf, + out_fmt, linesize, + cam->mmap_kludge); + ibuf += 2; + ll -= 2; + } + } else { + /*skip compressed interval from previous frame*/ + obuf += skipcount(*ibuf >> 1, out_fmt); + if (obuf > end_obuf) { + LOG("Insufficient buffer size\n"); + return -1; + } + ++ibuf; + ll--; + } + } + if (ll == 1) { + if (*ibuf != EOL) { + DBG("EOL not found giving up after %d/%d" + " bytes\n", origsize-size, origsize); + return -1; + } + + ++ibuf; /* skip over EOL */ + + if ((size > 3) && (ibuf[0] == EOI) && (ibuf[1] == EOI) && + (ibuf[2] == EOI) && (ibuf[3] == EOI)) { + size -= 4; + break; + } + + if(decimation) { + /* skip the odd lines for now */ + obuf += linesize; + } + + if (size > 1) { + ll = ibuf[0] | (ibuf[1] << 8); + ibuf += 2; /* skip over line length */ + } + if(!decimation) + even_line = !even_line; + } else { + LOG("line length was not 1 but %d after %d/%d bytes\n", + ll, origsize-size, origsize); + return -1; + } + } + + if(decimation) { + /* interpolate odd rows */ + int i, j; + u8 *prev, *next; + prev = cam->decompressed_frame.data; + obuf = prev+linesize; + next = obuf+linesize; + for(i=1; i<rows-1; i+=2) { + for(j=0; j<linesize; ++j) { + *obuf++ = ((int)*prev++ + *next++) / 2; + } + prev += linesize; + obuf += linesize; + next += linesize; + } + /* last row is odd, just copy previous row */ + memcpy(obuf, prev, linesize); + } + + cam->decompressed_frame.count = obuf-cam->decompressed_frame.data; + + return cam->decompressed_frame.count; +} + +/* InitStreamCap wrapper to select correct start line */ +static inline int init_stream_cap(struct cam_data *cam) +{ + return do_command(cam, CPIA_COMMAND_InitStreamCap, + 0, cam->params.streamStartLine, 0, 0); +} + + +/* find_over_exposure + * Finds a suitable value of OverExposure for use with SetFlickerCtrl + * Some calculation is required because this value changes with the brightness + * set with SetColourParameters + * + * Parameters: Brightness - last brightness value set with SetColourParameters + * + * Returns: OverExposure value to use with SetFlickerCtrl + */ +#define FLICKER_MAX_EXPOSURE 250 +#define FLICKER_ALLOWABLE_OVER_EXPOSURE 146 +#define FLICKER_BRIGHTNESS_CONSTANT 59 +static int find_over_exposure(int brightness) +{ + int MaxAllowableOverExposure, OverExposure; + + MaxAllowableOverExposure = FLICKER_MAX_EXPOSURE - brightness - + FLICKER_BRIGHTNESS_CONSTANT; + + if (MaxAllowableOverExposure < FLICKER_ALLOWABLE_OVER_EXPOSURE) { + OverExposure = MaxAllowableOverExposure; + } else { + OverExposure = FLICKER_ALLOWABLE_OVER_EXPOSURE; + } + + return OverExposure; +} +#undef FLICKER_MAX_EXPOSURE +#undef FLICKER_ALLOWABLE_OVER_EXPOSURE +#undef FLICKER_BRIGHTNESS_CONSTANT + +/* update various camera modes and settings */ +static void dispatch_commands(struct cam_data *cam) +{ + down(&cam->param_lock); + if (cam->cmd_queue==COMMAND_NONE) { + up(&cam->param_lock); + return; + } + DEB_BYTE(cam->cmd_queue); + DEB_BYTE(cam->cmd_queue>>8); + if (cam->cmd_queue & COMMAND_SETFORMAT) { + do_command(cam, CPIA_COMMAND_SetFormat, + cam->params.format.videoSize, + cam->params.format.subSample, + cam->params.format.yuvOrder, 0); + do_command(cam, CPIA_COMMAND_SetROI, + cam->params.roi.colStart, cam->params.roi.colEnd, + cam->params.roi.rowStart, cam->params.roi.rowEnd); + cam->first_frame = 1; + } + + if (cam->cmd_queue & COMMAND_SETCOLOURPARAMS) + do_command(cam, CPIA_COMMAND_SetColourParams, + cam->params.colourParams.brightness, + cam->params.colourParams.contrast, + cam->params.colourParams.saturation, 0); + + if (cam->cmd_queue & COMMAND_SETAPCOR) + do_command(cam, CPIA_COMMAND_SetApcor, + cam->params.apcor.gain1, + cam->params.apcor.gain2, + cam->params.apcor.gain4, + cam->params.apcor.gain8); + + if (cam->cmd_queue & COMMAND_SETVLOFFSET) + do_command(cam, CPIA_COMMAND_SetVLOffset, + cam->params.vlOffset.gain1, + cam->params.vlOffset.gain2, + cam->params.vlOffset.gain4, + cam->params.vlOffset.gain8); + + if (cam->cmd_queue & COMMAND_SETEXPOSURE) { + do_command_extended(cam, CPIA_COMMAND_SetExposure, + cam->params.exposure.gainMode, + 1, + cam->params.exposure.compMode, + cam->params.exposure.centreWeight, + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + cam->params.exposure.redComp, + cam->params.exposure.green1Comp, + cam->params.exposure.green2Comp, + cam->params.exposure.blueComp); + if(cam->params.exposure.expMode != 1) { + do_command_extended(cam, CPIA_COMMAND_SetExposure, + 0, + cam->params.exposure.expMode, + 0, 0, + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + 0, 0, 0, 0); + } + } + + if (cam->cmd_queue & COMMAND_SETCOLOURBALANCE) { + if (cam->params.colourBalance.balanceMode == 1) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 1, + cam->params.colourBalance.redGain, + cam->params.colourBalance.greenGain, + cam->params.colourBalance.blueGain); + do_command(cam, CPIA_COMMAND_SetColourBalance, + 3, 0, 0, 0); + } + if (cam->params.colourBalance.balanceMode == 2) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 2, 0, 0, 0); + } + if (cam->params.colourBalance.balanceMode == 3) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 3, 0, 0, 0); + } + } + + if (cam->cmd_queue & COMMAND_SETCOMPRESSIONTARGET) + do_command(cam, CPIA_COMMAND_SetCompressionTarget, + cam->params.compressionTarget.frTargeting, + cam->params.compressionTarget.targetFR, + cam->params.compressionTarget.targetQ, 0); + + if (cam->cmd_queue & COMMAND_SETYUVTHRESH) + do_command(cam, CPIA_COMMAND_SetYUVThresh, + cam->params.yuvThreshold.yThreshold, + cam->params.yuvThreshold.uvThreshold, 0, 0); + + if (cam->cmd_queue & COMMAND_SETCOMPRESSIONPARAMS) + do_command_extended(cam, CPIA_COMMAND_SetCompressionParams, + 0, 0, 0, 0, + cam->params.compressionParams.hysteresis, + cam->params.compressionParams.threshMax, + cam->params.compressionParams.smallStep, + cam->params.compressionParams.largeStep, + cam->params.compressionParams.decimationHysteresis, + cam->params.compressionParams.frDiffStepThresh, + cam->params.compressionParams.qDiffStepThresh, + cam->params.compressionParams.decimationThreshMod); + + if (cam->cmd_queue & COMMAND_SETCOMPRESSION) + do_command(cam, CPIA_COMMAND_SetCompression, + cam->params.compression.mode, + cam->params.compression.decimation, 0, 0); + + if (cam->cmd_queue & COMMAND_SETSENSORFPS) + do_command(cam, CPIA_COMMAND_SetSensorFPS, + cam->params.sensorFps.divisor, + cam->params.sensorFps.baserate, 0, 0); + + if (cam->cmd_queue & COMMAND_SETFLICKERCTRL) + do_command(cam, CPIA_COMMAND_SetFlickerCtrl, + cam->params.flickerControl.flickerMode, + cam->params.flickerControl.coarseJump, + abs(cam->params.flickerControl.allowableOverExposure), + 0); + + if (cam->cmd_queue & COMMAND_SETECPTIMING) + do_command(cam, CPIA_COMMAND_SetECPTiming, + cam->params.ecpTiming, 0, 0, 0); + + if (cam->cmd_queue & COMMAND_PAUSE) + do_command(cam, CPIA_COMMAND_EndStreamCap, 0, 0, 0, 0); + + if (cam->cmd_queue & COMMAND_RESUME) + init_stream_cap(cam); + + if (cam->cmd_queue & COMMAND_SETLIGHTS && cam->params.qx3.qx3_detected) + { + int p1 = (cam->params.qx3.bottomlight == 0) << 1; + int p2 = (cam->params.qx3.toplight == 0) << 3; + do_command(cam, CPIA_COMMAND_WriteVCReg, 0x90, 0x8F, 0x50, 0); + do_command(cam, CPIA_COMMAND_WriteMCPort, 2, 0, (p1|p2|0xE0), 0); + } + + cam->cmd_queue = COMMAND_NONE; + up(&cam->param_lock); + return; +} + + + +static void set_flicker(struct cam_params *params, volatile u32 *command_flags, + int on) +{ + /* Everything in here is from the Windows driver */ +#define FIRMWARE_VERSION(x,y) (params->version.firmwareVersion == (x) && \ + params->version.firmwareRevision == (y)) +/* define for compgain calculation */ +#if 0 +#define COMPGAIN(base, curexp, newexp) \ + (u8) ((((float) base - 128.0) * ((float) curexp / (float) newexp)) + 128.5) +#define EXP_FROM_COMP(basecomp, curcomp, curexp) \ + (u16)((float)curexp * (float)(u8)(curcomp + 128) / (float)(u8)(basecomp - 128)) +#else + /* equivalent functions without floating point math */ +#define COMPGAIN(base, curexp, newexp) \ + (u8)(128 + (((u32)(2*(base-128)*curexp + newexp)) / (2* newexp)) ) +#define EXP_FROM_COMP(basecomp, curcomp, curexp) \ + (u16)(((u32)(curexp * (u8)(curcomp + 128)) / (u8)(basecomp - 128))) +#endif + + + int currentexp = params->exposure.coarseExpLo + + params->exposure.coarseExpHi*256; + int startexp; + if (on) { + int cj = params->flickerControl.coarseJump; + params->flickerControl.flickerMode = 1; + params->flickerControl.disabled = 0; + if(params->exposure.expMode != 2) + *command_flags |= COMMAND_SETEXPOSURE; + params->exposure.expMode = 2; + currentexp = currentexp << params->exposure.gain; + params->exposure.gain = 0; + /* round down current exposure to nearest value */ + startexp = (currentexp + ROUND_UP_EXP_FOR_FLICKER) / cj; + if(startexp < 1) + startexp = 1; + startexp = (startexp * cj) - 1; + if(FIRMWARE_VERSION(1,2)) + while(startexp > MAX_EXP_102) + startexp -= cj; + else + while(startexp > MAX_EXP) + startexp -= cj; + params->exposure.coarseExpLo = startexp & 0xff; + params->exposure.coarseExpHi = startexp >> 8; + if (currentexp > startexp) { + if (currentexp > (2 * startexp)) + currentexp = 2 * startexp; + params->exposure.redComp = COMPGAIN (COMP_RED, currentexp, startexp); + params->exposure.green1Comp = COMPGAIN (COMP_GREEN1, currentexp, startexp); + params->exposure.green2Comp = COMPGAIN (COMP_GREEN2, currentexp, startexp); + params->exposure.blueComp = COMPGAIN (COMP_BLUE, currentexp, startexp); + } else { + params->exposure.redComp = COMP_RED; + params->exposure.green1Comp = COMP_GREEN1; + params->exposure.green2Comp = COMP_GREEN2; + params->exposure.blueComp = COMP_BLUE; + } + if(FIRMWARE_VERSION(1,2)) + params->exposure.compMode = 0; + else + params->exposure.compMode = 1; + + params->apcor.gain1 = 0x18; + params->apcor.gain2 = 0x18; + params->apcor.gain4 = 0x16; + params->apcor.gain8 = 0x14; + *command_flags |= COMMAND_SETAPCOR; + } else { + params->flickerControl.flickerMode = 0; + params->flickerControl.disabled = 1; + /* Coarse = average of equivalent coarse for each comp channel */ + startexp = EXP_FROM_COMP(COMP_RED, params->exposure.redComp, currentexp); + startexp += EXP_FROM_COMP(COMP_GREEN1, params->exposure.green1Comp, currentexp); + startexp += EXP_FROM_COMP(COMP_GREEN2, params->exposure.green2Comp, currentexp); + startexp += EXP_FROM_COMP(COMP_BLUE, params->exposure.blueComp, currentexp); + startexp = startexp >> 2; + while(startexp > MAX_EXP && + params->exposure.gain < params->exposure.gainMode-1) { + startexp = startexp >> 1; + ++params->exposure.gain; + } + if(FIRMWARE_VERSION(1,2) && startexp > MAX_EXP_102) + startexp = MAX_EXP_102; + if(startexp > MAX_EXP) + startexp = MAX_EXP; + params->exposure.coarseExpLo = startexp&0xff; + params->exposure.coarseExpHi = startexp >> 8; + params->exposure.redComp = COMP_RED; + params->exposure.green1Comp = COMP_GREEN1; + params->exposure.green2Comp = COMP_GREEN2; + params->exposure.blueComp = COMP_BLUE; + params->exposure.compMode = 1; + *command_flags |= COMMAND_SETEXPOSURE; + params->apcor.gain1 = 0x18; + params->apcor.gain2 = 0x16; + params->apcor.gain4 = 0x24; + params->apcor.gain8 = 0x34; + *command_flags |= COMMAND_SETAPCOR; + } + params->vlOffset.gain1 = 20; + params->vlOffset.gain2 = 24; + params->vlOffset.gain4 = 26; + params->vlOffset.gain8 = 26; + *command_flags |= COMMAND_SETVLOFFSET; +#undef FIRMWARE_VERSION +#undef EXP_FROM_COMP +#undef COMPGAIN +} + +#define FIRMWARE_VERSION(x,y) (cam->params.version.firmwareVersion == (x) && \ + cam->params.version.firmwareRevision == (y)) +/* monitor the exposure and adjust the sensor frame rate if needed */ +static void monitor_exposure(struct cam_data *cam) +{ + u8 exp_acc, bcomp, gain, coarseL, cmd[8], data[8]; + int retval, light_exp, dark_exp, very_dark_exp; + int old_exposure, new_exposure, framerate; + + /* get necessary stats and register settings from camera */ + /* do_command can't handle this, so do it ourselves */ + cmd[0] = CPIA_COMMAND_ReadVPRegs>>8; + cmd[1] = CPIA_COMMAND_ReadVPRegs&0xff; + cmd[2] = 30; + cmd[3] = 4; + cmd[4] = 9; + cmd[5] = 8; + cmd[6] = 8; + cmd[7] = 0; + retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data); + if (retval) { + LOG("ReadVPRegs(30,4,9,8) - failed, retval=%d\n", + retval); + return; + } + exp_acc = data[0]; + bcomp = data[1]; + gain = data[2]; + coarseL = data[3]; + + down(&cam->param_lock); + light_exp = cam->params.colourParams.brightness + + TC - 50 + EXP_ACC_LIGHT; + if(light_exp > 255) + light_exp = 255; + dark_exp = cam->params.colourParams.brightness + + TC - 50 - EXP_ACC_DARK; + if(dark_exp < 0) + dark_exp = 0; + very_dark_exp = dark_exp/2; + + old_exposure = cam->params.exposure.coarseExpHi * 256 + + cam->params.exposure.coarseExpLo; + + if(!cam->params.flickerControl.disabled) { + /* Flicker control on */ + int max_comp = FIRMWARE_VERSION(1,2) ? MAX_COMP : HIGH_COMP_102; + bcomp += 128; /* decode */ + if(bcomp >= max_comp && exp_acc < dark_exp) { + /* dark */ + if(exp_acc < very_dark_exp) { + /* very dark */ + if(cam->exposure_status == EXPOSURE_VERY_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_DARK; + cam->exposure_count = 1; + } + } else { + /* just dark */ + if(cam->exposure_status == EXPOSURE_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_DARK; + cam->exposure_count = 1; + } + } + } else if(old_exposure <= LOW_EXP || exp_acc > light_exp) { + /* light */ + if(old_exposure <= VERY_LOW_EXP) { + /* very light */ + if(cam->exposure_status == EXPOSURE_VERY_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_LIGHT; + cam->exposure_count = 1; + } + } else { + /* just light */ + if(cam->exposure_status == EXPOSURE_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_LIGHT; + cam->exposure_count = 1; + } + } + } else { + /* not dark or light */ + cam->exposure_status = EXPOSURE_NORMAL; + } + } else { + /* Flicker control off */ + if(old_exposure >= MAX_EXP && exp_acc < dark_exp) { + /* dark */ + if(exp_acc < very_dark_exp) { + /* very dark */ + if(cam->exposure_status == EXPOSURE_VERY_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_DARK; + cam->exposure_count = 1; + } + } else { + /* just dark */ + if(cam->exposure_status == EXPOSURE_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_DARK; + cam->exposure_count = 1; + } + } + } else if(old_exposure <= LOW_EXP || exp_acc > light_exp) { + /* light */ + if(old_exposure <= VERY_LOW_EXP) { + /* very light */ + if(cam->exposure_status == EXPOSURE_VERY_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_LIGHT; + cam->exposure_count = 1; + } + } else { + /* just light */ + if(cam->exposure_status == EXPOSURE_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_LIGHT; + cam->exposure_count = 1; + } + } + } else { + /* not dark or light */ + cam->exposure_status = EXPOSURE_NORMAL; + } + } + + framerate = cam->fps; + if(framerate > 30 || framerate < 1) + framerate = 1; + + if(!cam->params.flickerControl.disabled) { + /* Flicker control on */ + if((cam->exposure_status == EXPOSURE_VERY_DARK || + cam->exposure_status == EXPOSURE_DARK) && + cam->exposure_count >= DARK_TIME*framerate && + cam->params.sensorFps.divisor < 3) { + + /* dark for too long */ + ++cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->cmd_queue |= COMMAND_SETFLICKERCTRL; + + new_exposure = cam->params.flickerControl.coarseJump-1; + while(new_exposure < old_exposure/2) + new_exposure += cam->params.flickerControl.coarseJump; + cam->params.exposure.coarseExpLo = new_exposure & 0xff; + cam->params.exposure.coarseExpHi = new_exposure >> 8; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically decreasing sensor_fps\n"); + + } else if((cam->exposure_status == EXPOSURE_VERY_LIGHT || + cam->exposure_status == EXPOSURE_LIGHT) && + cam->exposure_count >= LIGHT_TIME*framerate && + cam->params.sensorFps.divisor > 0) { + + /* light for too long */ + int max_exp = FIRMWARE_VERSION(1,2) ? MAX_EXP_102 : MAX_EXP ; + + --cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->cmd_queue |= COMMAND_SETFLICKERCTRL; + + new_exposure = cam->params.flickerControl.coarseJump-1; + while(new_exposure < 2*old_exposure && + new_exposure+ + cam->params.flickerControl.coarseJump < max_exp) + new_exposure += cam->params.flickerControl.coarseJump; + cam->params.exposure.coarseExpLo = new_exposure & 0xff; + cam->params.exposure.coarseExpHi = new_exposure >> 8; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically increasing sensor_fps\n"); + } + } else { + /* Flicker control off */ + if((cam->exposure_status == EXPOSURE_VERY_DARK || + cam->exposure_status == EXPOSURE_DARK) && + cam->exposure_count >= DARK_TIME*framerate && + cam->params.sensorFps.divisor < 3) { + + /* dark for too long */ + ++cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + if(cam->params.exposure.gain > 0) { + --cam->params.exposure.gain; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + } + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically decreasing sensor_fps\n"); + + } else if((cam->exposure_status == EXPOSURE_VERY_LIGHT || + cam->exposure_status == EXPOSURE_LIGHT) && + cam->exposure_count >= LIGHT_TIME*framerate && + cam->params.sensorFps.divisor > 0) { + + /* light for too long */ + --cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + if(cam->params.exposure.gain < + cam->params.exposure.gainMode-1) { + ++cam->params.exposure.gain; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + } + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically increasing sensor_fps\n"); + } + } + up(&cam->param_lock); +} + +/*-----------------------------------------------------------------*/ +/* if flicker is switched off, this function switches it back on.It checks, + however, that conditions are suitable before restarting it. + This should only be called for firmware version 1.2. + + It also adjust the colour balance when an exposure step is detected - as + long as flicker is running +*/ +static void restart_flicker(struct cam_data *cam) +{ + int cam_exposure, old_exp; + if(!FIRMWARE_VERSION(1,2)) + return; + down(&cam->param_lock); + if(cam->params.flickerControl.flickerMode == 0 || + cam->raw_image[39] == 0) { + up(&cam->param_lock); + return; + } + cam_exposure = cam->raw_image[39]*2; + old_exp = cam->params.exposure.coarseExpLo + + cam->params.exposure.coarseExpHi*256; + /* + see how far away camera exposure is from a valid + flicker exposure value + */ + cam_exposure %= cam->params.flickerControl.coarseJump; + if(!cam->params.flickerControl.disabled && + cam_exposure <= cam->params.flickerControl.coarseJump - 3) { + /* Flicker control auto-disabled */ + cam->params.flickerControl.disabled = 1; + } + + if(cam->params.flickerControl.disabled && + cam->params.flickerControl.flickerMode && + old_exp > cam->params.flickerControl.coarseJump + + ROUND_UP_EXP_FOR_FLICKER) { + /* exposure is now high enough to switch + flicker control back on */ + set_flicker(&cam->params, &cam->cmd_queue, 1); + if((cam->cmd_queue & COMMAND_SETEXPOSURE) && + cam->params.exposure.expMode == 2) + cam->exposure_status = EXPOSURE_NORMAL; + + } + up(&cam->param_lock); +} +#undef FIRMWARE_VERSION + +static int clear_stall(struct cam_data *cam) +{ + /* FIXME: Does this actually work? */ + LOG("Clearing stall\n"); + + cam->ops->streamRead(cam->lowlevel_data, cam->raw_image, 0); + do_command(cam, CPIA_COMMAND_GetCameraStatus,0,0,0,0); + return cam->params.status.streamState != STREAM_PAUSED; +} + +/* kernel thread function to read image from camera */ +static int fetch_frame(void *data) +{ + int image_size, retry; + struct cam_data *cam = (struct cam_data *)data; + unsigned long oldjif, rate, diff; + + /* Allow up to two bad images in a row to be read and + * ignored before an error is reported */ + for (retry = 0; retry < 3; ++retry) { + if (retry) + DBG("retry=%d\n", retry); + + if (!cam->ops) + continue; + + /* load first frame always uncompressed */ + if (cam->first_frame && + cam->params.compression.mode != CPIA_COMPRESSION_NONE) { + do_command(cam, CPIA_COMMAND_SetCompression, + CPIA_COMPRESSION_NONE, + NO_DECIMATION, 0, 0); + /* Trial & error - Discarding a frame prevents the + first frame from having an error in the data. */ + do_command(cam, CPIA_COMMAND_DiscardFrame, 0, 0, 0, 0); + } + + /* init camera upload */ + if (do_command(cam, CPIA_COMMAND_GrabFrame, 0, + cam->params.streamStartLine, 0, 0)) + continue; + + if (cam->ops->wait_for_stream_ready) { + /* loop until image ready */ + int count = 0; + do_command(cam, CPIA_COMMAND_GetCameraStatus,0,0,0,0); + while (cam->params.status.streamState != STREAM_READY) { + if(++count > READY_TIMEOUT) + break; + if(cam->params.status.streamState == + STREAM_PAUSED) { + /* Bad news */ + if(!clear_stall(cam)) + return -EIO; + } + + cond_resched(); + + /* sleep for 10 ms, hopefully ;) */ + msleep_interruptible(10); + if (signal_pending(current)) + return -EINTR; + + do_command(cam, CPIA_COMMAND_GetCameraStatus, + 0, 0, 0, 0); + } + if(cam->params.status.streamState != STREAM_READY) { + continue; + } + } + + cond_resched(); + + /* grab image from camera */ + oldjif = jiffies; + image_size = cam->ops->streamRead(cam->lowlevel_data, + cam->raw_image, 0); + if (image_size <= 0) { + DBG("streamRead failed: %d\n", image_size); + continue; + } + + rate = image_size * HZ / 1024; + diff = jiffies-oldjif; + cam->transfer_rate = diff==0 ? rate : rate/diff; + /* diff==0 ? unlikely but possible */ + + /* Switch flicker control back on if it got turned off */ + restart_flicker(cam); + + /* If AEC is enabled, monitor the exposure and + adjust the sensor frame rate if needed */ + if(cam->params.exposure.expMode == 2) + monitor_exposure(cam); + + /* camera idle now so dispatch queued commands */ + dispatch_commands(cam); + + /* Update our knowledge of the camera state */ + do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0); + do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0); + do_command(cam, CPIA_COMMAND_ReadMCPorts, 0, 0, 0, 0); + + /* decompress and convert image to by copying it from + * raw_image to decompressed_frame + */ + + cond_resched(); + + cam->image_size = parse_picture(cam, image_size); + if (cam->image_size <= 0) { + DBG("parse_picture failed %d\n", cam->image_size); + if(cam->params.compression.mode != + CPIA_COMPRESSION_NONE) { + /* Compression may not work right if we + had a bad frame, get the next one + uncompressed. */ + cam->first_frame = 1; + do_command(cam, CPIA_COMMAND_SetGrabMode, + CPIA_GRAB_SINGLE, 0, 0, 0); + /* FIXME: Trial & error - need up to 70ms for + the grab mode change to complete ? */ + msleep_interruptible(70); + if (signal_pending(current)) + return -EINTR; + } + } else + break; + } + + if (retry < 3) { + /* FIXME: this only works for double buffering */ + if (cam->frame[cam->curframe].state == FRAME_READY) { + memcpy(cam->frame[cam->curframe].data, + cam->decompressed_frame.data, + cam->decompressed_frame.count); + cam->frame[cam->curframe].state = FRAME_DONE; + } else + cam->decompressed_frame.state = FRAME_DONE; + + if (cam->first_frame) { + cam->first_frame = 0; + do_command(cam, CPIA_COMMAND_SetCompression, + cam->params.compression.mode, + cam->params.compression.decimation, 0, 0); + + /* Switch from single-grab to continuous grab */ + do_command(cam, CPIA_COMMAND_SetGrabMode, + CPIA_GRAB_CONTINUOUS, 0, 0, 0); + } + return 0; + } + return -EIO; +} + +static int capture_frame(struct cam_data *cam, struct video_mmap *vm) +{ + if (!cam->frame_buf) { + /* we do lazy allocation */ + int err; + if ((err = allocate_frame_buf(cam))) + return err; + } + + cam->curframe = vm->frame; + cam->frame[cam->curframe].state = FRAME_READY; + return fetch_frame(cam); +} + +static int goto_high_power(struct cam_data *cam) +{ + if (do_command(cam, CPIA_COMMAND_GotoHiPower, 0, 0, 0, 0)) + return -EIO; + msleep_interruptible(40); /* windows driver does it too */ + if(signal_pending(current)) + return -EINTR; + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -EIO; + if (cam->params.status.systemState == HI_POWER_STATE) { + DBG("camera now in HIGH power state\n"); + return 0; + } + printstatus(cam); + return -EIO; +} + +static int goto_low_power(struct cam_data *cam) +{ + if (do_command(cam, CPIA_COMMAND_GotoLoPower, 0, 0, 0, 0)) + return -1; + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -1; + if (cam->params.status.systemState == LO_POWER_STATE) { + DBG("camera now in LOW power state\n"); + return 0; + } + printstatus(cam); + return -1; +} + +static void save_camera_state(struct cam_data *cam) +{ + if(!(cam->cmd_queue & COMMAND_SETCOLOURBALANCE)) + do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0); + if(!(cam->cmd_queue & COMMAND_SETEXPOSURE)) + do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0); + + DBG("%d/%d/%d/%d/%d/%d/%d/%d\n", + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + cam->params.exposure.redComp, + cam->params.exposure.green1Comp, + cam->params.exposure.green2Comp, + cam->params.exposure.blueComp); + DBG("%d/%d/%d\n", + cam->params.colourBalance.redGain, + cam->params.colourBalance.greenGain, + cam->params.colourBalance.blueGain); +} + +static int set_camera_state(struct cam_data *cam) +{ + cam->cmd_queue = COMMAND_SETCOMPRESSION | + COMMAND_SETCOMPRESSIONTARGET | + COMMAND_SETCOLOURPARAMS | + COMMAND_SETFORMAT | + COMMAND_SETYUVTHRESH | + COMMAND_SETECPTIMING | + COMMAND_SETCOMPRESSIONPARAMS | + COMMAND_SETEXPOSURE | + COMMAND_SETCOLOURBALANCE | + COMMAND_SETSENSORFPS | + COMMAND_SETAPCOR | + COMMAND_SETFLICKERCTRL | + COMMAND_SETVLOFFSET; + + do_command(cam, CPIA_COMMAND_SetGrabMode, CPIA_GRAB_SINGLE,0,0,0); + dispatch_commands(cam); + + /* Wait 6 frames for the sensor to get all settings and + AEC/ACB to settle */ + msleep_interruptible(6*(cam->params.sensorFps.baserate ? 33 : 40) * + (1 << cam->params.sensorFps.divisor) + 10); + + if(signal_pending(current)) + return -EINTR; + + save_camera_state(cam); + + return 0; +} + +static void get_version_information(struct cam_data *cam) +{ + /* GetCPIAVersion */ + do_command(cam, CPIA_COMMAND_GetCPIAVersion, 0, 0, 0, 0); + + /* GetPnPID */ + do_command(cam, CPIA_COMMAND_GetPnPID, 0, 0, 0, 0); +} + +/* initialize camera */ +static int reset_camera(struct cam_data *cam) +{ + int err; + /* Start the camera in low power mode */ + if (goto_low_power(cam)) { + if (cam->params.status.systemState != WARM_BOOT_STATE) + return -ENODEV; + + /* FIXME: this is just dirty trial and error */ + err = goto_high_power(cam); + if(err) + return err; + do_command(cam, CPIA_COMMAND_DiscardFrame, 0, 0, 0, 0); + if (goto_low_power(cam)) + return -ENODEV; + } + + /* procedure described in developer's guide p3-28 */ + + /* Check the firmware version. */ + cam->params.version.firmwareVersion = 0; + get_version_information(cam); + if (cam->params.version.firmwareVersion != 1) + return -ENODEV; + + /* A bug in firmware 1-02 limits gainMode to 2 */ + if(cam->params.version.firmwareRevision <= 2 && + cam->params.exposure.gainMode > 2) { + cam->params.exposure.gainMode = 2; + } + + /* set QX3 detected flag */ + cam->params.qx3.qx3_detected = (cam->params.pnpID.vendor == 0x0813 && + cam->params.pnpID.product == 0x0001); + + /* The fatal error checking should be done after + * the camera powers up (developer's guide p 3-38) */ + + /* Set streamState before transition to high power to avoid bug + * in firmware 1-02 */ + do_command(cam, CPIA_COMMAND_ModifyCameraStatus, STREAMSTATE, 0, + STREAM_NOT_READY, 0); + + /* GotoHiPower */ + err = goto_high_power(cam); + if (err) + return err; + + /* Check the camera status */ + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -EIO; + + if (cam->params.status.fatalError) { + DBG("fatal_error: %#04x\n", + cam->params.status.fatalError); + DBG("vp_status: %#04x\n", + cam->params.status.vpStatus); + if (cam->params.status.fatalError & ~(COM_FLAG|CPIA_FLAG)) { + /* Fatal error in camera */ + return -EIO; + } else if (cam->params.status.fatalError & (COM_FLAG|CPIA_FLAG)) { + /* Firmware 1-02 may do this for parallel port cameras, + * just clear the flags (developer's guide p 3-38) */ + do_command(cam, CPIA_COMMAND_ModifyCameraStatus, + FATALERROR, ~(COM_FLAG|CPIA_FLAG), 0, 0); + } + } + + /* Check the camera status again */ + if (cam->params.status.fatalError) { + if (cam->params.status.fatalError) + return -EIO; + } + + /* VPVersion can't be retrieved before the camera is in HiPower, + * so get it here instead of in get_version_information. */ + do_command(cam, CPIA_COMMAND_GetVPVersion, 0, 0, 0, 0); + + /* set camera to a known state */ + return set_camera_state(cam); +} + +static void put_cam(struct cpia_camera_ops* ops) +{ + if (ops->owner) + module_put(ops->owner); +} + +/* ------------------------- V4L interface --------------------- */ +static int cpia_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct cam_data *cam = dev->priv; + int err; + + if (!cam) { + DBG("Internal error, cam_data not found!\n"); + return -ENODEV; + } + + if (cam->open_count > 0) { + DBG("Camera already open\n"); + return -EBUSY; + } + + if (!try_module_get(cam->ops->owner)) + return -ENODEV; + + down(&cam->busy_lock); + err = -ENOMEM; + if (!cam->raw_image) { + cam->raw_image = rvmalloc(CPIA_MAX_IMAGE_SIZE); + if (!cam->raw_image) + goto oops; + } + + if (!cam->decompressed_frame.data) { + cam->decompressed_frame.data = rvmalloc(CPIA_MAX_FRAME_SIZE); + if (!cam->decompressed_frame.data) + goto oops; + } + + /* open cpia */ + err = -ENODEV; + if (cam->ops->open(cam->lowlevel_data)) + goto oops; + + /* reset the camera */ + if ((err = reset_camera(cam)) != 0) { + cam->ops->close(cam->lowlevel_data); + goto oops; + } + + err = -EINTR; + if(signal_pending(current)) + goto oops; + + /* Set ownership of /proc/cpia/videoX to current user */ + if(cam->proc_entry) + cam->proc_entry->uid = current->uid; + + /* set mark for loading first frame uncompressed */ + cam->first_frame = 1; + + /* init it to something */ + cam->mmap_kludge = 0; + + ++cam->open_count; + file->private_data = dev; + up(&cam->busy_lock); + return 0; + + oops: + if (cam->decompressed_frame.data) { + rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE); + cam->decompressed_frame.data = NULL; + } + if (cam->raw_image) { + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + } + up(&cam->busy_lock); + put_cam(cam->ops); + return err; +} + +static int cpia_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + struct cam_data *cam = dev->priv; + + if (cam->ops) { + /* Return ownership of /proc/cpia/videoX to root */ + if(cam->proc_entry) + cam->proc_entry->uid = 0; + + /* save camera state for later open (developers guide ch 3.5.3) */ + save_camera_state(cam); + + /* GotoLoPower */ + goto_low_power(cam); + + /* Update the camera status */ + do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0); + + /* cleanup internal state stuff */ + free_frames(cam->frame); + + /* close cpia */ + cam->ops->close(cam->lowlevel_data); + + put_cam(cam->ops); + } + + if (--cam->open_count == 0) { + /* clean up capture-buffers */ + if (cam->raw_image) { + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + } + + if (cam->decompressed_frame.data) { + rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE); + cam->decompressed_frame.data = NULL; + } + + if (cam->frame_buf) + free_frame_buf(cam); + + if (!cam->ops) + kfree(cam); + } + file->private_data = NULL; + + return 0; +} + +static ssize_t cpia_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *dev = file->private_data; + struct cam_data *cam = dev->priv; + int err; + + /* make this _really_ smp and multithread-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (!buf) { + DBG("buf NULL\n"); + up(&cam->busy_lock); + return -EINVAL; + } + + if (!count) { + DBG("count 0\n"); + up(&cam->busy_lock); + return 0; + } + + if (!cam->ops) { + DBG("ops NULL\n"); + up(&cam->busy_lock); + return -ENODEV; + } + + /* upload frame */ + cam->decompressed_frame.state = FRAME_READY; + cam->mmap_kludge=0; + if((err = fetch_frame(cam)) != 0) { + DBG("ERROR from fetch_frame: %d\n", err); + up(&cam->busy_lock); + return err; + } + cam->decompressed_frame.state = FRAME_UNUSED; + + /* copy data to user space */ + if (cam->decompressed_frame.count > count) { + DBG("count wrong: %d, %lu\n", cam->decompressed_frame.count, + (unsigned long) count); + up(&cam->busy_lock); + return -EFAULT; + } + if (copy_to_user(buf, cam->decompressed_frame.data, + cam->decompressed_frame.count)) { + DBG("copy_to_user failed\n"); + up(&cam->busy_lock); + return -EFAULT; + } + + up(&cam->busy_lock); + return cam->decompressed_frame.count; +} + +static int cpia_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = file->private_data; + struct cam_data *cam = dev->priv; + int retval = 0; + + if (!cam || !cam->ops) + return -ENODEV; + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + //DBG("cpia_ioctl: %u\n", ioctlnr); + + switch (ioctlnr) { + /* query capabilites */ + case VIDIOCGCAP: + { + struct video_capability *b = arg; + + DBG("VIDIOCGCAP\n"); + strcpy(b->name, "CPiA Camera"); + b->type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE; + b->channels = 1; + b->audios = 0; + b->maxwidth = 352; /* VIDEOSIZE_CIF */ + b->maxheight = 288; + b->minwidth = 48; /* VIDEOSIZE_48_48 */ + b->minheight = 48; + break; + } + + /* get/set video source - we are a camera and nothing else */ + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + + DBG("VIDIOCGCHAN\n"); + if (v->channel != 0) { + retval = -EINVAL; + break; + } + + v->channel = 0; + strcpy(v->name, "Camera"); + v->tuners = 0; + v->flags = 0; + v->type = VIDEO_TYPE_CAMERA; + v->norm = 0; + break; + } + + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + + DBG("VIDIOCSCHAN\n"); + if (v->channel != 0) + retval = -EINVAL; + break; + } + + /* image properties */ + case VIDIOCGPICT: + { + struct video_picture *pic = arg; + DBG("VIDIOCGPICT\n"); + *pic = cam->vp; + break; + } + + case VIDIOCSPICT: + { + struct video_picture *vp = arg; + + DBG("VIDIOCSPICT\n"); + + /* check validity */ + DBG("palette: %d\n", vp->palette); + DBG("depth: %d\n", vp->depth); + if (!valid_mode(vp->palette, vp->depth)) { + retval = -EINVAL; + break; + } + + down(&cam->param_lock); + /* brightness, colour, contrast need no check 0-65535 */ + cam->vp = *vp; + /* update cam->params.colourParams */ + cam->params.colourParams.brightness = vp->brightness*100/65535; + cam->params.colourParams.contrast = vp->contrast*100/65535; + cam->params.colourParams.saturation = vp->colour*100/65535; + /* contrast is in steps of 8, so round */ + cam->params.colourParams.contrast = + ((cam->params.colourParams.contrast + 3) / 8) * 8; + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2 && + cam->params.colourParams.contrast > 80) { + /* 1-02 firmware limits contrast to 80 */ + cam->params.colourParams.contrast = 80; + } + + /* Adjust flicker control if necessary */ + if(cam->params.flickerControl.allowableOverExposure < 0) + cam->params.flickerControl.allowableOverExposure = + -find_over_exposure(cam->params.colourParams.brightness); + if(cam->params.flickerControl.flickerMode != 0) + cam->cmd_queue |= COMMAND_SETFLICKERCTRL; + + + /* queue command to update camera */ + cam->cmd_queue |= COMMAND_SETCOLOURPARAMS; + up(&cam->param_lock); + DBG("VIDIOCSPICT: %d / %d // %d / %d / %d / %d\n", + vp->depth, vp->palette, vp->brightness, vp->hue, vp->colour, + vp->contrast); + break; + } + + /* get/set capture window */ + case VIDIOCGWIN: + { + struct video_window *vw = arg; + DBG("VIDIOCGWIN\n"); + + *vw = cam->vw; + break; + } + + case VIDIOCSWIN: + { + /* copy_from_user, check validity, copy to internal structure */ + struct video_window *vw = arg; + DBG("VIDIOCSWIN\n"); + + if (vw->clipcount != 0) { /* clipping not supported */ + retval = -EINVAL; + break; + } + if (vw->clips != NULL) { /* clipping not supported */ + retval = -EINVAL; + break; + } + + /* we set the video window to something smaller or equal to what + * is requested by the user??? + */ + down(&cam->param_lock); + if (vw->width != cam->vw.width || vw->height != cam->vw.height) { + int video_size = match_videosize(vw->width, vw->height); + + if (video_size < 0) { + retval = -EINVAL; + up(&cam->param_lock); + break; + } + cam->video_size = video_size; + + /* video size is changing, reset the subcapture area */ + memset(&cam->vc, 0, sizeof(cam->vc)); + + set_vw_size(cam); + DBG("%d / %d\n", cam->vw.width, cam->vw.height); + cam->cmd_queue |= COMMAND_SETFORMAT; + } + + up(&cam->param_lock); + + /* setformat ignored by camera during streaming, + * so stop/dispatch/start */ + if (cam->cmd_queue & COMMAND_SETFORMAT) { + DBG("\n"); + dispatch_commands(cam); + } + DBG("%d/%d:%d\n", cam->video_size, + cam->vw.width, cam->vw.height); + break; + } + + /* mmap interface */ + case VIDIOCGMBUF: + { + struct video_mbuf *vm = arg; + int i; + + DBG("VIDIOCGMBUF\n"); + memset(vm, 0, sizeof(*vm)); + vm->size = CPIA_MAX_FRAME_SIZE*FRAME_NUM; + vm->frames = FRAME_NUM; + for (i = 0; i < FRAME_NUM; i++) + vm->offsets[i] = CPIA_MAX_FRAME_SIZE * i; + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + int video_size; + + DBG("VIDIOCMCAPTURE: %d / %d / %dx%d\n", vm->format, vm->frame, + vm->width, vm->height); + if (vm->frame<0||vm->frame>=FRAME_NUM) { + retval = -EINVAL; + break; + } + + /* set video format */ + cam->vp.palette = vm->format; + switch(vm->format) { + case VIDEO_PALETTE_GREY: + cam->vp.depth=8; + break; + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + case VIDEO_PALETTE_UYVY: + cam->vp.depth = 16; + break; + case VIDEO_PALETTE_RGB24: + cam->vp.depth = 24; + break; + case VIDEO_PALETTE_RGB32: + cam->vp.depth = 32; + break; + default: + retval = -EINVAL; + break; + } + if (retval) + break; + + /* set video size */ + video_size = match_videosize(vm->width, vm->height); + if (video_size < 0) { + retval = -EINVAL; + break; + } + if (video_size != cam->video_size) { + cam->video_size = video_size; + + /* video size is changing, reset the subcapture area */ + memset(&cam->vc, 0, sizeof(cam->vc)); + + set_vw_size(cam); + cam->cmd_queue |= COMMAND_SETFORMAT; + dispatch_commands(cam); + } + /* according to v4l-spec we must start streaming here */ + cam->mmap_kludge = 1; + retval = capture_frame(cam, vm); + + break; + } + + case VIDIOCSYNC: + { + int *frame = arg; + + //DBG("VIDIOCSYNC: %d\n", *frame); + + if (*frame<0 || *frame >= FRAME_NUM) { + retval = -EINVAL; + break; + } + + switch (cam->frame[*frame].state) { + case FRAME_UNUSED: + case FRAME_READY: + case FRAME_GRABBING: + DBG("sync to unused frame %d\n", *frame); + retval = -EINVAL; + break; + + case FRAME_DONE: + cam->frame[*frame].state = FRAME_UNUSED; + //DBG("VIDIOCSYNC: %d synced\n", *frame); + break; + } + if (retval == -EINTR) { + /* FIXME - xawtv does not handle this nice */ + retval = 0; + } + break; + } + + case VIDIOCGCAPTURE: + { + struct video_capture *vc = arg; + + DBG("VIDIOCGCAPTURE\n"); + + *vc = cam->vc; + + break; + } + + case VIDIOCSCAPTURE: + { + struct video_capture *vc = arg; + + DBG("VIDIOCSCAPTURE\n"); + + if (vc->decimation != 0) { /* How should this be used? */ + retval = -EINVAL; + break; + } + if (vc->flags != 0) { /* Even/odd grab not supported */ + retval = -EINVAL; + break; + } + + /* Clip to the resolution we can set for the ROI + (every 8 columns and 4 rows) */ + vc->x = vc->x & ~(__u32)7; + vc->y = vc->y & ~(__u32)3; + vc->width = vc->width & ~(__u32)7; + vc->height = vc->height & ~(__u32)3; + + if(vc->width == 0 || vc->height == 0 || + vc->x + vc->width > cam->vw.width || + vc->y + vc->height > cam->vw.height) { + retval = -EINVAL; + break; + } + + DBG("%d,%d/%dx%d\n", vc->x,vc->y,vc->width, vc->height); + + down(&cam->param_lock); + + cam->vc.x = vc->x; + cam->vc.y = vc->y; + cam->vc.width = vc->width; + cam->vc.height = vc->height; + + set_vw_size(cam); + cam->cmd_queue |= COMMAND_SETFORMAT; + + up(&cam->param_lock); + + /* setformat ignored by camera during streaming, + * so stop/dispatch/start */ + dispatch_commands(cam); + break; + } + + case VIDIOCGUNIT: + { + struct video_unit *vu = arg; + + DBG("VIDIOCGUNIT\n"); + + vu->video = cam->vdev.minor; + vu->vbi = VIDEO_NO_UNIT; + vu->radio = VIDEO_NO_UNIT; + vu->audio = VIDEO_NO_UNIT; + vu->teletext = VIDEO_NO_UNIT; + + break; + } + + + /* pointless to implement overlay with this camera */ + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCKEY: + /* tuner interface - we have none */ + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + /* audio interface - we have none */ + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + retval = -EINVAL; + break; + default: + retval = -ENOIOCTLCMD; + break; + } + + up(&cam->busy_lock); + return retval; +} + +static int cpia_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, cpia_do_ioctl); +} + + +/* FIXME */ +static int cpia_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long page, pos; + struct cam_data *cam = dev->priv; + int retval; + + if (!cam || !cam->ops) + return -ENODEV; + + DBG("cpia_mmap: %ld\n", size); + + if (size > FRAME_NUM*CPIA_MAX_FRAME_SIZE) + return -EINVAL; + + if (!cam || !cam->ops) + return -ENODEV; + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (!cam->frame_buf) { /* we do lazy allocation */ + if ((retval = allocate_frame_buf(cam))) { + up(&cam->busy_lock); + return retval; + } + } + + pos = (unsigned long)(cam->frame_buf); + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + up(&cam->busy_lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + DBG("cpia_mmap: %ld\n", size); + up(&cam->busy_lock); + + return 0; +} + +static struct file_operations cpia_fops = { + .owner = THIS_MODULE, + .open = cpia_open, + .release = cpia_close, + .read = cpia_read, + .mmap = cpia_mmap, + .ioctl = cpia_ioctl, + .llseek = no_llseek, +}; + +static struct video_device cpia_template = { + .owner = THIS_MODULE, + .name = "CPiA Camera", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_CPIA, + .fops = &cpia_fops, +}; + +/* initialise cam_data structure */ +static void reset_camera_struct(struct cam_data *cam) +{ + /* The following parameter values are the defaults from + * "Software Developer's Guide for CPiA Cameras". Any changes + * to the defaults are noted in comments. */ + cam->params.colourParams.brightness = 50; + cam->params.colourParams.contrast = 48; + cam->params.colourParams.saturation = 50; + cam->params.exposure.gainMode = 4; + cam->params.exposure.expMode = 2; /* AEC */ + cam->params.exposure.compMode = 1; + cam->params.exposure.centreWeight = 1; + cam->params.exposure.gain = 0; + cam->params.exposure.fineExp = 0; + cam->params.exposure.coarseExpLo = 185; + cam->params.exposure.coarseExpHi = 0; + cam->params.exposure.redComp = COMP_RED; + cam->params.exposure.green1Comp = COMP_GREEN1; + cam->params.exposure.green2Comp = COMP_GREEN2; + cam->params.exposure.blueComp = COMP_BLUE; + cam->params.colourBalance.balanceMode = 2; /* ACB */ + cam->params.colourBalance.redGain = 32; + cam->params.colourBalance.greenGain = 6; + cam->params.colourBalance.blueGain = 92; + cam->params.apcor.gain1 = 0x18; + cam->params.apcor.gain2 = 0x16; + cam->params.apcor.gain4 = 0x24; + cam->params.apcor.gain8 = 0x34; + cam->params.flickerControl.flickerMode = 0; + cam->params.flickerControl.disabled = 1; + + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->params.flickerControl.allowableOverExposure = + -find_over_exposure(cam->params.colourParams.brightness); + cam->params.vlOffset.gain1 = 20; + cam->params.vlOffset.gain2 = 24; + cam->params.vlOffset.gain4 = 26; + cam->params.vlOffset.gain8 = 26; + cam->params.compressionParams.hysteresis = 3; + cam->params.compressionParams.threshMax = 11; + cam->params.compressionParams.smallStep = 1; + cam->params.compressionParams.largeStep = 3; + cam->params.compressionParams.decimationHysteresis = 2; + cam->params.compressionParams.frDiffStepThresh = 5; + cam->params.compressionParams.qDiffStepThresh = 3; + cam->params.compressionParams.decimationThreshMod = 2; + /* End of default values from Software Developer's Guide */ + + cam->transfer_rate = 0; + cam->exposure_status = EXPOSURE_NORMAL; + + /* Set Sensor FPS to 15fps. This seems better than 30fps + * for indoor lighting. */ + cam->params.sensorFps.divisor = 1; + cam->params.sensorFps.baserate = 1; + + cam->params.yuvThreshold.yThreshold = 6; /* From windows driver */ + cam->params.yuvThreshold.uvThreshold = 6; /* From windows driver */ + + cam->params.format.subSample = SUBSAMPLE_422; + cam->params.format.yuvOrder = YUVORDER_YUYV; + + cam->params.compression.mode = CPIA_COMPRESSION_AUTO; + cam->params.compressionTarget.frTargeting = + CPIA_COMPRESSION_TARGET_QUALITY; + cam->params.compressionTarget.targetFR = 15; /* From windows driver */ + cam->params.compressionTarget.targetQ = 5; /* From windows driver */ + + cam->params.qx3.qx3_detected = 0; + cam->params.qx3.toplight = 0; + cam->params.qx3.bottomlight = 0; + cam->params.qx3.button = 0; + cam->params.qx3.cradled = 0; + + cam->video_size = VIDEOSIZE_CIF; + + cam->vp.colour = 32768; /* 50% */ + cam->vp.hue = 32768; /* 50% */ + cam->vp.brightness = 32768; /* 50% */ + cam->vp.contrast = 32768; /* 50% */ + cam->vp.whiteness = 0; /* not used -> grayscale only */ + cam->vp.depth = 24; /* to be set by user */ + cam->vp.palette = VIDEO_PALETTE_RGB24; /* to be set by user */ + + cam->vc.x = 0; + cam->vc.y = 0; + cam->vc.width = 0; + cam->vc.height = 0; + + cam->vw.x = 0; + cam->vw.y = 0; + set_vw_size(cam); + cam->vw.chromakey = 0; + cam->vw.flags = 0; + cam->vw.clipcount = 0; + cam->vw.clips = NULL; + + cam->cmd_queue = COMMAND_NONE; + cam->first_frame = 1; + + return; +} + +/* initialize cam_data structure */ +static void init_camera_struct(struct cam_data *cam, + struct cpia_camera_ops *ops ) +{ + int i; + + /* Default everything to 0 */ + memset(cam, 0, sizeof(struct cam_data)); + + cam->ops = ops; + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + reset_camera_struct(cam); + + cam->proc_entry = NULL; + + memcpy(&cam->vdev, &cpia_template, sizeof(cpia_template)); + cam->vdev.priv = cam; + + cam->curframe = 0; + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].width = 0; + cam->frame[i].height = 0; + cam->frame[i].state = FRAME_UNUSED; + cam->frame[i].data = NULL; + } + cam->decompressed_frame.width = 0; + cam->decompressed_frame.height = 0; + cam->decompressed_frame.state = FRAME_UNUSED; + cam->decompressed_frame.data = NULL; +} + +struct cam_data *cpia_register_camera(struct cpia_camera_ops *ops, void *lowlevel) +{ + struct cam_data *camera; + + if ((camera = kmalloc(sizeof(struct cam_data), GFP_KERNEL)) == NULL) + return NULL; + + + init_camera_struct( camera, ops ); + camera->lowlevel_data = lowlevel; + + /* register v4l device */ + if (video_register_device(&camera->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + kfree(camera); + printk(KERN_DEBUG "video_register_device failed\n"); + return NULL; + } + + /* get version information from camera: open/reset/close */ + + /* open cpia */ + if (camera->ops->open(camera->lowlevel_data)) + return camera; + + /* reset the camera */ + if (reset_camera(camera) != 0) { + camera->ops->close(camera->lowlevel_data); + return camera; + } + + /* close cpia */ + camera->ops->close(camera->lowlevel_data); + +#ifdef CONFIG_PROC_FS + create_proc_cpia_cam(camera); +#endif + + printk(KERN_INFO " CPiA Version: %d.%02d (%d.%d)\n", + camera->params.version.firmwareVersion, + camera->params.version.firmwareRevision, + camera->params.version.vcVersion, + camera->params.version.vcRevision); + printk(KERN_INFO " CPiA PnP-ID: %04x:%04x:%04x\n", + camera->params.pnpID.vendor, + camera->params.pnpID.product, + camera->params.pnpID.deviceRevision); + printk(KERN_INFO " VP-Version: %d.%d %04x\n", + camera->params.vpVersion.vpVersion, + camera->params.vpVersion.vpRevision, + camera->params.vpVersion.cameraHeadID); + + return camera; +} + +void cpia_unregister_camera(struct cam_data *cam) +{ + DBG("unregistering video\n"); + video_unregister_device(&cam->vdev); + if (cam->open_count) { + put_cam(cam->ops); + DBG("camera open -- setting ops to NULL\n"); + cam->ops = NULL; + } + +#ifdef CONFIG_PROC_FS + DBG("destroying /proc/cpia/video%d\n", cam->vdev.minor); + destroy_proc_cpia_cam(cam); +#endif + if (!cam->open_count) { + DBG("freeing camera\n"); + kfree(cam); + } +} + +static int __init cpia_init(void) +{ + printk(KERN_INFO "%s v%d.%d.%d\n", ABOUT, + CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER); + + printk(KERN_WARNING "Since in-kernel colorspace conversion is not " + "allowed, it is disabled by default now. Users should fix the " + "applications in case they don't work without conversion " + "reenabled by setting the 'colorspace_conv' module " + "parameter to 1"); + +#ifdef CONFIG_PROC_FS + proc_cpia_create(); +#endif + +#ifdef CONFIG_VIDEO_CPIA_PP + cpia_pp_init(); +#endif +#ifdef CONFIG_VIDEO_CPIA_USB + cpia_usb_init(); +#endif + + return 0; +} + +static void __exit cpia_exit(void) +{ +#ifdef CONFIG_PROC_FS + proc_cpia_destroy(); +#endif +} + +module_init(cpia_init); +module_exit(cpia_exit); + +/* Exported symbols for modules. */ + +EXPORT_SYMBOL(cpia_register_camera); +EXPORT_SYMBOL(cpia_unregister_camera); diff --git a/drivers/media/video/cpia.h b/drivers/media/video/cpia.h new file mode 100644 index 000000000000..f629b693ee65 --- /dev/null +++ b/drivers/media/video/cpia.h @@ -0,0 +1,430 @@ +#ifndef cpia_h +#define cpia_h + +/* + * CPiA Parallel Port Video4Linux driver + * + * Supports CPiA based parallel port Video Camera's. + * + * (C) Copyright 1999 Bas Huisman, + * Peter Pregler, + * Scott J. Bertin, + * VLSI Vision Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define CPIA_MAJ_VER 1 +#define CPIA_MIN_VER 2 +#define CPIA_PATCH_VER 3 + +#define CPIA_PP_MAJ_VER CPIA_MAJ_VER +#define CPIA_PP_MIN_VER CPIA_MIN_VER +#define CPIA_PP_PATCH_VER CPIA_PATCH_VER + +#define CPIA_USB_MAJ_VER CPIA_MAJ_VER +#define CPIA_USB_MIN_VER CPIA_MIN_VER +#define CPIA_USB_PATCH_VER CPIA_PATCH_VER + +#define CPIA_MAX_FRAME_SIZE_UNALIGNED (352 * 288 * 4) /* CIF at RGB32 */ +#define CPIA_MAX_FRAME_SIZE ((CPIA_MAX_FRAME_SIZE_UNALIGNED + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) /* align above to PAGE_SIZE */ + +#ifdef __KERNEL__ + +#include <asm/uaccess.h> +#include <linux/videodev.h> +#include <linux/list.h> +#include <linux/smp_lock.h> + +struct cpia_camera_ops +{ + /* open sets privdata to point to structure for this camera. + * Returns negative value on error, otherwise 0. + */ + int (*open)(void *privdata); + + /* Registers callback function cb to be called with cbdata + * when an image is ready. If cb is NULL, only single image grabs + * should be used. cb should immediately call streamRead to read + * the data or data may be lost. Returns negative value on error, + * otherwise 0. + */ + int (*registerCallback)(void *privdata, void (*cb)(void *cbdata), + void *cbdata); + + /* transferCmd sends commands to the camera. command MUST point to + * an 8 byte buffer in kernel space. data can be NULL if no extra + * data is needed. The size of the data is given by the last 2 + * bytes of command. data must also point to memory in kernel space. + * Returns negative value on error, otherwise 0. + */ + int (*transferCmd)(void *privdata, u8 *command, u8 *data); + + /* streamStart initiates stream capture mode. + * Returns negative value on error, otherwise 0. + */ + int (*streamStart)(void *privdata); + + /* streamStop terminates stream capture mode. + * Returns negative value on error, otherwise 0. + */ + int (*streamStop)(void *privdata); + + /* streamRead reads a frame from the camera. buffer points to a + * buffer large enough to hold a complete frame in kernel space. + * noblock indicates if this should be a non blocking read. + * Returns the number of bytes read, or negative value on error. + */ + int (*streamRead)(void *privdata, u8 *buffer, int noblock); + + /* close disables the device until open() is called again. + * Returns negative value on error, otherwise 0. + */ + int (*close)(void *privdata); + + /* If wait_for_stream_ready is non-zero, wait until the streamState + * is STREAM_READY before calling streamRead. + */ + int wait_for_stream_ready; + + /* + * Used to maintain lowlevel module usage counts + */ + struct module *owner; +}; + +struct cpia_frame { + u8 *data; + int count; + int width; + int height; + volatile int state; +}; + +struct cam_params { + struct { + u8 firmwareVersion; + u8 firmwareRevision; + u8 vcVersion; + u8 vcRevision; + } version; + struct { + u16 vendor; + u16 product; + u16 deviceRevision; + } pnpID; + struct { + u8 vpVersion; + u8 vpRevision; + u16 cameraHeadID; + } vpVersion; + struct { + u8 systemState; + u8 grabState; + u8 streamState; + u8 fatalError; + u8 cmdError; + u8 debugFlags; + u8 vpStatus; + u8 errorCode; + } status; + struct { + u8 brightness; + u8 contrast; + u8 saturation; + } colourParams; + struct { + u8 gainMode; + u8 expMode; + u8 compMode; + u8 centreWeight; + u8 gain; + u8 fineExp; + u8 coarseExpLo; + u8 coarseExpHi; + u8 redComp; + u8 green1Comp; + u8 green2Comp; + u8 blueComp; + } exposure; + struct { + u8 balanceMode; + u8 redGain; + u8 greenGain; + u8 blueGain; + } colourBalance; + struct { + u8 divisor; + u8 baserate; + } sensorFps; + struct { + u8 gain1; + u8 gain2; + u8 gain4; + u8 gain8; + } apcor; + struct { + u8 disabled; + u8 flickerMode; + u8 coarseJump; + int allowableOverExposure; + } flickerControl; + struct { + u8 gain1; + u8 gain2; + u8 gain4; + u8 gain8; + } vlOffset; + struct { + u8 mode; + u8 decimation; + } compression; + struct { + u8 frTargeting; + u8 targetFR; + u8 targetQ; + } compressionTarget; + struct { + u8 yThreshold; + u8 uvThreshold; + } yuvThreshold; + struct { + u8 hysteresis; + u8 threshMax; + u8 smallStep; + u8 largeStep; + u8 decimationHysteresis; + u8 frDiffStepThresh; + u8 qDiffStepThresh; + u8 decimationThreshMod; + } compressionParams; + struct { + u8 videoSize; /* CIF/QCIF */ + u8 subSample; + u8 yuvOrder; + } format; + struct { /* Intel QX3 specific data */ + u8 qx3_detected; /* a QX3 is present */ + u8 toplight; /* top light lit , R/W */ + u8 bottomlight; /* bottom light lit, R/W */ + u8 button; /* snapshot button pressed (R/O) */ + u8 cradled; /* microscope is in cradle (R/O) */ + } qx3; + struct { + u8 colStart; /* skip first 8*colStart pixels */ + u8 colEnd; /* finish at 8*colEnd pixels */ + u8 rowStart; /* skip first 4*rowStart lines */ + u8 rowEnd; /* finish at 4*rowEnd lines */ + } roi; + u8 ecpTiming; + u8 streamStartLine; +}; + +enum v4l_camstates { + CPIA_V4L_IDLE = 0, + CPIA_V4L_ERROR, + CPIA_V4L_COMMAND, + CPIA_V4L_GRABBING, + CPIA_V4L_STREAMING, + CPIA_V4L_STREAMING_PAUSED, +}; + +#define FRAME_NUM 2 /* double buffering for now */ + +struct cam_data { + struct list_head cam_data_list; + + struct semaphore busy_lock; /* guard against SMP multithreading */ + struct cpia_camera_ops *ops; /* lowlevel driver operations */ + void *lowlevel_data; /* private data for lowlevel driver */ + u8 *raw_image; /* buffer for raw image data */ + struct cpia_frame decompressed_frame; + /* buffer to hold decompressed frame */ + int image_size; /* sizeof last decompressed image */ + int open_count; /* # of process that have camera open */ + + /* camera status */ + int fps; /* actual fps reported by the camera */ + int transfer_rate; /* transfer rate from camera in kB/s */ + u8 mainsFreq; /* for flicker control */ + + /* proc interface */ + struct semaphore param_lock; /* params lock for this camera */ + struct cam_params params; /* camera settings */ + struct proc_dir_entry *proc_entry; /* /proc/cpia/videoX */ + + /* v4l */ + int video_size; /* VIDEO_SIZE_ */ + volatile enum v4l_camstates camstate; /* v4l layer status */ + struct video_device vdev; /* v4l videodev */ + struct video_picture vp; /* v4l camera settings */ + struct video_window vw; /* v4l capture area */ + struct video_capture vc; /* v4l subcapture area */ + + /* mmap interface */ + int curframe; /* the current frame to grab into */ + u8 *frame_buf; /* frame buffer data */ + struct cpia_frame frame[FRAME_NUM]; + /* FRAME_NUM-buffering, so we need a array */ + + int first_frame; + int mmap_kludge; /* 'wrong' byte order for mmap */ + volatile u32 cmd_queue; /* queued commands */ + int exposure_status; /* EXPOSURE_* */ + int exposure_count; /* number of frames at this status */ +}; + +/* cpia_register_camera is called by low level driver for each camera. + * A unique camera number is returned, or a negative value on error */ +struct cam_data *cpia_register_camera(struct cpia_camera_ops *ops, void *lowlevel); + +/* cpia_unregister_camera is called by low level driver when a camera + * is removed. This must not fail. */ +void cpia_unregister_camera(struct cam_data *cam); + +/* raw CIF + 64 byte header + (2 bytes line_length + EOL) per line + 4*EOI + + * one byte 16bit DMA alignment + */ +#define CPIA_MAX_IMAGE_SIZE ((352*288*2)+64+(288*3)+5) + +/* constant value's */ +#define MAGIC_0 0x19 +#define MAGIC_1 0x68 +#define DATA_IN 0xC0 +#define DATA_OUT 0x40 +#define VIDEOSIZE_QCIF 0 /* 176x144 */ +#define VIDEOSIZE_CIF 1 /* 352x288 */ +#define VIDEOSIZE_SIF 2 /* 320x240 */ +#define VIDEOSIZE_QSIF 3 /* 160x120 */ +#define VIDEOSIZE_48_48 4 /* where no one has gone before, iconsize! */ +#define VIDEOSIZE_64_48 5 +#define VIDEOSIZE_128_96 6 +#define VIDEOSIZE_160_120 VIDEOSIZE_QSIF +#define VIDEOSIZE_176_144 VIDEOSIZE_QCIF +#define VIDEOSIZE_192_144 7 +#define VIDEOSIZE_224_168 8 +#define VIDEOSIZE_256_192 9 +#define VIDEOSIZE_288_216 10 +#define VIDEOSIZE_320_240 VIDEOSIZE_SIF +#define VIDEOSIZE_352_288 VIDEOSIZE_CIF +#define VIDEOSIZE_88_72 11 /* quarter CIF */ +#define SUBSAMPLE_420 0 +#define SUBSAMPLE_422 1 +#define YUVORDER_YUYV 0 +#define YUVORDER_UYVY 1 +#define NOT_COMPRESSED 0 +#define COMPRESSED 1 +#define NO_DECIMATION 0 +#define DECIMATION_ENAB 1 +#define EOI 0xff /* End Of Image */ +#define EOL 0xfd /* End Of Line */ +#define FRAME_HEADER_SIZE 64 + +/* Image grab modes */ +#define CPIA_GRAB_SINGLE 0 +#define CPIA_GRAB_CONTINUOUS 1 + +/* Compression parameters */ +#define CPIA_COMPRESSION_NONE 0 +#define CPIA_COMPRESSION_AUTO 1 +#define CPIA_COMPRESSION_MANUAL 2 +#define CPIA_COMPRESSION_TARGET_QUALITY 0 +#define CPIA_COMPRESSION_TARGET_FRAMERATE 1 + +/* Return offsets for GetCameraState */ +#define SYSTEMSTATE 0 +#define GRABSTATE 1 +#define STREAMSTATE 2 +#define FATALERROR 3 +#define CMDERROR 4 +#define DEBUGFLAGS 5 +#define VPSTATUS 6 +#define ERRORCODE 7 + +/* SystemState */ +#define UNINITIALISED_STATE 0 +#define PASS_THROUGH_STATE 1 +#define LO_POWER_STATE 2 +#define HI_POWER_STATE 3 +#define WARM_BOOT_STATE 4 + +/* GrabState */ +#define GRAB_IDLE 0 +#define GRAB_ACTIVE 1 +#define GRAB_DONE 2 + +/* StreamState */ +#define STREAM_NOT_READY 0 +#define STREAM_READY 1 +#define STREAM_OPEN 2 +#define STREAM_PAUSED 3 +#define STREAM_FINISHED 4 + +/* Fatal Error, CmdError, and DebugFlags */ +#define CPIA_FLAG 1 +#define SYSTEM_FLAG 2 +#define INT_CTRL_FLAG 4 +#define PROCESS_FLAG 8 +#define COM_FLAG 16 +#define VP_CTRL_FLAG 32 +#define CAPTURE_FLAG 64 +#define DEBUG_FLAG 128 + +/* VPStatus */ +#define VP_STATE_OK 0x00 + +#define VP_STATE_FAILED_VIDEOINIT 0x01 +#define VP_STATE_FAILED_AECACBINIT 0x02 +#define VP_STATE_AEC_MAX 0x04 +#define VP_STATE_ACB_BMAX 0x08 + +#define VP_STATE_ACB_RMIN 0x10 +#define VP_STATE_ACB_GMIN 0x20 +#define VP_STATE_ACB_RMAX 0x40 +#define VP_STATE_ACB_GMAX 0x80 + +/* default (minimum) compensation values */ +#define COMP_RED 220 +#define COMP_GREEN1 214 +#define COMP_GREEN2 COMP_GREEN1 +#define COMP_BLUE 230 + +/* exposure status */ +#define EXPOSURE_VERY_LIGHT 0 +#define EXPOSURE_LIGHT 1 +#define EXPOSURE_NORMAL 2 +#define EXPOSURE_DARK 3 +#define EXPOSURE_VERY_DARK 4 + +/* ErrorCode */ +#define ERROR_FLICKER_BELOW_MIN_EXP 0x01 /*flicker exposure got below minimum exposure */ +#define ALOG(fmt,args...) printk(fmt, ##args) +#define LOG(fmt,args...) ALOG(KERN_INFO __FILE__ ":%s(%d):" fmt, __FUNCTION__ , __LINE__ , ##args) + +#ifdef _CPIA_DEBUG_ +#define ADBG(fmt,args...) printk(fmt, jiffies, ##args) +#define DBG(fmt,args...) ADBG(KERN_DEBUG __FILE__" (%ld):%s(%d):" fmt, __FUNCTION__, __LINE__ , ##args) +#else +#define DBG(fmn,args...) do {} while(0) +#endif + +#define DEB_BYTE(p)\ + DBG("%1d %1d %1d %1d %1d %1d %1d %1d \n",\ + (p)&0x80?1:0, (p)&0x40?1:0, (p)&0x20?1:0, (p)&0x10?1:0,\ + (p)&0x08?1:0, (p)&0x04?1:0, (p)&0x02?1:0, (p)&0x01?1:0); + +#endif /* __KERNEL__ */ + +#endif /* cpia_h */ diff --git a/drivers/media/video/cpia_pp.c b/drivers/media/video/cpia_pp.c new file mode 100644 index 000000000000..ddf184f95d80 --- /dev/null +++ b/drivers/media/video/cpia_pp.c @@ -0,0 +1,886 @@ +/* + * cpia_pp CPiA Parallel Port driver + * + * Supports CPiA based parallel port Video Camera's. + * + * (C) Copyright 1999 Bas Huisman <bhuism@cs.utwente.nl> + * (C) Copyright 1999-2000 Scott J. Bertin <sbertin@securenym.net>, + * (C) Copyright 1999-2000 Peter Pregler <Peter_Pregler@email.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* define _CPIA_DEBUG_ for verbose debug output (see cpia.h) */ +/* #define _CPIA_DEBUG_ 1 */ + +#include <linux/config.h> + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/kernel.h> +#include <linux/parport.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/smp_lock.h> +#include <linux/sched.h> + +#include <linux/kmod.h> + +/* #define _CPIA_DEBUG_ define for verbose debug output */ +#include "cpia.h" + +static int cpia_pp_open(void *privdata); +static int cpia_pp_registerCallback(void *privdata, void (*cb) (void *cbdata), + void *cbdata); +static int cpia_pp_transferCmd(void *privdata, u8 *command, u8 *data); +static int cpia_pp_streamStart(void *privdata); +static int cpia_pp_streamStop(void *privdata); +static int cpia_pp_streamRead(void *privdata, u8 *buffer, int noblock); +static int cpia_pp_close(void *privdata); + + +#define ABOUT "Parallel port driver for Vision CPiA based cameras" + +#define PACKET_LENGTH 8 + +/* Magic numbers for defining port-device mappings */ +#define PPCPIA_PARPORT_UNSPEC -4 +#define PPCPIA_PARPORT_AUTO -3 +#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,}; + +MODULE_AUTHOR("B. Huisman <bhuism@cs.utwente.nl> & Peter Pregler <Peter_Pregler@email.com>"); +MODULE_DESCRIPTION("Parallel port driver for Vision CPiA based cameras"); +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; + struct parport *port; + struct work_struct cb_task; + int open_count; + wait_queue_head_t wq_stream; + /* image state flags */ + int image_ready; /* we got an interrupt */ + int image_complete; /* we have seen 4 EOI */ + + int streaming; /* we are in streaming mode */ + int stream_irq; +}; + +static struct cpia_camera_ops cpia_pp_ops = +{ + cpia_pp_open, + cpia_pp_registerCallback, + cpia_pp_transferCmd, + cpia_pp_streamStart, + cpia_pp_streamStop, + cpia_pp_streamRead, + cpia_pp_close, + 1, + THIS_MODULE +}; + +static LIST_HEAD(cam_list); +static spinlock_t cam_list_lock_pp; + +/* FIXME */ +static void cpia_parport_enable_irq( struct parport *port ) { + parport_enable_irq(port); + mdelay(10); + return; +} + +static void cpia_parport_disable_irq( struct parport *port ) { + parport_disable_irq(port); + mdelay(10); + return; +} + +/* Special CPiA PPC modes: These are invoked by using the 1284 Extensibility + * Link Flag during negotiation */ +#define UPLOAD_FLAG 0x08 +#define NIBBLE_TRANSFER 0x01 +#define ECP_TRANSFER 0x03 + +#define PARPORT_CHUNK_SIZE PAGE_SIZE + + +/**************************************************************************** + * + * CPiA-specific low-level parport functions for nibble uploads + * + ***************************************************************************/ +/* CPiA nonstandard "Nibble" mode (no nDataAvail signal after each byte). */ +/* The standard kernel parport_ieee1284_read_nibble() fails with the CPiA... */ + +static size_t cpia_read_nibble (struct parport *port, + void *buffer, size_t len, + int flags) +{ + /* adapted verbatim, with one change, from + parport_ieee1284_read_nibble() in drivers/parport/ieee1284-ops.c */ + + unsigned char *buf = buffer; + int i; + unsigned char byte = 0; + + len *= 2; /* in nibbles */ + for (i=0; i < len; i++) { + unsigned char nibble; + + /* The CPiA firmware suppresses the use of nDataAvail (nFault LO) + * after every second nibble to signal that more + * data is available. (the total number of Bytes that + * should be sent is known; if too few are received, an error + * will be recorded after a timeout). + * This is incompatible with parport_ieee1284_read_nibble(), + * which expects to find nFault LO after every second nibble. + */ + + /* Solution: modify cpia_read_nibble to only check for + * nDataAvail before the first nibble is sent. + */ + + /* Does the error line indicate end of data? */ + if (((i /*& 1*/) == 0) && + (parport_read_status(port) & PARPORT_STATUS_ERROR)) { + port->physport->ieee1284.phase = IEEE1284_PH_HBUSY_DNA; + DBG("%s: No more nibble data (%d bytes)\n", + port->name, i/2); + + /* Go to reverse idle phase. */ + parport_frob_control (port, + PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + port->physport->ieee1284.phase = IEEE1284_PH_REV_IDLE; + break; + } + + /* Event 7: Set nAutoFd low. */ + parport_frob_control (port, + PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + + /* Event 9: nAck goes low. */ + port->ieee1284.phase = IEEE1284_PH_REV_DATA; + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, 0)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 9 (%d bytes)\n", + port->name, i/2); + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + break; + } + + + /* Read a nibble. */ + nibble = parport_read_status (port) >> 3; + nibble &= ~8; + if ((nibble & 0x10) == 0) + nibble |= 8; + nibble &= 0xf; + + /* Event 10: Set nAutoFd high. */ + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + + /* Event 11: nAck goes high. */ + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, + PARPORT_STATUS_ACK)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 11\n", + port->name); + break; + } + + if (i & 1) { + /* Second nibble */ + byte |= nibble << 4; + *buf++ = byte; + } else + byte = nibble; + } + + i /= 2; /* i is now in bytes */ + + if (i == len) { + /* Read the last nibble without checking data avail. */ + port = port->physport; + if (parport_read_status (port) & PARPORT_STATUS_ERROR) + port->ieee1284.phase = IEEE1284_PH_HBUSY_DNA; + else + port->ieee1284.phase = IEEE1284_PH_HBUSY_DAVAIL; + } + + return i; +} + +/* CPiA nonstandard "Nibble Stream" mode (2 nibbles per cycle, instead of 1) + * (See CPiA Data sheet p. 31) + * + * "Nibble Stream" mode used by CPiA for uploads to non-ECP ports is a + * nonstandard variant of nibble mode which allows the same (mediocre) + * data flow of 8 bits per cycle as software-enabled ECP by TRISTATE-capable + * parallel ports, but works also for non-TRISTATE-capable ports. + * (Standard nibble mode only send 4 bits per cycle) + * + */ + +static size_t cpia_read_nibble_stream(struct parport *port, + void *buffer, size_t len, + int flags) +{ + int i; + unsigned char *buf = buffer; + int endseen = 0; + + for (i=0; i < len; i++) { + unsigned char nibble[2], byte = 0; + int j; + + /* Image Data is complete when 4 consecutive EOI bytes (0xff) are seen */ + if (endseen > 3 ) + break; + + /* Event 7: Set nAutoFd low. */ + parport_frob_control (port, + PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + + /* Event 9: nAck goes low. */ + port->ieee1284.phase = IEEE1284_PH_REV_DATA; + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, 0)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 9 (%d bytes)\n", + port->name, i/2); + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + break; + } + + /* Read lower nibble */ + nibble[0] = parport_read_status (port) >>3; + + /* Event 10: Set nAutoFd high. */ + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + + /* Event 11: nAck goes high. */ + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, + PARPORT_STATUS_ACK)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 11\n", + port->name); + break; + } + + /* Read upper nibble */ + nibble[1] = parport_read_status (port) >>3; + + /* reassemble the byte */ + for (j = 0; j < 2 ; j++ ) { + nibble[j] &= ~8; + if ((nibble[j] & 0x10) == 0) + nibble[j] |= 8; + nibble[j] &= 0xf; + } + byte = (nibble[0] |(nibble[1] << 4)); + *buf++ = byte; + + if(byte == EOI) + endseen++; + else + endseen = 0; + } + return i; +} + +/**************************************************************************** + * + * EndTransferMode + * + ***************************************************************************/ +static void EndTransferMode(struct pp_cam_entry *cam) +{ + parport_negotiate(cam->port, IEEE1284_MODE_COMPAT); +} + +/**************************************************************************** + * + * ForwardSetup + * + ***************************************************************************/ +static int ForwardSetup(struct pp_cam_entry *cam) +{ + int retry; + + /* The CPiA uses ECP protocol for Downloads from the Host to the camera. + * This will be software-emulated if ECP hardware is not present + */ + + /* the usual camera maximum response time is 10ms, but after receiving + * some commands, it needs up to 40ms. (Data Sheet p. 32)*/ + + for(retry = 0; retry < 4; ++retry) { + if(!parport_negotiate(cam->port, IEEE1284_MODE_ECP)) { + break; + } + mdelay(10); + } + if(retry == 4) { + DBG("Unable to negotiate IEEE1284 ECP Download mode\n"); + return -1; + } + return 0; +} +/**************************************************************************** + * + * ReverseSetup + * + ***************************************************************************/ +static int ReverseSetup(struct pp_cam_entry *cam, int extensibility) +{ + int retry; + int upload_mode, mode = IEEE1284_MODE_ECP; + int transfer_mode = ECP_TRANSFER; + + if (!(cam->port->modes & PARPORT_MODE_ECP) && + !(cam->port->modes & PARPORT_MODE_TRISTATE)) { + mode = IEEE1284_MODE_NIBBLE; + transfer_mode = NIBBLE_TRANSFER; + } + + upload_mode = mode; + if(extensibility) mode = UPLOAD_FLAG|transfer_mode|IEEE1284_EXT_LINK; + + /* the usual camera maximum response time is 10ms, but after + * receiving some commands, it needs up to 40ms. */ + + for(retry = 0; retry < 4; ++retry) { + if(!parport_negotiate(cam->port, mode)) { + break; + } + mdelay(10); + } + if(retry == 4) { + if(extensibility) + DBG("Unable to negotiate upload extensibility mode\n"); + else + DBG("Unable to negotiate upload mode\n"); + return -1; + } + if(extensibility) cam->port->ieee1284.mode = upload_mode; + return 0; +} + +/**************************************************************************** + * + * WritePacket + * + ***************************************************************************/ +static int WritePacket(struct pp_cam_entry *cam, const u8 *packet, size_t size) +{ + int retval=0; + int size_written; + + if (packet == NULL) { + return -EINVAL; + } + if (ForwardSetup(cam)) { + DBG("Write failed in setup\n"); + return -EIO; + } + size_written = parport_write(cam->port, packet, size); + if(size_written != size) { + DBG("Write failed, wrote %d/%d\n", size_written, size); + retval = -EIO; + } + EndTransferMode(cam); + return retval; +} + +/**************************************************************************** + * + * ReadPacket + * + ***************************************************************************/ +static int ReadPacket(struct pp_cam_entry *cam, u8 *packet, size_t size) +{ + int retval=0; + + if (packet == NULL) { + return -EINVAL; + } + if (ReverseSetup(cam, 0)) { + return -EIO; + } + + /* support for CPiA variant nibble reads */ + if(cam->port->ieee1284.mode == IEEE1284_MODE_NIBBLE) { + if(cpia_read_nibble(cam->port, packet, size, 0) != size) + retval = -EIO; + } else { + if(parport_read(cam->port, packet, size) != size) + retval = -EIO; + } + EndTransferMode(cam); + return retval; +} + +/**************************************************************************** + * + * cpia_pp_streamStart + * + ***************************************************************************/ +static int cpia_pp_streamStart(void *privdata) +{ + struct pp_cam_entry *cam = privdata; + DBG("\n"); + cam->streaming=1; + cam->image_ready=0; + //if (ReverseSetup(cam,1)) return -EIO; + if(cam->stream_irq) cpia_parport_enable_irq(cam->port); + return 0; +} + +/**************************************************************************** + * + * cpia_pp_streamStop + * + ***************************************************************************/ +static int cpia_pp_streamStop(void *privdata) +{ + struct pp_cam_entry *cam = privdata; + + DBG("\n"); + cam->streaming=0; + cpia_parport_disable_irq(cam->port); + //EndTransferMode(cam); + + return 0; +} + +/**************************************************************************** + * + * cpia_pp_streamRead + * + ***************************************************************************/ +static int cpia_pp_read(struct parport *port, u8 *buffer, int len) +{ + int bytes_read; + + /* support for CPiA variant "nibble stream" reads */ + if(port->ieee1284.mode == IEEE1284_MODE_NIBBLE) + bytes_read = cpia_read_nibble_stream(port,buffer,len,0); + else { + int new_bytes; + for(bytes_read=0; bytes_read<len; bytes_read += new_bytes) { + new_bytes = parport_read(port, buffer+bytes_read, + len-bytes_read); + if(new_bytes < 0) break; + } + } + return bytes_read; +} + +static int cpia_pp_streamRead(void *privdata, u8 *buffer, int noblock) +{ + struct pp_cam_entry *cam = privdata; + int read_bytes = 0; + int i, endseen, block_size, new_bytes; + + if(cam == NULL) { + DBG("Internal driver error: cam is NULL\n"); + return -EINVAL; + } + if(buffer == NULL) { + DBG("Internal driver error: buffer is NULL\n"); + return -EINVAL; + } + //if(cam->streaming) DBG("%d / %d\n", cam->image_ready, noblock); + if( cam->stream_irq ) { + DBG("%d\n", cam->image_ready); + cam->image_ready--; + } + cam->image_complete=0; + if (0/*cam->streaming*/) { + if(!cam->image_ready) { + if(noblock) return -EWOULDBLOCK; + interruptible_sleep_on(&cam->wq_stream); + if( signal_pending(current) ) return -EINTR; + DBG("%d\n", cam->image_ready); + } + } else { + if (ReverseSetup(cam, 1)) { + DBG("unable to ReverseSetup\n"); + return -EIO; + } + } + endseen = 0; + block_size = PARPORT_CHUNK_SIZE; + while( !cam->image_complete ) { + cond_resched(); + + new_bytes = cpia_pp_read(cam->port, buffer, block_size ); + if( new_bytes <= 0 ) { + break; + } + i=-1; + while(++i<new_bytes && endseen<4) { + if(*buffer==EOI) { + endseen++; + } else { + endseen=0; + } + buffer++; + } + read_bytes += i; + if( endseen==4 ) { + cam->image_complete=1; + break; + } + if( CPIA_MAX_IMAGE_SIZE-read_bytes <= PARPORT_CHUNK_SIZE ) { + block_size=CPIA_MAX_IMAGE_SIZE-read_bytes; + } + } + EndTransferMode(cam); + return cam->image_complete ? read_bytes : -EIO; +} +/**************************************************************************** + * + * cpia_pp_transferCmd + * + ***************************************************************************/ +static int cpia_pp_transferCmd(void *privdata, u8 *command, u8 *data) +{ + int err; + int retval=0; + int databytes; + struct pp_cam_entry *cam = privdata; + + if(cam == NULL) { + DBG("Internal driver error: cam is NULL\n"); + return -EINVAL; + } + if(command == NULL) { + DBG("Internal driver error: command is NULL\n"); + return -EINVAL; + } + databytes = (((int)command[7])<<8) | command[6]; + if ((err = WritePacket(cam, command, PACKET_LENGTH)) < 0) { + DBG("Error writing command\n"); + return err; + } + if(command[0] == DATA_IN) { + u8 buffer[8]; + if(data == NULL) { + DBG("Internal driver error: data is NULL\n"); + return -EINVAL; + } + if((err = ReadPacket(cam, buffer, 8)) < 0) { + DBG("Error reading command result\n"); + return err; + } + memcpy(data, buffer, databytes); + } else if(command[0] == DATA_OUT) { + if(databytes > 0) { + if(data == NULL) { + DBG("Internal driver error: data is NULL\n"); + retval = -EINVAL; + } else { + if((err=WritePacket(cam, data, databytes)) < 0){ + DBG("Error writing command data\n"); + return err; + } + } + } + } else { + DBG("Unexpected first byte of command: %x\n", command[0]); + retval = -EINVAL; + } + return retval; +} + +/**************************************************************************** + * + * cpia_pp_open + * + ***************************************************************************/ +static int cpia_pp_open(void *privdata) +{ + struct pp_cam_entry *cam = (struct pp_cam_entry *)privdata; + + if (cam == NULL) + return -EINVAL; + + if(cam->open_count == 0) { + if (parport_claim(cam->pdev)) { + DBG("failed to claim the port\n"); + return -EBUSY; + } + parport_negotiate(cam->port, IEEE1284_MODE_COMPAT); + parport_data_forward(cam->port); + parport_write_control(cam->port, PARPORT_CONTROL_SELECT); + udelay(50); + parport_write_control(cam->port, + PARPORT_CONTROL_SELECT + | PARPORT_CONTROL_INIT); + } + + ++cam->open_count; + + return 0; +} + +/**************************************************************************** + * + * cpia_pp_registerCallback + * + ***************************************************************************/ +static int cpia_pp_registerCallback(void *privdata, void (*cb)(void *cbdata), void *cbdata) +{ + struct pp_cam_entry *cam = privdata; + int retval = 0; + + if(cam->port->irq != PARPORT_IRQ_NONE) { + INIT_WORK(&cam->cb_task, cb, cbdata); + } else { + retval = -1; + } + return retval; +} + +/**************************************************************************** + * + * cpia_pp_close + * + ***************************************************************************/ +static int cpia_pp_close(void *privdata) +{ + struct pp_cam_entry *cam = privdata; + if (--cam->open_count == 0) { + parport_release(cam->pdev); + } + return 0; +} + +/**************************************************************************** + * + * cpia_pp_register + * + ***************************************************************************/ +static int cpia_pp_register(struct parport *port) +{ + struct pardevice *pdev = NULL; + struct pp_cam_entry *cam; + struct cam_data *cpia; + + if (!(port->modes & PARPORT_MODE_PCSPP)) { + LOG("port is not supported by CPiA driver\n"); + return -ENXIO; + } + + cam = kmalloc(sizeof(struct pp_cam_entry), GFP_KERNEL); + if (cam == NULL) { + LOG("failed to allocate camera structure\n"); + return -ENOMEM; + } + memset(cam,0,sizeof(struct pp_cam_entry)); + + pdev = parport_register_device(port, "cpia_pp", NULL, NULL, + NULL, 0, cam); + + if (!pdev) { + LOG("failed to parport_register_device\n"); + kfree(cam); + return -ENXIO; + } + + cam->pdev = pdev; + cam->port = port; + init_waitqueue_head(&cam->wq_stream); + + cam->streaming = 0; + cam->stream_irq = 0; + + if((cpia = cpia_register_camera(&cpia_pp_ops, cam)) == NULL) { + LOG("failed to cpia_register_camera\n"); + parport_unregister_device(pdev); + kfree(cam); + return -ENXIO; + } + spin_lock( &cam_list_lock_pp ); + list_add( &cpia->cam_data_list, &cam_list ); + spin_unlock( &cam_list_lock_pp ); + + return 0; +} + +static void cpia_pp_detach (struct parport *port) +{ + struct list_head *tmp; + struct cam_data *cpia = NULL; + struct pp_cam_entry *cam; + + spin_lock( &cam_list_lock_pp ); + list_for_each (tmp, &cam_list) { + cpia = list_entry(tmp, struct cam_data, cam_data_list); + cam = (struct pp_cam_entry *) cpia->lowlevel_data; + if (cam && cam->port->number == port->number) { + list_del(&cpia->cam_data_list); + break; + } + cpia = NULL; + } + spin_unlock( &cam_list_lock_pp ); + + if (!cpia) { + DBG("cpia_pp_detach failed to find cam_data in cam_list\n"); + return; + } + + cam = (struct pp_cam_entry *) cpia->lowlevel_data; + cpia_unregister_camera(cpia); + if(cam->open_count > 0) + cpia_pp_close(cam); + parport_unregister_device(cam->pdev); + cpia->lowlevel_data = NULL; + kfree(cam); +} + +static void cpia_pp_attach (struct parport *port) +{ + unsigned int i; + + switch (parport_nr[0]) + { + case PPCPIA_PARPORT_UNSPEC: + case PPCPIA_PARPORT_AUTO: + if (port->probe_info[0].class != PARPORT_CLASS_MEDIA || + port->probe_info[0].cmdset == NULL || + strncmp(port->probe_info[0].cmdset, "CPIA_1", 6) != 0) + return; + + cpia_pp_register(port); + + break; + + default: + for (i = 0; i < PARPORT_MAX; ++i) { + if (port->number == parport_nr[i]) { + cpia_pp_register(port); + break; + } + } + break; + } +} + +static struct parport_driver cpia_pp_driver = { + .name = "cpia_pp", + .attach = cpia_pp_attach, + .detach = cpia_pp_detach, +}; + +int 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); + + if(parport_nr[0] == PPCPIA_PARPORT_OFF) { + printk(" disabled\n"); + return 0; + } + + spin_lock_init( &cam_list_lock_pp ); + + if (parport_register_driver (&cpia_pp_driver)) { + LOG ("unable to register with parport\n"); + return -EIO; + } + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + if (parport[0]) { + /* The user gave some parameters. Let's see what they were. */ + if (!strncmp(parport[0], "auto", 4)) { + parport_nr[0] = PPCPIA_PARPORT_AUTO; + } else { + int n; + for (n = 0; n < PARPORT_MAX && parport[n]; n++) { + if (!strncmp(parport[n], "none", 4)) { + parport_nr[n] = PPCPIA_PARPORT_NONE; + } else { + char *ep; + unsigned long r = simple_strtoul(parport[n], &ep, 0); + if (ep != parport[n]) { + parport_nr[n] = r; + } else { + LOG("bad port specifier `%s'\n", parport[n]); + return -ENODEV; + } + } + } + } + } + return cpia_pp_init(); +} + +void cleanup_module(void) +{ + parport_unregister_driver (&cpia_pp_driver); + return; +} + +#else /* !MODULE */ + +static int __init cpia_pp_setup(char *str) +{ + 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; + } + + return 0; +} + +__setup("cpia_pp=", cpia_pp_setup); + +#endif /* !MODULE */ diff --git a/drivers/media/video/cpia_usb.c b/drivers/media/video/cpia_usb.c new file mode 100644 index 000000000000..cdda423386c5 --- /dev/null +++ b/drivers/media/video/cpia_usb.c @@ -0,0 +1,662 @@ +/* + * cpia_usb CPiA USB driver + * + * Supports CPiA based parallel port Video Camera's. + * + * Copyright (C) 1999 Jochen Scharrlach <Jochen.Scharrlach@schwaben.de> + * Copyright (C) 1999, 2000 Johannes Erdfelt <johannes@erdfelt.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* define _CPIA_DEBUG_ for verbose debug output (see cpia.h) */ +/* #define _CPIA_DEBUG_ 1 */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/usb.h> + +#include "cpia.h" + +#define USB_REQ_CPIA_GRAB_FRAME 0xC1 +#define USB_REQ_CPIA_UPLOAD_FRAME 0xC2 +#define WAIT_FOR_NEXT_FRAME 0 +#define FORCE_FRAME_UPLOAD 1 + +#define FRAMES_PER_DESC 10 +#define FRAME_SIZE_PER_DESC 960 /* Shouldn't be hardcoded */ +#define CPIA_NUMSBUF 2 +#define STREAM_BUF_SIZE (PAGE_SIZE * 4) +#define SCRATCH_BUF_SIZE (STREAM_BUF_SIZE * 2) + +struct cpia_sbuf { + char *data; + struct urb *urb; +}; + +#define FRAMEBUF_LEN (CPIA_MAX_FRAME_SIZE+100) +enum framebuf_status { + FRAME_EMPTY, + FRAME_READING, + FRAME_READY, + FRAME_ERROR, +}; + +struct framebuf { + int length; + enum framebuf_status status; + u8 data[FRAMEBUF_LEN]; + struct framebuf *next; +}; + +struct usb_cpia { + /* Device structure */ + struct usb_device *dev; + + unsigned char iface; + wait_queue_head_t wq_stream; + + int cursbuf; /* Current receiving sbuf */ + struct cpia_sbuf sbuf[CPIA_NUMSBUF]; /* Double buffering */ + + int streaming; + int open; + int present; + struct framebuf *buffers[3]; + struct framebuf *curbuff, *workbuff; +}; + +static int cpia_usb_open(void *privdata); +static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata), + void *cbdata); +static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data); +static int cpia_usb_streamStart(void *privdata); +static int cpia_usb_streamStop(void *privdata); +static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock); +static int cpia_usb_close(void *privdata); + +#define ABOUT "USB driver for Vision CPiA based cameras" + +static struct cpia_camera_ops cpia_usb_ops = { + cpia_usb_open, + cpia_usb_registerCallback, + cpia_usb_transferCmd, + cpia_usb_streamStart, + cpia_usb_streamStop, + cpia_usb_streamRead, + cpia_usb_close, + 0, + THIS_MODULE +}; + +static LIST_HEAD(cam_list); +static spinlock_t cam_list_lock_usb; + +static void cpia_usb_complete(struct urb *urb, struct pt_regs *regs) +{ + int i; + char *cdata; + struct usb_cpia *ucpia; + + if (!urb || !urb->context) + return; + + ucpia = (struct usb_cpia *) urb->context; + + if (!ucpia->dev || !ucpia->streaming || !ucpia->present || !ucpia->open) + return; + + if (ucpia->workbuff->status == FRAME_EMPTY) { + ucpia->workbuff->status = FRAME_READING; + ucpia->workbuff->length = 0; + } + + for (i = 0; i < urb->number_of_packets; i++) { + int n = urb->iso_frame_desc[i].actual_length; + int st = urb->iso_frame_desc[i].status; + + cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + if (st) + printk(KERN_DEBUG "cpia data error: [%d] len=%d, status=%X\n", i, n, st); + + if (FRAMEBUF_LEN < ucpia->workbuff->length + n) { + printk(KERN_DEBUG "cpia: scratch buf overflow!scr_len: %d, n: %d\n", ucpia->workbuff->length, n); + return; + } + + if (n) { + if ((ucpia->workbuff->length > 0) || + (0x19 == cdata[0] && 0x68 == cdata[1])) { + memcpy(ucpia->workbuff->data + ucpia->workbuff->length, cdata, n); + ucpia->workbuff->length += n; + } else + DBG("Ignoring packet!\n"); + } else { + if (ucpia->workbuff->length > 4 && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-1] && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-2] && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-3] && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-4]) { + ucpia->workbuff->status = FRAME_READY; + ucpia->curbuff = ucpia->workbuff; + ucpia->workbuff = ucpia->workbuff->next; + ucpia->workbuff->status = FRAME_EMPTY; + ucpia->workbuff->length = 0; + + if (waitqueue_active(&ucpia->wq_stream)) + wake_up_interruptible(&ucpia->wq_stream); + } + } + } + + /* resubmit */ + urb->dev = ucpia->dev; + if ((i = usb_submit_urb(urb, GFP_ATOMIC)) != 0) + printk(KERN_ERR "%s: usb_submit_urb ret %d\n", __FUNCTION__, i); +} + +static int cpia_usb_open(void *privdata) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + struct urb *urb; + int ret, retval = 0, fx, err; + + if (!ucpia) + return -EINVAL; + + ucpia->sbuf[0].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL); + if (!ucpia->sbuf[0].data) + return -EINVAL; + + ucpia->sbuf[1].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL); + if (!ucpia->sbuf[1].data) { + retval = -EINVAL; + goto error_0; + } + + ret = usb_set_interface(ucpia->dev, ucpia->iface, 3); + if (ret < 0) { + printk(KERN_ERR "cpia_usb_open: usb_set_interface error (ret = %d)\n", ret); + retval = -EBUSY; + goto error_1; + } + + ucpia->buffers[0]->status = FRAME_EMPTY; + ucpia->buffers[0]->length = 0; + ucpia->buffers[1]->status = FRAME_EMPTY; + ucpia->buffers[1]->length = 0; + ucpia->buffers[2]->status = FRAME_EMPTY; + ucpia->buffers[2]->length = 0; + ucpia->curbuff = ucpia->buffers[0]; + ucpia->workbuff = ucpia->buffers[1]; + + /* We double buffer the Iso lists, and also know the polling + * interval is every frame (1 == (1 << (bInterval -1))). + */ + urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL); + if (!urb) { + printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n"); + retval = -ENOMEM; + goto error_1; + } + + ucpia->sbuf[0].urb = urb; + urb->dev = ucpia->dev; + urb->context = ucpia; + urb->pipe = usb_rcvisocpipe(ucpia->dev, 1); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = ucpia->sbuf[0].data; + urb->complete = cpia_usb_complete; + urb->number_of_packets = FRAMES_PER_DESC; + urb->interval = 1; + urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC; + for (fx = 0; fx < FRAMES_PER_DESC; fx++) { + urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx; + urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC; + } + + urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL); + if (!urb) { + printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 1\n"); + retval = -ENOMEM; + goto error_urb0; + } + + ucpia->sbuf[1].urb = urb; + urb->dev = ucpia->dev; + urb->context = ucpia; + urb->pipe = usb_rcvisocpipe(ucpia->dev, 1); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = ucpia->sbuf[1].data; + urb->complete = cpia_usb_complete; + urb->number_of_packets = FRAMES_PER_DESC; + urb->interval = 1; + urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC; + for (fx = 0; fx < FRAMES_PER_DESC; fx++) { + urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx; + urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC; + } + + /* queue the ISO urbs, and resubmit in the completion handler */ + err = usb_submit_urb(ucpia->sbuf[0].urb, GFP_KERNEL); + if (err) { + printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 0 ret %d\n", + err); + goto error_urb1; + } + err = usb_submit_urb(ucpia->sbuf[1].urb, GFP_KERNEL); + if (err) { + printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 1 ret %d\n", + err); + goto error_urb1; + } + + ucpia->streaming = 1; + ucpia->open = 1; + + return 0; + +error_urb1: /* free urb 1 */ + usb_free_urb(ucpia->sbuf[1].urb); + ucpia->sbuf[1].urb = NULL; +error_urb0: /* free urb 0 */ + usb_free_urb(ucpia->sbuf[0].urb); + ucpia->sbuf[0].urb = NULL; +error_1: + kfree (ucpia->sbuf[1].data); + ucpia->sbuf[1].data = NULL; +error_0: + kfree (ucpia->sbuf[0].data); + ucpia->sbuf[0].data = NULL; + + return retval; +} + +// +// convenience functions +// + +/**************************************************************************** + * + * WritePacket + * + ***************************************************************************/ +static int WritePacket(struct usb_device *udev, const u8 *packet, u8 *buf, size_t size) +{ + if (!packet) + return -EINVAL; + + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + packet[1] + (packet[0] << 8), + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + packet[2] + (packet[3] << 8), + packet[4] + (packet[5] << 8), buf, size, 1000); +} + +/**************************************************************************** + * + * ReadPacket + * + ***************************************************************************/ +static int ReadPacket(struct usb_device *udev, u8 *packet, u8 *buf, size_t size) +{ + if (!packet || size <= 0) + return -EINVAL; + + return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + packet[1] + (packet[0] << 8), + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + packet[2] + (packet[3] << 8), + packet[4] + (packet[5] << 8), buf, size, 1000); +} + +static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data) +{ + int err = 0; + int databytes; + struct usb_cpia *ucpia = (struct usb_cpia *)privdata; + struct usb_device *udev = ucpia->dev; + + if (!udev) { + DBG("Internal driver error: udev is NULL\n"); + return -EINVAL; + } + + if (!command) { + DBG("Internal driver error: command is NULL\n"); + return -EINVAL; + } + + databytes = (((int)command[7])<<8) | command[6]; + + if (command[0] == DATA_IN) { + u8 buffer[8]; + + if (!data) { + DBG("Internal driver error: data is NULL\n"); + return -EINVAL; + } + + err = ReadPacket(udev, command, buffer, 8); + if (err < 0) + return err; + + memcpy(data, buffer, databytes); + } else if(command[0] == DATA_OUT) + WritePacket(udev, command, data, databytes); + else { + DBG("Unexpected first byte of command: %x\n", command[0]); + err = -EINVAL; + } + + return 0; +} + +static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata), + void *cbdata) +{ + return -ENODEV; +} + +static int cpia_usb_streamStart(void *privdata) +{ + return -ENODEV; +} + +static int cpia_usb_streamStop(void *privdata) +{ + return -ENODEV; +} + +static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + struct framebuf *mybuff; + + if (!ucpia || !ucpia->present) + return -1; + + if (ucpia->curbuff->status != FRAME_READY) + interruptible_sleep_on(&ucpia->wq_stream); + else + DBG("Frame already waiting!\n"); + + mybuff = ucpia->curbuff; + + if (!mybuff) + return -1; + + if (mybuff->status != FRAME_READY || mybuff->length < 4) { + DBG("Something went wrong!\n"); + return -1; + } + + memcpy(frame, mybuff->data, mybuff->length); + mybuff->status = FRAME_EMPTY; + +/* DBG("read done, %d bytes, Header: %x/%x, Footer: %x%x%x%x\n", */ +/* mybuff->length, frame[0], frame[1], */ +/* frame[mybuff->length-4], frame[mybuff->length-3], */ +/* frame[mybuff->length-2], frame[mybuff->length-1]); */ + + return mybuff->length; +} + +static void cpia_usb_free_resources(struct usb_cpia *ucpia, int try) +{ + if (!ucpia->streaming) + return; + + ucpia->streaming = 0; + + /* Set packet size to 0 */ + if (try) { + int ret; + + ret = usb_set_interface(ucpia->dev, ucpia->iface, 0); + if (ret < 0) { + printk(KERN_ERR "usb_set_interface error (ret = %d)\n", ret); + return; + } + } + + /* Unschedule all of the iso td's */ + if (ucpia->sbuf[1].urb) { + usb_kill_urb(ucpia->sbuf[1].urb); + usb_free_urb(ucpia->sbuf[1].urb); + ucpia->sbuf[1].urb = NULL; + } + + if (ucpia->sbuf[1].data) { + kfree(ucpia->sbuf[1].data); + ucpia->sbuf[1].data = NULL; + } + + if (ucpia->sbuf[0].urb) { + usb_kill_urb(ucpia->sbuf[0].urb); + usb_free_urb(ucpia->sbuf[0].urb); + ucpia->sbuf[0].urb = NULL; + } + + if (ucpia->sbuf[0].data) { + kfree(ucpia->sbuf[0].data); + ucpia->sbuf[0].data = NULL; + } +} + +static int cpia_usb_close(void *privdata) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + + if(!ucpia) + return -ENODEV; + + ucpia->open = 0; + + /* ucpia->present = 0 protects against trying to reset the + * alt setting if camera is physically disconnected while open */ + cpia_usb_free_resources(ucpia, ucpia->present); + + return 0; +} + +int cpia_usb_init(void) +{ + /* return -ENODEV; */ + return 0; +} + +/* Probing and initializing */ + +static int cpia_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *interface; + struct usb_cpia *ucpia; + struct cam_data *cam; + int ret; + + /* A multi-config CPiA camera? */ + if (udev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + interface = intf->cur_altsetting; + + printk(KERN_INFO "USB CPiA camera found\n"); + + ucpia = kmalloc(sizeof(*ucpia), GFP_KERNEL); + if (!ucpia) { + printk(KERN_ERR "couldn't kmalloc cpia struct\n"); + return -ENOMEM; + } + + memset(ucpia, 0, sizeof(*ucpia)); + + ucpia->dev = udev; + ucpia->iface = interface->desc.bInterfaceNumber; + init_waitqueue_head(&ucpia->wq_stream); + + ucpia->buffers[0] = vmalloc(sizeof(*ucpia->buffers[0])); + if (!ucpia->buffers[0]) { + printk(KERN_ERR "couldn't vmalloc frame buffer 0\n"); + goto fail_alloc_0; + } + + ucpia->buffers[1] = vmalloc(sizeof(*ucpia->buffers[1])); + if (!ucpia->buffers[1]) { + printk(KERN_ERR "couldn't vmalloc frame buffer 1\n"); + goto fail_alloc_1; + } + + ucpia->buffers[2] = vmalloc(sizeof(*ucpia->buffers[2])); + if (!ucpia->buffers[2]) { + printk(KERN_ERR "couldn't vmalloc frame buffer 2\n"); + goto fail_alloc_2; + } + + ucpia->buffers[0]->next = ucpia->buffers[1]; + ucpia->buffers[1]->next = ucpia->buffers[2]; + ucpia->buffers[2]->next = ucpia->buffers[0]; + + ret = usb_set_interface(udev, ucpia->iface, 0); + if (ret < 0) { + printk(KERN_ERR "cpia_probe: usb_set_interface error (ret = %d)\n", ret); + /* goto fail_all; */ + } + + /* Before register_camera, important */ + ucpia->present = 1; + + cam = cpia_register_camera(&cpia_usb_ops, ucpia); + if (!cam) { + LOG("failed to cpia_register_camera\n"); + goto fail_all; + } + + spin_lock( &cam_list_lock_usb ); + list_add( &cam->cam_data_list, &cam_list ); + spin_unlock( &cam_list_lock_usb ); + + usb_set_intfdata(intf, cam); + return 0; + +fail_all: + vfree(ucpia->buffers[2]); + ucpia->buffers[2] = NULL; +fail_alloc_2: + vfree(ucpia->buffers[1]); + ucpia->buffers[1] = NULL; +fail_alloc_1: + vfree(ucpia->buffers[0]); + ucpia->buffers[0] = NULL; +fail_alloc_0: + kfree(ucpia); + return -EIO; +} + +static void cpia_disconnect(struct usb_interface *intf); + +static struct usb_device_id cpia_id_table [] = { + { USB_DEVICE(0x0553, 0x0002) }, + { USB_DEVICE(0x0813, 0x0001) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, cpia_id_table); +MODULE_LICENSE("GPL"); + + +static struct usb_driver cpia_driver = { + .owner = THIS_MODULE, + .name = "cpia", + .probe = cpia_probe, + .disconnect = cpia_disconnect, + .id_table = cpia_id_table, +}; + +static void cpia_disconnect(struct usb_interface *intf) +{ + struct cam_data *cam = usb_get_intfdata(intf); + struct usb_cpia *ucpia; + struct usb_device *udev; + + usb_set_intfdata(intf, NULL); + if (!cam) + return; + + ucpia = (struct usb_cpia *) cam->lowlevel_data; + spin_lock( &cam_list_lock_usb ); + list_del(&cam->cam_data_list); + spin_unlock( &cam_list_lock_usb ); + + ucpia->present = 0; + + cpia_unregister_camera(cam); + if(ucpia->open) + cpia_usb_close(cam->lowlevel_data); + + ucpia->curbuff->status = FRAME_ERROR; + + if (waitqueue_active(&ucpia->wq_stream)) + wake_up_interruptible(&ucpia->wq_stream); + + udev = interface_to_usbdev(intf); + + ucpia->curbuff = ucpia->workbuff = NULL; + + if (ucpia->buffers[2]) { + vfree(ucpia->buffers[2]); + ucpia->buffers[2] = NULL; + } + + if (ucpia->buffers[1]) { + vfree(ucpia->buffers[1]); + ucpia->buffers[1] = NULL; + } + + if (ucpia->buffers[0]) { + vfree(ucpia->buffers[0]); + ucpia->buffers[0] = NULL; + } + + cam->lowlevel_data = NULL; + kfree(ucpia); +} + +static int __init usb_cpia_init(void) +{ + printk(KERN_INFO "%s v%d.%d.%d\n",ABOUT, + CPIA_USB_MAJ_VER,CPIA_USB_MIN_VER,CPIA_USB_PATCH_VER); + + spin_lock_init(&cam_list_lock_usb); + return usb_register(&cpia_driver); +} + +static void __exit usb_cpia_cleanup(void) +{ + usb_deregister(&cpia_driver); +} + + +module_init (usb_cpia_init); +module_exit (usb_cpia_cleanup); + diff --git a/drivers/media/video/cs8420.h b/drivers/media/video/cs8420.h new file mode 100644 index 000000000000..2b22f3a38de5 --- /dev/null +++ b/drivers/media/video/cs8420.h @@ -0,0 +1,50 @@ +/* cs8420.h - cs8420 initializations + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + 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. + + */ +#ifndef __CS8420_H__ +#define __CS8420_H__ + +/* Initialization Sequence */ + +static __u8 init8420[] = { + 1, 0x01, 2, 0x02, 3, 0x00, 4, 0x46, + 5, 0x24, 6, 0x84, 18, 0x18, 19, 0x13, +}; + +#define INIT8420LEN (sizeof(init8420)/2) + +static __u8 mode8420pro[] = { /* professional output mode */ + 32, 0xa1, 33, 0x00, 34, 0x00, 35, 0x00, + 36, 0x00, 37, 0x00, 38, 0x00, 39, 0x00, + 40, 0x00, 41, 0x00, 42, 0x00, 43, 0x00, + 44, 0x00, 45, 0x00, 46, 0x00, 47, 0x00, + 48, 0x00, 49, 0x00, 50, 0x00, 51, 0x00, + 52, 0x00, 53, 0x00, 54, 0x00, 55, 0x00, +}; +#define MODE8420LEN (sizeof(mode8420pro)/2) + +static __u8 mode8420con[] = { /* consumer output mode */ + 32, 0x20, 33, 0x00, 34, 0x00, 35, 0x48, + 36, 0x00, 37, 0x00, 38, 0x00, 39, 0x00, + 40, 0x00, 41, 0x00, 42, 0x00, 43, 0x00, + 44, 0x00, 45, 0x00, 46, 0x00, 47, 0x00, + 48, 0x00, 49, 0x00, 50, 0x00, 51, 0x00, + 52, 0x00, 53, 0x00, 54, 0x00, 55, 0x00, +}; + +#endif diff --git a/drivers/media/video/cx88/Makefile b/drivers/media/video/cx88/Makefile new file mode 100644 index 000000000000..606d0348da2c --- /dev/null +++ b/drivers/media/video/cx88/Makefile @@ -0,0 +1,11 @@ +cx88xx-objs := cx88-cards.o cx88-core.o cx88-i2c.o cx88-tvaudio.o \ + cx88-input.o +cx8800-objs := cx88-video.o cx88-vbi.o +cx8802-objs := cx88-mpeg.o + +obj-$(CONFIG_VIDEO_CX88) += cx88xx.o cx8800.o cx8802.o cx88-blackbird.o +obj-$(CONFIG_VIDEO_CX88_DVB) += cx88-dvb.o + +EXTRA_CFLAGS += -I$(src)/.. +EXTRA_CFLAGS += -I$(srctree)/drivers/media/dvb/dvb-core +EXTRA_CFLAGS += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/video/cx88/cx88-blackbird.c b/drivers/media/video/cx88/cx88-blackbird.c new file mode 100644 index 000000000000..46d6778b863b --- /dev/null +++ b/drivers/media/video/cx88/cx88-blackbird.c @@ -0,0 +1,911 @@ +/* + * $Id: cx88-blackbird.c,v 1.26 2005/03/07 15:58:05 kraxel Exp $ + * + * Support for a cx23416 mpeg encoder via cx2388x host port. + * "blackbird" reference design. + * + * (c) 2004 Jelle Foks <jelle@foks.8m.com> + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> + * + * Includes parts from the ivtv driver( http://ivtv.sourceforge.net/), + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firmware.h> + +#include "cx88.h" + +MODULE_DESCRIPTION("driver for cx2388x/cx23416 based mpeg encoder cards"); +MODULE_AUTHOR("Jelle Foks <jelle@foks.8m.com>"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int mpegbufs = 8; +module_param(mpegbufs,int,0644); +MODULE_PARM_DESC(mpegbufs,"number of mpeg buffers, range 2-32"); + +static unsigned int debug = 0; +module_param(debug,int,0644); +MODULE_PARM_DESC(debug,"enable debug messages [blackbird]"); + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-bb: " fmt, dev->core->name , ## arg) + +static LIST_HEAD(cx8802_devlist); + +/* ------------------------------------------------------------------ */ + +#define BLACKBIRD_FIRM_ENC_FILENAME "blackbird-fw-enc.bin" +#define BLACKBIRD_FIRM_IMAGE_SIZE 256*1024 + +/* defines below are from ivtv-driver.h */ + +#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF + +/*Firmware API commands*/ +#define IVTV_API_ENC_PING_FW 0x00000080 +#define IVTV_API_ENC_GETVER 0x000000C4 +#define IVTV_API_ENC_HALT_FW 0x000000C3 +#define IVTV_API_STD_TIMEOUT 0x00010000 /*units??*/ +//#define IVTV_API_ASSIGN_PGM_INDEX_INFO 0x000000c7 +#define IVTV_API_ASSIGN_STREAM_TYPE 0x000000b9 +#define IVTV_API_ASSIGN_OUTPUT_PORT 0x000000bb +#define IVTV_API_ASSIGN_FRAMERATE 0x0000008f +#define IVTV_API_ASSIGN_FRAME_SIZE 0x00000091 +#define IVTV_API_ASSIGN_ASPECT_RATIO 0x00000099 +#define IVTV_API_ASSIGN_BITRATES 0x00000095 +#define IVTV_API_ASSIGN_GOP_PROPERTIES 0x00000097 +#define IVTV_API_ASSIGN_3_2_PULLDOWN 0x000000b1 +#define IVTV_API_ASSIGN_GOP_CLOSURE 0x000000c5 +#define IVTV_API_ASSIGN_AUDIO_PROPERTIES 0x000000bd +#define IVTV_API_ASSIGN_DNR_FILTER_MODE 0x0000009b +#define IVTV_API_ASSIGN_DNR_FILTER_PROPS 0x0000009d +#define IVTV_API_ASSIGN_CORING_LEVELS 0x0000009f +#define IVTV_API_ASSIGN_SPATIAL_FILTER_TYPE 0x000000a1 +#define IVTV_API_ASSIGN_FRAME_DROP_RATE 0x000000d0 +#define IVTV_API_ASSIGN_PLACEHOLDER 0x000000d8 +#define IVTV_API_MUTE_VIDEO 0x000000d9 +#define IVTV_API_MUTE_AUDIO 0x000000da +#define IVTV_API_INITIALIZE_INPUT 0x000000cd +#define IVTV_API_REFRESH_INPUT 0x000000d3 +#define IVTV_API_ASSIGN_NUM_VSYNC_LINES 0x000000d6 +#define IVTV_API_BEGIN_CAPTURE 0x00000081 +//#define IVTV_API_PAUSE_ENCODER 0x000000d2 +//#define IVTV_API_EVENT_NOTIFICATION 0x000000d5 +#define IVTV_API_END_CAPTURE 0x00000082 + +/* Registers */ +#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_SPU (0x9050 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_HW_BLOCKS (0x9054 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_VPU (0x9058 /*| IVTV_REG_OFFSET*/) +#define IVTV_REG_APU (0xA064 /*| IVTV_REG_OFFSET*/) + +/* ------------------------------------------------------------------ */ + +static void host_setup(struct cx88_core *core) +{ + /* toggle reset of the host */ + cx_write(MO_GPHST_SOFT_RST, 1); + udelay(100); + cx_write(MO_GPHST_SOFT_RST, 0); + udelay(100); + + /* host port setup */ + cx_write(MO_GPHST_WSC, 0x44444444U); + cx_write(MO_GPHST_XFR, 0); + cx_write(MO_GPHST_WDTH, 15); + cx_write(MO_GPHST_HDSHK, 0); + cx_write(MO_GPHST_MUX16, 0x44448888U); + cx_write(MO_GPHST_MODE, 0); +} + +/* ------------------------------------------------------------------ */ + +#define P1_MDATA0 0x390000 +#define P1_MDATA1 0x390001 +#define P1_MDATA2 0x390002 +#define P1_MDATA3 0x390003 +#define P1_MADDR2 0x390004 +#define P1_MADDR1 0x390005 +#define P1_MADDR0 0x390006 +#define P1_RDATA0 0x390008 +#define P1_RDATA1 0x390009 +#define P1_RDATA2 0x39000A +#define P1_RDATA3 0x39000B +#define P1_RADDR0 0x39000C +#define P1_RADDR1 0x39000D +#define P1_RRDWR 0x39000E + +static int wait_ready_gpio0_bit1(struct cx88_core *core, u32 state) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1); + u32 gpio0,need; + + need = state ? 2 : 0; + for (;;) { + gpio0 = cx_read(MO_GP0_IO) & 2; + if (need == gpio0) + return 0; + if (time_after(jiffies,timeout)) + return -1; + udelay(1); + } +} + +static int memory_write(struct cx88_core *core, u32 address, u32 value) +{ + /* Warning: address is dword address (4 bytes) */ + cx_writeb(P1_MDATA0, (unsigned int)value); + cx_writeb(P1_MDATA1, (unsigned int)(value >> 8)); + cx_writeb(P1_MDATA2, (unsigned int)(value >> 16)); + cx_writeb(P1_MDATA3, (unsigned int)(value >> 24)); + cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) | 0x40); + cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_MADDR0, (unsigned int)address); + cx_read(P1_MDATA0); + cx_read(P1_MADDR0); + + return wait_ready_gpio0_bit1(core,1); +} + +static int memory_read(struct cx88_core *core, u32 address, u32 *value) +{ + int retval; + u32 val; + + /* Warning: address is dword address (4 bytes) */ + cx_writeb(P1_MADDR2, (unsigned int)(address >> 16) & ~0xC0); + cx_writeb(P1_MADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_MADDR0, (unsigned int)address); + cx_read(P1_MADDR0); + + retval = wait_ready_gpio0_bit1(core,1); + + cx_writeb(P1_MDATA3, 0); + val = (unsigned char)cx_read(P1_MDATA3) << 24; + cx_writeb(P1_MDATA2, 0); + val |= (unsigned char)cx_read(P1_MDATA2) << 16; + cx_writeb(P1_MDATA1, 0); + val |= (unsigned char)cx_read(P1_MDATA1) << 8; + cx_writeb(P1_MDATA0, 0); + val |= (unsigned char)cx_read(P1_MDATA0); + + *value = val; + return retval; +} + +static int register_write(struct cx88_core *core, u32 address, u32 value) +{ + cx_writeb(P1_RDATA0, (unsigned int)value); + cx_writeb(P1_RDATA1, (unsigned int)(value >> 8)); + cx_writeb(P1_RDATA2, (unsigned int)(value >> 16)); + cx_writeb(P1_RDATA3, (unsigned int)(value >> 24)); + cx_writeb(P1_RADDR0, (unsigned int)address); + cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_RRDWR, 1); + cx_read(P1_RDATA0); + cx_read(P1_RADDR0); + + return wait_ready_gpio0_bit1(core,1); +} + + +static int register_read(struct cx88_core *core, u32 address, u32 *value) +{ + int retval; + u32 val; + + cx_writeb(P1_RADDR0, (unsigned int)address); + cx_writeb(P1_RADDR1, (unsigned int)(address >> 8)); + cx_writeb(P1_RRDWR, 0); + cx_read(P1_RADDR0); + + retval = wait_ready_gpio0_bit1(core,1); + val = (unsigned char)cx_read(P1_RDATA0); + val |= (unsigned char)cx_read(P1_RDATA1) << 8; + val |= (unsigned char)cx_read(P1_RDATA2) << 16; + val |= (unsigned char)cx_read(P1_RDATA3) << 24; + + *value = val; + return retval; +} + +/* ------------------------------------------------------------------ */ + +/* We don't need to call the API often, so using just one mailbox will probably suffice */ +static int blackbird_api_cmd(struct cx8802_dev *dev, u32 command, + u32 inputcnt, u32 outputcnt, ...) +{ + unsigned long timeout; + u32 value, flag, retval; + int i; + va_list args; + va_start(args, outputcnt); + + dprintk(1,"%s: 0x%X\n", __FUNCTION__, command); + + /* this may not be 100% safe if we can't read any memory location + without side effects */ + memory_read(dev->core, dev->mailbox - 4, &value); + if (value != 0x12345678) { + dprintk(0, "Firmware and/or mailbox pointer not initialized or corrupted\n"); + return -1; + } + + memory_read(dev->core, dev->mailbox, &flag); + if (flag) { + dprintk(0, "ERROR: Mailbox appears to be in use (%x)\n", flag); + return -1; + } + + flag |= 1; /* tell 'em we're working on it */ + memory_write(dev->core, dev->mailbox, flag); + + /* write command + args + fill remaining with zeros */ + memory_write(dev->core, dev->mailbox + 1, command); /* command code */ + memory_write(dev->core, dev->mailbox + 3, IVTV_API_STD_TIMEOUT); /* timeout */ + for (i = 0; i < inputcnt ; i++) { + value = va_arg(args, int); + memory_write(dev->core, dev->mailbox + 4 + i, value); + dprintk(1, "API Input %d = %d\n", i, value); + } + for (; i < 16 ; i++) + memory_write(dev->core, dev->mailbox + 4 + i, 0); + + flag |= 3; /* tell 'em we're done writing */ + memory_write(dev->core, dev->mailbox, flag); + + /* wait for firmware to handle the API command */ + timeout = jiffies + msecs_to_jiffies(10); + for (;;) { + memory_read(dev->core, dev->mailbox, &flag); + if (0 != (flag & 4)) + break; + if (time_after(jiffies,timeout)) { + dprintk(0, "ERROR: API Mailbox timeout\n"); + return -1; + } + udelay(10); + } + + /* read output values */ + for (i = 0; i < outputcnt ; i++) { + int *vptr = va_arg(args, int *); + memory_read(dev->core, dev->mailbox + 4 + i, vptr); + dprintk(1, "API Output %d = %d\n", i, *vptr); + } + va_end(args); + + memory_read(dev->core, dev->mailbox + 2, &retval); + dprintk(1, "API result = %d\n",retval); + + flag = 0; + memory_write(dev->core, dev->mailbox, flag); + return retval; +} + + +static int blackbird_find_mailbox(struct cx8802_dev *dev) +{ + u32 signature[4]={0x12345678, 0x34567812, 0x56781234, 0x78123456}; + int signaturecnt=0; + u32 value; + int i; + + for (i = 0; i < BLACKBIRD_FIRM_IMAGE_SIZE; i++) { + memory_read(dev->core, i, &value); + if (value == signature[signaturecnt]) + signaturecnt++; + else + signaturecnt = 0; + if (4 == signaturecnt) { + dprintk(1, "Mailbox signature found\n"); + return i+1; + } + } + dprintk(0, "Mailbox signature values not found!\n"); + return -1; +} + +static int blackbird_load_firmware(struct cx8802_dev *dev) +{ + static const unsigned char magic[8] = { + 0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa + }; + const struct firmware *firmware; + int i, retval = 0; + u32 value = 0; + u32 checksum = 0; + u32 *dataptr; + + retval = register_write(dev->core, IVTV_REG_VPU, 0xFFFFFFED); + retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST); + retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_REFRESH, 0x80000640); + retval |= register_write(dev->core, IVTV_REG_ENC_SDRAM_PRECHARGE, 0x1A); + msleep(1); + retval |= register_write(dev->core, IVTV_REG_APU, 0); + + if (retval < 0) + dprintk(0, "Error with register_write\n"); + + retval = request_firmware(&firmware, BLACKBIRD_FIRM_ENC_FILENAME, + &dev->pci->dev); + if (retval != 0) { + dprintk(0, "ERROR: Hotplug firmware request failed (%s).\n", + BLACKBIRD_FIRM_ENC_FILENAME); + dprintk(0, "Please fix your hotplug setup, the board will " + "not work without firmware loaded!\n"); + return -1; + } + + if (firmware->size != BLACKBIRD_FIRM_IMAGE_SIZE) { + dprintk(0, "ERROR: Firmware size mismatch (have %zd, expected %d)\n", + firmware->size, BLACKBIRD_FIRM_IMAGE_SIZE); + return -1; + } + + if (0 != memcmp(firmware->data, magic, 8)) { + dprintk(0, "ERROR: Firmware magic mismatch, wrong file?\n"); + return -1; + } + + /* transfer to the chip */ + dprintk(1,"Loading firmware ...\n"); + dataptr = (u32*)firmware->data; + for (i = 0; i < (firmware->size >> 2); i++) { + value = *dataptr; + checksum += ~value; + memory_write(dev->core, i, value); + dataptr++; + } + + /* read back to verify with the checksum */ + for (i--; i >= 0; i--) { + memory_read(dev->core, i, &value); + checksum -= ~value; + } + if (checksum) { + dprintk(0, "ERROR: Firmware load failed (checksum mismatch).\n"); + return -1; + } + release_firmware(firmware); + dprintk(0, "Firmware upload successful.\n"); + + retval |= register_write(dev->core, IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST); + retval |= register_read(dev->core, IVTV_REG_SPU, &value); + retval |= register_write(dev->core, IVTV_REG_SPU, value & 0xFFFFFFFE); + msleep(1); + + retval |= register_read(dev->core, IVTV_REG_VPU, &value); + retval |= register_write(dev->core, IVTV_REG_VPU, value & 0xFFFFFFE8); + + if (retval < 0) + dprintk(0, "Error with register_write\n"); + return 0; +} + +static void blackbird_codec_settings(struct cx8802_dev *dev) +{ + int bitrate_mode = 1; + int bitrate = 7500000; + int bitrate_peak = 7500000; + + /* assign stream type */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_STREAM_TYPE, 1, 0, 0); /* program stream */ + //blackbird_api_cmd(dev, IVTV_API_ASSIGN_STREAM_TYPE, 1, 0, 2); /* MPEG1 stream */ + //blackbird_api_cmd(dev, IVTV_API_ASSIGN_STREAM_TYPE, 1, 0, 3); /* PES A/V */ + //blackbird_api_cmd(dev, IVTV_API_ASSIGN_STREAM_TYPE, 1, 0, 10); /* DVD stream */ + + /* assign output port */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_OUTPUT_PORT, 1, 0, 1); /* 1 = Host */ + + /* assign framerate */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_FRAMERATE, 1, 0, 0); + + /* assign frame size */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_FRAME_SIZE, 2, 0, + dev->height, dev->width); + + /* assign aspect ratio */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_ASPECT_RATIO, 1, 0, 2); + + /* assign bitrates */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_BITRATES, 5, 0, + bitrate_mode, /* mode */ + bitrate, /* bps */ + bitrate_peak / 400, /* peak/400 */ + 0, 0x70); /* encoding buffer, ckennedy */ + + /* assign gop properties */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_GOP_PROPERTIES, 2, 0, 15, 3); + //blackbird_api_cmd(dev, IVTV_API_ASSIGN_GOP_PROPERTIES, 2, 0, 2, 1); + + /* assign 3 2 pulldown */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_3_2_PULLDOWN, 1, 0, 0); + + /* note: it's not necessary to set the samplerate, the mpeg encoder seems to autodetect/adjust */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_AUDIO_PROPERTIES, 1, 0, (2<<2) | (8<<4)); + + /* assign gop closure */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_GOP_CLOSURE, 1, 0, 0); + + /* assign audio properties */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_AUDIO_PROPERTIES, 1, 0, 0 | (2 << 2) | (14 << 4)); + + /* assign dnr filter mode */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_DNR_FILTER_MODE, 2, 0, 0, 0); + + /* assign dnr filter props*/ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_DNR_FILTER_PROPS, 2, 0, 0, 0); + + /* assign coring levels (luma_h, luma_l, chroma_h, chroma_l) */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_CORING_LEVELS, 4, 0, 0, 255, 0, 255); + + /* assign spatial filter type: luma_t: 1 = horiz_only, chroma_t: 1 = horiz_only */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_SPATIAL_FILTER_TYPE, 2, 0, 1, 1); + + /* assign frame drop rate */ + blackbird_api_cmd(dev, IVTV_API_ASSIGN_FRAME_DROP_RATE, 1, 0, 0); +} + +static int blackbird_initialize_codec(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + int version; + int retval; + + dprintk(1,"Initialize codec\n"); + retval = blackbird_api_cmd(dev, IVTV_API_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + /* ping was not successful, reset and upload firmware */ + cx_write(MO_SRST_IO, 0); /* SYS_RSTO=0 */ + msleep(1); + cx_write(MO_SRST_IO, 1); /* SYS_RSTO=1 */ + msleep(1); + retval = blackbird_load_firmware(dev); + if (retval < 0) + return retval; + + dev->mailbox = blackbird_find_mailbox(dev); + if (dev->mailbox < 0) + return -1; + + retval = blackbird_api_cmd(dev, IVTV_API_ENC_PING_FW, 0, 0); /* ping */ + if (retval < 0) { + dprintk(0, "ERROR: Firmware ping failed!\n"); + return -1; + } + + retval = blackbird_api_cmd(dev, IVTV_API_ENC_GETVER, 0, 1, &version); + if (retval < 0) { + dprintk(0, "ERROR: Firmware get encoder version failed!\n"); + return -1; + } + dprintk(0, "Firmware version is 0x%08x\n", version); + } + msleep(1); + + cx_write(MO_PINMUX_IO, 0x88); /* 656-8bit IO and enable MPEG parallel IO */ + cx_clear(MO_INPUT_FORMAT, 0x100); /* chroma subcarrier lock to normal? */ + cx_write(MO_VBOS_CONTROL, 0x84A00); /* no 656 mode, 8-bit pixels, disable VBI */ + cx_clear(MO_OUTPUT_FORMAT, 0x0008); /* Normal Y-limits to let the mpeg encoder sync */ + +#if 0 /* FIXME */ + set_scale(dev, 720, 480, V4L2_FIELD_INTERLACED); +#endif + blackbird_codec_settings(dev); + msleep(1); + + //blackbird_api_cmd(dev, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 4, 0, 0xef, 0xef); + blackbird_api_cmd(dev, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 4, 0, 0xf0, 0xf0); + //blackbird_api_cmd(dev, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 4, 0, 0x180, 0x180); + blackbird_api_cmd(dev, IVTV_API_ASSIGN_PLACEHOLDER, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + blackbird_api_cmd(dev, IVTV_API_INITIALIZE_INPUT, 0, 0); /* initialize the video input */ + + msleep(1); + + blackbird_api_cmd(dev, IVTV_API_MUTE_VIDEO, 1, 0, 0); + msleep(1); + blackbird_api_cmd(dev, IVTV_API_MUTE_AUDIO, 1, 0, 0); + msleep(1); + + blackbird_api_cmd(dev, IVTV_API_BEGIN_CAPTURE, 2, 0, 0, 0x13); /* start capturing to the host interface */ + //blackbird_api_cmd(dev, IVTV_API_BEGIN_CAPTURE, 2, 0, 0, 0); /* start capturing to the host interface */ + msleep(1); + + blackbird_api_cmd(dev, IVTV_API_REFRESH_INPUT, 0,0); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int bb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx8802_fh *fh = q->priv_data; + + fh->dev->ts_packet_size = 512; + fh->dev->ts_packet_count = 100; + + *size = fh->dev->ts_packet_size * fh->dev->ts_packet_count; + if (0 == *count) + *count = mpegbufs; + if (*count < 2) + *count = 2; + if (*count > 32) + *count = 32; + return 0; +} + +static int +bb_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8802_fh *fh = q->priv_data; + return cx8802_buf_prepare(fh->dev, (struct cx88_buffer*)vb); +} + +static void +bb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_fh *fh = q->priv_data; + cx8802_buf_queue(fh->dev, (struct cx88_buffer*)vb); +} + +static void +bb_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_fh *fh = q->priv_data; + cx88_free_buffer(fh->dev->pci, (struct cx88_buffer*)vb); +} + +static struct videobuf_queue_ops blackbird_qops = { + .buf_setup = bb_buf_setup, + .buf_prepare = bb_buf_prepare, + .buf_queue = bb_buf_queue, + .buf_release = bb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +static int mpeg_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct cx8802_fh *fh = file->private_data; + struct cx8802_dev *dev = fh->dev; + + if (debug > 1) + cx88_print_ioctl(dev->core->name,cmd); + + switch (cmd) { + + /* --- capture ioctls ---------------------------------------- */ + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + int index; + + index = f->index; + if (index != 0) + return -EINVAL; + + memset(f,0,sizeof(*f)); + f->index = index; + strlcpy(f->description, "MPEG TS", sizeof(f->description)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->pixelformat = V4L2_PIX_FMT_MPEG; + return 0; + } + case VIDIOC_G_FMT: + case VIDIOC_S_FMT: + case VIDIOC_TRY_FMT: + { + /* FIXME -- quick'n'dirty for exactly one size ... */ + struct v4l2_format *f = arg; + + memset(f,0,sizeof(*f)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = 1024 * 512 /* FIXME: BUFFER_SIZE */; + } + + /* --- streaming capture ------------------------------------- */ + case VIDIOC_REQBUFS: + return videobuf_reqbufs(&fh->mpegq, arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(&fh->mpegq, arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(&fh->mpegq, arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(&fh->mpegq, arg, + file->f_flags & O_NONBLOCK); + + case VIDIOC_STREAMON: + return videobuf_streamon(&fh->mpegq); + + case VIDIOC_STREAMOFF: + return videobuf_streamoff(&fh->mpegq); + + default: + return -EINVAL; + } + return 0; +} + +static int mpeg_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mpeg_do_ioctl); +} + +static int mpeg_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct cx8802_dev *h,*dev = NULL; + struct cx8802_fh *fh; + struct list_head *list; + + list_for_each(list,&cx8802_devlist) { + h = list_entry(list, struct cx8802_dev, devlist); + if (h->mpeg_dev->minor == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + if (blackbird_initialize_codec(dev) < 0) + return -EINVAL; + dprintk(1,"open minor=%d\n",minor); + + /* allocate + initialize per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + memset(fh,0,sizeof(*fh)); + file->private_data = fh; + fh->dev = dev; + + /* FIXME: locking against other video device */ + cx88_set_scale(dev->core, dev->width, dev->height, + V4L2_FIELD_INTERLACED); + + videobuf_queue_init(&fh->mpegq, &blackbird_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_TOP, + sizeof(struct cx88_buffer), + fh); + return 0; +} + +static int mpeg_release(struct inode *inode, struct file *file) +{ + struct cx8802_fh *fh = file->private_data; + + blackbird_api_cmd(fh->dev, IVTV_API_END_CAPTURE, 3, 0, 1, 0, 0x13); + + /* stop mpeg capture */ + if (fh->mpegq.streaming) + videobuf_streamoff(&fh->mpegq); + if (fh->mpegq.reading) + videobuf_read_stop(&fh->mpegq); + + videobuf_mmap_free(&fh->mpegq); + file->private_data = NULL; + kfree(fh); + return 0; +} + +static ssize_t +mpeg_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct cx8802_fh *fh = file->private_data; + + return videobuf_read_stream(&fh->mpegq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static unsigned int +mpeg_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cx8802_fh *fh = file->private_data; + + return videobuf_poll_stream(file, &fh->mpegq, wait); +} + +static int +mpeg_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct cx8802_fh *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->mpegq, vma); +} + +static struct file_operations mpeg_fops = +{ + .owner = THIS_MODULE, + .open = mpeg_open, + .release = mpeg_release, + .read = mpeg_read, + .poll = mpeg_poll, + .mmap = mpeg_mmap, + .ioctl = mpeg_ioctl, + .llseek = no_llseek, +}; + +static struct video_device cx8802_mpeg_template = +{ + .name = "cx8802", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_SCALES|VID_TYPE_MPEG_ENCODER, + .hardware = 0, + .fops = &mpeg_fops, + .minor = -1, +}; + +/* ------------------------------------------------------------------ */ + +static void blackbird_unregister_video(struct cx8802_dev *dev) +{ + if (dev->mpeg_dev) { + if (-1 != dev->mpeg_dev->minor) + video_unregister_device(dev->mpeg_dev); + else + video_device_release(dev->mpeg_dev); + dev->mpeg_dev = NULL; + } +} + +static int blackbird_register_video(struct cx8802_dev *dev) +{ + int err; + + dev->mpeg_dev = cx88_vdev_init(dev->core,dev->pci, + &cx8802_mpeg_template,"mpeg"); + err = video_register_device(dev->mpeg_dev,VFL_TYPE_GRABBER, -1); + if (err < 0) { + printk(KERN_INFO "%s/2: can't register mpeg device\n", + dev->core->name); + return err; + } + printk(KERN_INFO "%s/2: registered device video%d [mpeg]\n", + dev->core->name,dev->mpeg_dev->minor & 0x1f); + return 0; +} + +/* ----------------------------------------------------------- */ + +static int __devinit blackbird_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8802_dev *dev; + struct cx88_core *core; + int err; + + /* general setup */ + core = cx88_core_get(pci_dev); + if (NULL == core) + return -EINVAL; + + err = -ENODEV; + if (!cx88_boards[core->board].blackbird) + goto fail_core; + + err = -ENOMEM; + dev = kmalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + goto fail_core; + memset(dev,0,sizeof(*dev)); + dev->pci = pci_dev; + dev->core = core; + dev->width = 720; + dev->height = 480; + + err = cx8802_init_common(dev); + if (0 != err) + goto fail_free; + + /* blackbird stuff */ + printk("%s/2: cx23416 based mpeg encoder (blackbird reference design)\n", + core->name); + host_setup(dev->core); + + list_add_tail(&dev->devlist,&cx8802_devlist); + blackbird_register_video(dev); + return 0; + + fail_free: + kfree(dev); + fail_core: + cx88_core_put(core,pci_dev); + return err; +} + +static void __devexit blackbird_remove(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + + /* blackbird */ + blackbird_unregister_video(dev); + list_del(&dev->devlist); + + /* common */ + cx8802_fini_common(dev); + cx88_core_put(dev->core,dev->pci); + kfree(dev); +} + +static struct pci_device_id cx8802_pci_tbl[] = { + { + .vendor = 0x14f1, + .device = 0x8802, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8802_pci_tbl); + +static struct pci_driver blackbird_pci_driver = { + .name = "cx88-blackbird", + .id_table = cx8802_pci_tbl, + .probe = blackbird_probe, + .remove = __devexit_p(blackbird_remove), + .suspend = cx8802_suspend_common, + .resume = cx8802_resume_common, +}; + +static int blackbird_init(void) +{ + printk(KERN_INFO "cx2388x blackbird driver version %d.%d.%d loaded\n", + (CX88_VERSION_CODE >> 16) & 0xff, + (CX88_VERSION_CODE >> 8) & 0xff, + CX88_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "cx2388x: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_register_driver(&blackbird_pci_driver); +} + +static void blackbird_fini(void) +{ + pci_unregister_driver(&blackbird_pci_driver); +} + +module_init(blackbird_init); +module_exit(blackbird_fini); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-cards.c b/drivers/media/video/cx88/cx88-cards.c new file mode 100644 index 000000000000..367624822d77 --- /dev/null +++ b/drivers/media/video/cx88/cx88-cards.c @@ -0,0 +1,938 @@ +/* + * $Id: cx88-cards.c,v 1.66 2005/03/04 09:12:23 kraxel Exp $ + * + * device driver for Conexant 2388x based TV cards + * card-specific stuff. + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include "cx88.h" + +/* ------------------------------------------------------------------ */ +/* board config info */ + +struct cx88_board cx88_boards[] = { + [CX88_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + },{ + .type = CX88_VMUX_COMPOSITE3, + .vmux = 2, + },{ + .type = CX88_VMUX_COMPOSITE4, + .vmux = 3, + }}, + }, + [CX88_BOARD_HAUPPAUGE] = { + .name = "Hauppauge WinTV 34xxx models", + .tuner_type = UNSET, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, // internal decoder + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, // mono from tuner chip + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xff02, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff01, + }, + }, + [CX88_BOARD_GDI] = { + .name = "GDI Black Gold", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }}, + }, + [CX88_BOARD_PIXELVIEW] = { + .name = "PixelView", + .tuner_type = 5, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, // internal decoder + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff10, + }, + }, + [CX88_BOARD_ATI_WONDER_PRO] = { + .name = "ATI TV Wonder Pro", + .tuner_type = 44, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x03ff, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x03fe, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x03fe, + }}, + }, + [CX88_BOARD_WINFAST2000XP_EXPERT] = { + .name = "Leadtek Winfast 2000XP Expert", + .tuner_type = 44, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00F5e700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5e700, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00F5c700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5c700, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00F5c700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5c700, + .gpio3 = 0x02000000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x00F5d700, + .gpio1 = 0x00003004, + .gpio2 = 0x00F5d700, + .gpio3 = 0x02000000, + }, + }, + [CX88_BOARD_AVERTV_303] = { + .name = "AverTV Studio 303 (M126)", + .tuner_type = 38, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio1 = 0x309f, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio1 = 0x305f, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio1 = 0x305f, + }}, + .radio = { + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_MSI_TVANYWHERE_MASTER] = { + // added gpio values thanks to Michal + // values for PAL from DScaler + .name = "MSI TV-@nywhere Master", + .tuner_type = 33, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000040bf, + .gpio1 = 0x000080c0, + .gpio2 = 0x0000ff40, + }}, + .radio = { + .type = CX88_RADIO, + }, + }, + [CX88_BOARD_WINFAST_DV2000] = { + .name = "Leadtek Winfast DV2000", + .tuner_type = 38, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0035e700, + .gpio1 = 0x00003004, + .gpio2 = 0x0035e700, + .gpio3 = 0x02000000, + },{ + + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0035c700, + .gpio1 = 0x00003004, + .gpio2 = 0x0035c700, + .gpio3 = 0x02000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0035c700, + .gpio1 = 0x0035c700, + .gpio2 = 0x02000000, + .gpio3 = 0x02000000, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0035d700, + .gpio1 = 0x00007004, + .gpio2 = 0x0035d700, + .gpio3 = 0x02000000, + }, + }, + [CX88_BOARD_LEADTEK_PVR2000] = { + // gpio values for PAL version from regspy by DScaler + .name = "Leadtek PVR 2000", + .tuner_type = 38, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000bde6, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000bde6, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000bde6, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000bd62, + }, + .blackbird = 1, + }, + [CX88_BOARD_IODATA_GVVCP3PCI] = { + .name = "IODATA GV-VCP3/PCI", + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 0, + },{ + .type = CX88_VMUX_COMPOSITE2, + .vmux = 1, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + }}, + }, + [CX88_BOARD_PROLINK_PLAYTVPVR] = { + .name = "Prolink PlayTV PVR", + .tuner_type = 43, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xff00, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff03, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xff03, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xff00, + }, + }, + [CX88_BOARD_ASUS_PVR_416] = { + .name = "ASUS PVR-416", + .tuner_type = 43, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000fde6, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000fde6, // 0x0000fda6 L,R RCA audio in? + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000fde2, + }, + .blackbird = 1, + }, + [CX88_BOARD_MSI_TVANYWHERE] = { + .name = "MSI TV-@nywhere", + .tuner_type = 33, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc08, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc68, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000fbf, + .gpio2 = 0x0000fc68, + }}, + }, + [CX88_BOARD_KWORLD_DVB_T] = { + .name = "KWorld/VStream XPert DVB-T", + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0700, + .gpio2 = 0x0101, + }}, + .dvb = 1, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1] = { + .name = "DVICO FusionHDTV DVB-T1", + .tuner_type = TUNER_ABSENT, /* No analog tuner */ + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, + }}, + .dvb = 1, + }, + [CX88_BOARD_KWORLD_LTV883] = { + .name = "KWorld LTV883RF", + .tuner_type = 48, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x07f8, + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0x07f9, // mono from tuner chip + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000007fa, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000007fa, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x000007f8, + }, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD] = { + .name = "DViCO - FusionHDTV 3 Gold", + .tuner_type = TUNER_MICROTUNE_4042FI5, + /* + GPIO[0] resets DT3302 DTV receiver + 0 - reset asserted + 1 - normal operation + GPIO[1] mutes analog audio output connector + 0 - enable selected source + 1 - mute + GPIO[2] selects source for analog audio output connector + 0 - analog audio input connector on tab + 1 - analog DAC output from CX23881 chip + GPIO[3] selects RF input connector on tuner module + 0 - RF connector labeled CABLE + 1 - RF connector labeled ANT + */ + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0f0d, + },{ + .type = CX88_VMUX_CABLE, + .vmux = 0, + .gpio0 = 0x0f05, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0f00, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0f00, + }}, +#if 0 + .ts = { + .type = CX88_TS, + .gpio0 = 0x00000f01, /* Hooked to tuner reset bit */ + } +#endif + }, + [CX88_BOARD_HAUPPAUGE_DVB_T1] = { + .name = "Hauppauge Nova-T DVB-T", + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + }}, + .dvb = 1, + }, + [CX88_BOARD_CONEXANT_DVB_T1] = { + .name = "Conexant DVB-T reference design", + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + }}, + .dvb = 1, + }, + [CX88_BOARD_PROVIDEO_PV259] = { + .name = "Provideo PV259", + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + }}, + .blackbird = 1, + }, + [CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS] = { + .name = "DVICO FusionHDTV DVB-T Plus", + .tuner_type = TUNER_ABSENT, /* No analog tuner */ + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x000027df, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x000027df, + }}, + .dvb = 1, + }, + [CX88_BOARD_DNTV_LIVE_DVB_T] = { + .name = "digitalnow DNTV Live! DVB-T", + .tuner_type = TUNER_ABSENT, + .input = {{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00000700, + .gpio2 = 0x00000101, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00000700, + .gpio2 = 0x00000101, + }}, + .dvb = 1, + }, + [CX88_BOARD_PCHDTV_HD3000] = { + .name = "pcHDTV HD3000 HDTV", + .tuner_type = TUNER_THOMSON_DTT7610, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x00008484, + .gpio1 = 0x00000000, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x00008400, + .gpio1 = 0x00000000, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x00008400, + .gpio1 = 0x00000000, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }}, + .radio = { + .type = CX88_RADIO, + .vmux = 2, + .gpio0 = 0x00008400, + .gpio1 = 0x00000000, + .gpio2 = 0x00000000, + .gpio3 = 0x00000000, + }, + .dvb = 1, + }, + [CX88_BOARD_HAUPPAUGE_ROSLYN] = { + // entry added by Kaustubh D. Bhalerao <bhalerao.1@osu.edu> + // GPIO values obtained from regspy, courtesy Sean Covel + .name = "Hauppauge WinTV 28xxx (Roslyn) models", + .tuner_type = UNSET, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0xed12, // internal decoder + .gpio2 = 0x00ff, + },{ + .type = CX88_VMUX_DEBUG, + .vmux = 0, + .gpio0 = 0xff01, // mono from tuner chip + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0xff02, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0xed92, + .gpio2 = 0x00ff, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0xed96, + .gpio2 = 0x00ff, + }, + .blackbird = 1, + }, + [CX88_BOARD_DIGITALLOGIC_MEC] = { + /* params copied over from Leadtek PVR 2000 */ + .name = "Digital-Logic MICROSPACE Entertainment Center (MEC)", + /* not sure yet about the tuner type */ + .tuner_type = 38, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 0, + .gpio0 = 0x0000bde6, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 1, + .gpio0 = 0x0000bde6, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 2, + .gpio0 = 0x0000bde6, + }}, + .radio = { + .type = CX88_RADIO, + .gpio0 = 0x0000bd62, + }, + .blackbird = 1, + }, + [CX88_BOARD_IODATA_GVBCTV7E] = { + .name = "IODATA GV/BCTV7E", + .tuner_type = TUNER_PHILIPS_FQ1286, + .tda9887_conf = TDA9887_PRESENT, + .input = {{ + .type = CX88_VMUX_TELEVISION, + .vmux = 1, + .gpio1 = 0x0000e03f, + },{ + .type = CX88_VMUX_COMPOSITE1, + .vmux = 2, + .gpio1 = 0x0000e07f, + },{ + .type = CX88_VMUX_SVIDEO, + .vmux = 3, + .gpio1 = 0x0000e07f, + }} + }, +}; +const unsigned int cx88_bcount = ARRAY_SIZE(cx88_boards); + +/* ------------------------------------------------------------------ */ +/* PCI subsystem IDs */ + +struct cx88_subid cx88_subids[] = { + { + .subvendor = 0x0070, + .subdevice = 0x3400, + .card = CX88_BOARD_HAUPPAUGE, + },{ + .subvendor = 0x0070, + .subdevice = 0x3401, + .card = CX88_BOARD_HAUPPAUGE, + },{ + .subvendor = 0x14c7, + .subdevice = 0x0106, + .card = CX88_BOARD_GDI, + },{ + .subvendor = 0x14c7, + .subdevice = 0x0107, /* with mpeg encoder */ + .card = CX88_BOARD_GDI, + },{ + .subvendor = PCI_VENDOR_ID_ATI, + .subdevice = 0x00f8, + .card = CX88_BOARD_ATI_WONDER_PRO, + },{ + .subvendor = 0x107d, + .subdevice = 0x6611, + .card = CX88_BOARD_WINFAST2000XP_EXPERT, + },{ + .subvendor = 0x107d, + .subdevice = 0x6613, /* NTSC */ + .card = CX88_BOARD_WINFAST2000XP_EXPERT, + },{ + .subvendor = 0x107d, + .subdevice = 0x6620, + .card = CX88_BOARD_WINFAST_DV2000, + },{ + .subvendor = 0x107d, + .subdevice = 0x663b, + .card = CX88_BOARD_LEADTEK_PVR2000, + },{ + .subvendor = 0x107d, + .subdevice = 0x663C, + .card = CX88_BOARD_LEADTEK_PVR2000, + },{ + .subvendor = 0x1461, + .subdevice = 0x000b, + .card = CX88_BOARD_AVERTV_303, + },{ + .subvendor = 0x1462, + .subdevice = 0x8606, + .card = CX88_BOARD_MSI_TVANYWHERE_MASTER, + },{ + .subvendor = 0x10fc, + .subdevice = 0xd003, + .card = CX88_BOARD_IODATA_GVVCP3PCI, + },{ + .subvendor = 0x1043, + .subdevice = 0x4823, /* with mpeg encoder */ + .card = CX88_BOARD_ASUS_PVR_416, + },{ + .subvendor = 0x17de, + .subdevice = 0x08a6, + .card = CX88_BOARD_KWORLD_DVB_T, + },{ + .subvendor = 0x18ac, + .subdevice = 0xd810, + .card = CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD, + },{ + .subvendor = 0x18AC, + .subdevice = 0xDB00, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1, + },{ + .subvendor = 0x0070, + .subdevice = 0x9002, + .card = CX88_BOARD_HAUPPAUGE_DVB_T1, + },{ + .subvendor = 0x14f1, + .subdevice = 0x0187, + .card = CX88_BOARD_CONEXANT_DVB_T1, + },{ + .subvendor = 0x1540, + .subdevice = 0x2580, + .card = CX88_BOARD_PROVIDEO_PV259, + },{ + .subvendor = 0x18AC, + .subdevice = 0xDB10, + .card = CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS, + },{ + .subvendor = 0x1554, + .subdevice = 0x4811, + .card = CX88_BOARD_PIXELVIEW, + },{ + .subvendor = 0x7063, + .subdevice = 0x3000, /* HD-3000 card */ + .card = CX88_BOARD_PCHDTV_HD3000, + },{ + .subvendor = 0x17DE, + .subdevice = 0xA8A6, + .card = CX88_BOARD_DNTV_LIVE_DVB_T, + },{ + .subvendor = 0x0070, + .subdevice = 0x2801, + .card = CX88_BOARD_HAUPPAUGE_ROSLYN, + },{ + .subvendor = 0x14F1, + .subdevice = 0x0342, + .card = CX88_BOARD_DIGITALLOGIC_MEC, + },{ + .subvendor = 0x10fc, + .subdevice = 0xd035, + .card = CX88_BOARD_IODATA_GVBCTV7E, + } +}; +const unsigned int cx88_idcount = ARRAY_SIZE(cx88_subids); + +/* ----------------------------------------------------------------------- */ +/* some leadtek specific stuff */ + +static void __devinit leadtek_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + /* This is just for the "Winfast 2000XP Expert" board ATM; I don't have data on + * any others. + * + * Byte 0 is 1 on the NTSC board. + */ + + if (eeprom_data[4] != 0x7d || + eeprom_data[5] != 0x10 || + eeprom_data[7] != 0x66) { + printk(KERN_WARNING "%s: Leadtek eeprom invalid.\n", + core->name); + return; + } + + core->has_radio = 1; + core->tuner_type = (eeprom_data[6] == 0x13) ? 43 : 38; + + printk(KERN_INFO "%s: Leadtek Winfast 2000XP Expert config: " + "tuner=%d, eeprom[0]=0x%02x\n", + core->name, core->tuner_type, eeprom_data[0]); +} + + +/* ----------------------------------------------------------------------- */ + +static void hauppauge_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + struct tveeprom tv; + + tveeprom_hauppauge_analog(&tv, eeprom_data); + core->tuner_type = tv.tuner_type; + core->has_radio = tv.has_radio; +} + +static int hauppauge_eeprom_dvb(struct cx88_core *core, u8 *ee) +{ + int model; + int tuner; + + /* Make sure we support the board model */ + model = ee[0x1f] << 24 | ee[0x1e] << 16 | ee[0x1d] << 8 | ee[0x1c]; + switch(model) { + case 90002: + case 90500: + case 90501: + /* known */ + break; + default: + printk("%s: warning: unknown hauppauge model #%d\n", + core->name, model); + break; + } + + /* Make sure we support the tuner */ + tuner = ee[0x2d]; + switch(tuner) { + case 0x4B: /* dtt 7595 */ + case 0x4C: /* dtt 7592 */ + break; + default: + printk("%s: error: unknown hauppauge tuner 0x%02x\n", + core->name, tuner); + return -ENODEV; + } + printk(KERN_INFO "%s: hauppauge eeprom: model=%d, tuner=%d\n", + core->name, model, tuner); + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* some GDI (was: Modular Technology) specific stuff */ + +static struct { + int id; + int fm; + char *name; +} gdi_tuner[] = { + [ 0x01 ] = { .id = TUNER_ABSENT, + .name = "NTSC_M" }, + [ 0x02 ] = { .id = TUNER_ABSENT, + .name = "PAL_B" }, + [ 0x03 ] = { .id = TUNER_ABSENT, + .name = "PAL_I" }, + [ 0x04 ] = { .id = TUNER_ABSENT, + .name = "PAL_D" }, + [ 0x05 ] = { .id = TUNER_ABSENT, + .name = "SECAM" }, + + [ 0x10 ] = { .id = TUNER_ABSENT, + .fm = 1, + .name = "TEMIC_4049" }, + [ 0x11 ] = { .id = TUNER_TEMIC_4136FY5, + .name = "TEMIC_4136" }, + [ 0x12 ] = { .id = TUNER_ABSENT, + .name = "TEMIC_4146" }, + + [ 0x20 ] = { .id = TUNER_PHILIPS_FQ1216ME, + .fm = 1, + .name = "PHILIPS_FQ1216_MK3" }, + [ 0x21 ] = { .id = TUNER_ABSENT, .fm = 1, + .name = "PHILIPS_FQ1236_MK3" }, + [ 0x22 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1236_MK3" }, + [ 0x23 ] = { .id = TUNER_ABSENT, + .name = "PHILIPS_FI1216_MK3" }, +}; + +static void gdi_eeprom(struct cx88_core *core, u8 *eeprom_data) +{ + char *name = (eeprom_data[0x0d] < ARRAY_SIZE(gdi_tuner)) + ? gdi_tuner[eeprom_data[0x0d]].name : NULL; + + printk(KERN_INFO "%s: GDI: tuner=%s\n", core->name, + name ? name : "unknown"); + if (NULL == name) + return; + core->tuner_type = gdi_tuner[eeprom_data[0x0d]].id; + core->has_radio = gdi_tuner[eeprom_data[0x0d]].fm; +} + +/* ----------------------------------------------------------------------- */ + +void cx88_card_list(struct cx88_core *core, struct pci_dev *pci) +{ + int i; + + if (0 == pci->subsystem_vendor && + 0 == pci->subsystem_device) { + printk("%s: Your board has no valid PCI Subsystem ID and thus can't\n" + "%s: be autodetected. Please pass card=<n> insmod option to\n" + "%s: workaround that. Redirect complaints to the vendor of\n" + "%s: the TV card. Best regards,\n" + "%s: -- tux\n", + core->name,core->name,core->name,core->name,core->name); + } else { + printk("%s: Your board isn't known (yet) to the driver. You can\n" + "%s: try to pick one of the existing card configs via\n" + "%s: card=<n> insmod option. Updating to the latest\n" + "%s: version might help as well.\n", + core->name,core->name,core->name,core->name); + } + printk("%s: Here is a list of valid choices for the card=<n> insmod option:\n", + core->name); + for (i = 0; i < cx88_bcount; i++) + printk("%s: card=%d -> %s\n", + core->name, i, cx88_boards[i].name); +} + +void cx88_card_setup(struct cx88_core *core) +{ + static u8 eeprom[128]; + + if (0 == core->i2c_rc) { + core->i2c_client.addr = 0xa0 >> 1; + tveeprom_read(&core->i2c_client,eeprom,sizeof(eeprom)); + } + + switch (core->board) { + case CX88_BOARD_HAUPPAUGE: + case CX88_BOARD_HAUPPAUGE_ROSLYN: + if (0 == core->i2c_rc) + hauppauge_eeprom(core,eeprom+8); + break; + case CX88_BOARD_GDI: + if (0 == core->i2c_rc) + gdi_eeprom(core,eeprom); + break; + case CX88_BOARD_WINFAST2000XP_EXPERT: + if (0 == core->i2c_rc) + leadtek_eeprom(core,eeprom); + break; + case CX88_BOARD_HAUPPAUGE_DVB_T1: + if (0 == core->i2c_rc) + hauppauge_eeprom_dvb(core,eeprom); + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1: + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS: + /* GPIO0:0 is hooked to mt352 reset pin */ + cx_set(MO_GP0_IO, 0x00000101); + cx_clear(MO_GP0_IO, 0x00000001); + msleep(1); + cx_set(MO_GP0_IO, 0x00000101); + break; + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_DNTV_LIVE_DVB_T: + cx_set(MO_GP0_IO, 0x00000707); + cx_set(MO_GP2_IO, 0x00000101); + cx_clear(MO_GP2_IO, 0x00000001); + msleep(1); + cx_clear(MO_GP0_IO, 0x00000007); + cx_set(MO_GP2_IO, 0x00000101); + break; + } + if (cx88_boards[core->board].radio.type == CX88_RADIO) + core->has_radio = 1; +} + +/* ------------------------------------------------------------------ */ + +EXPORT_SYMBOL(cx88_boards); +EXPORT_SYMBOL(cx88_bcount); +EXPORT_SYMBOL(cx88_subids); +EXPORT_SYMBOL(cx88_idcount); +EXPORT_SYMBOL(cx88_card_list); +EXPORT_SYMBOL(cx88_card_setup); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-core.c b/drivers/media/video/cx88/cx88-core.c new file mode 100644 index 000000000000..26a6138015cb --- /dev/null +++ b/drivers/media/video/cx88/cx88-core.c @@ -0,0 +1,1239 @@ +/* + * $Id: cx88-core.c,v 1.24 2005/01/19 12:01:55 kraxel Exp $ + * + * device driver for Conexant 2388x based TV cards + * driver core + * + * (c) 2003 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/sound.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/videodev.h> + +#include "cx88.h" + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int core_debug = 0; +module_param(core_debug,int,0644); +MODULE_PARM_DESC(core_debug,"enable debug messages [core]"); + +static unsigned int latency = UNSET; +module_param(latency,int,0444); +MODULE_PARM_DESC(latency,"pci latency timer"); + +static unsigned int tuner[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int card[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; + +module_param_array(tuner, int, NULL, 0444); +module_param_array(card, int, NULL, 0444); + +MODULE_PARM_DESC(tuner,"tuner type"); +MODULE_PARM_DESC(card,"card type"); + +static unsigned int nicam = 0; +module_param(nicam,int,0644); +MODULE_PARM_DESC(nicam,"tv audio is nicam"); + +static unsigned int nocomb = 0; +module_param(nocomb,int,0644); +MODULE_PARM_DESC(nocomb,"disable comb filter"); + +#define dprintk(level,fmt, arg...) if (core_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, core->name , ## arg) + +static unsigned int cx88_devcount; +static LIST_HEAD(cx88_devlist); +static DECLARE_MUTEX(devlist); + +/* ------------------------------------------------------------------ */ +/* debug help functions */ + +static const char *v4l1_ioctls[] = { + "0", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", + "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", + "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", + "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", + "SMICROCODE", "GVBIFMT", "SVBIFMT" }; +#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls) + +static const char *v4l2_ioctls[] = { + "QUERYCAP", "1", "ENUM_PIXFMT", "ENUM_FBUFFMT", "G_FMT", "S_FMT", + "G_COMP", "S_COMP", "REQBUFS", "QUERYBUF", "G_FBUF", "S_FBUF", + "G_WIN", "S_WIN", "PREVIEW", "QBUF", "16", "DQBUF", "STREAMON", + "STREAMOFF", "G_PERF", "G_PARM", "S_PARM", "G_STD", "S_STD", + "ENUMSTD", "ENUMINPUT", "G_CTRL", "S_CTRL", "G_TUNER", "S_TUNER", + "G_FREQ", "S_FREQ", "G_AUDIO", "S_AUDIO", "35", "QUERYCTRL", + "QUERYMENU", "G_INPUT", "S_INPUT", "ENUMCVT", "41", "42", "43", + "44", "45", "G_OUTPUT", "S_OUTPUT", "ENUMOUTPUT", "G_AUDOUT", + "S_AUDOUT", "ENUMFX", "G_EFFECT", "S_EFFECT", "G_MODULATOR", + "S_MODULATOR" +}; +#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +void cx88_print_ioctl(char *name, unsigned int cmd) +{ + char *dir; + + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: dir = "--"; break; + case _IOC_READ: dir = "r-"; break; + case _IOC_WRITE: dir = "-w"; break; + case _IOC_READ | _IOC_WRITE: dir = "rw"; break; + default: dir = "??"; break; + } + switch (_IOC_TYPE(cmd)) { + case 'v': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l1, %s, VIDIOC%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L1_IOCTLS) ? + v4l1_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'V': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l2, %s, VIDIOC_%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L2_IOCTLS) ? + v4l2_ioctls[_IOC_NR(cmd)] : "???"); + break; + default: + printk(KERN_DEBUG "%s: ioctl 0x%08x (???, %s, #%d)\n", + name, cmd, dir, _IOC_NR(cmd)); + } +} + +/* ------------------------------------------------------------------ */ +#define NO_SYNC_LINE (-1U) + +static u32* cx88_risc_field(u32 *rp, struct scatterlist *sglist, + unsigned int offset, u32 sync_line, + unsigned int bpl, unsigned int padding, + unsigned int lines) +{ + struct scatterlist *sg; + unsigned int line,todo; + + /* sync instruction */ + if (sync_line != NO_SYNC_LINE) + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + /* scan lines */ + sg = sglist; + for (line = 0; line < lines; line++) { + while (offset && offset >= sg_dma_len(sg)) { + offset -= sg_dma_len(sg); + sg++; + } + if (bpl <= sg_dma_len(sg)-offset) { + /* fits into current chunk */ + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_SOL|RISC_EOL|bpl); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + offset+=bpl; + } else { + /* scanline needs to be splitted */ + todo = bpl; + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_SOL| + (sg_dma_len(sg)-offset)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)+offset); + todo -= (sg_dma_len(sg)-offset); + offset = 0; + sg++; + while (todo > sg_dma_len(sg)) { + *(rp++)=cpu_to_le32(RISC_WRITE| + sg_dma_len(sg)); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + todo -= sg_dma_len(sg); + sg++; + } + *(rp++)=cpu_to_le32(RISC_WRITE|RISC_EOL|todo); + *(rp++)=cpu_to_le32(sg_dma_address(sg)); + offset += todo; + } + offset += padding; + } + + return rp; +} + +int cx88_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines) +{ + u32 instructions,fields; + u32 *rp; + int rc; + + fields = 0; + if (UNSET != top_offset) + fields++; + if (UNSET != bottom_offset) + fields++; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords) */ + instructions = (bpl * lines * fields) / PAGE_SIZE + lines * fields; + instructions += 3 + 4; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + if (UNSET != top_offset) + rp = cx88_risc_field(rp, sglist, top_offset, 0, + bpl, padding, lines); + if (UNSET != bottom_offset) + rp = cx88_risc_field(rp, sglist, bottom_offset, 0x200, + bpl, padding, lines); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) / 4 > risc->size); + return 0; +} + +int cx88_risc_databuffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines) +{ + u32 instructions; + u32 *rp; + int rc; + + /* estimate risc mem: worst case is one write per page border + + one write per scan line + syncs + jump (all 2 dwords) */ + instructions = (bpl * lines) / PAGE_SIZE + lines; + instructions += 3 + 4; + if ((rc = btcx_riscmem_alloc(pci,risc,instructions*8)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + rp = cx88_risc_field(rp, sglist, 0, NO_SYNC_LINE, bpl, 0, lines); + + /* save pointer to jmp instruction address */ + risc->jmp = rp; + BUG_ON((risc->jmp - risc->cpu + 2) / 4 > risc->size); + return 0; +} + +int cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value) +{ + u32 *rp; + int rc; + + if ((rc = btcx_riscmem_alloc(pci, risc, 4*16)) < 0) + return rc; + + /* write risc instructions */ + rp = risc->cpu; + *(rp++) = cpu_to_le32(RISC_WRITECR | RISC_IRQ2 | RISC_IMM); + *(rp++) = cpu_to_le32(reg); + *(rp++) = cpu_to_le32(value); + *(rp++) = cpu_to_le32(mask); + *(rp++) = cpu_to_le32(RISC_JUMP); + *(rp++) = cpu_to_le32(risc->dma); + return 0; +} + +void +cx88_free_buffer(struct pci_dev *pci, struct cx88_buffer *buf) +{ + if (in_interrupt()) + BUG(); + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + btcx_riscmem_free(pci, &buf->risc); + buf->vb.state = STATE_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ +/* our SRAM memory layout */ + +/* we are going to put all thr risc programs into host memory, so we + * can use the whole SDRAM for the DMA fifos. To simplify things, we + * use a static memory layout. That surely will waste memory in case + * we don't use all DMA channels at the same time (which will be the + * case most of the time). But that still gives us enougth FIFO space + * to be able to deal with insane long pci latencies ... + * + * FIFO space allocations: + * channel 21 (y video) - 10.0k + * channel 22 (u video) - 2.0k + * channel 23 (v video) - 2.0k + * channel 24 (vbi) - 4.0k + * channels 25+26 (audio) - 0.5k + * channel 28 (mpeg) - 4.0k + * TOTAL = 25.5k + * + * Every channel has 160 bytes control data (64 bytes instruction + * queue and 6 CDT entries), which is close to 2k total. + * + * Address layout: + * 0x0000 - 0x03ff CMDs / reserved + * 0x0400 - 0x0bff instruction queues + CDs + * 0x0c00 - FIFOs + */ + +struct sram_channel cx88_sram_channels[] = { + [SRAM_CH21] = { + .name = "video y / packed", + .cmds_start = 0x180040, + .ctrl_start = 0x180400, + .cdt = 0x180400 + 64, + .fifo_start = 0x180c00, + .fifo_size = 0x002800, + .ptr1_reg = MO_DMA21_PTR1, + .ptr2_reg = MO_DMA21_PTR2, + .cnt1_reg = MO_DMA21_CNT1, + .cnt2_reg = MO_DMA21_CNT2, + }, + [SRAM_CH22] = { + .name = "video u", + .cmds_start = 0x180080, + .ctrl_start = 0x1804a0, + .cdt = 0x1804a0 + 64, + .fifo_start = 0x183400, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA22_PTR1, + .ptr2_reg = MO_DMA22_PTR2, + .cnt1_reg = MO_DMA22_CNT1, + .cnt2_reg = MO_DMA22_CNT2, + }, + [SRAM_CH23] = { + .name = "video v", + .cmds_start = 0x1800c0, + .ctrl_start = 0x180540, + .cdt = 0x180540 + 64, + .fifo_start = 0x183c00, + .fifo_size = 0x000800, + .ptr1_reg = MO_DMA23_PTR1, + .ptr2_reg = MO_DMA23_PTR2, + .cnt1_reg = MO_DMA23_CNT1, + .cnt2_reg = MO_DMA23_CNT2, + }, + [SRAM_CH24] = { + .name = "vbi", + .cmds_start = 0x180100, + .ctrl_start = 0x1805e0, + .cdt = 0x1805e0 + 64, + .fifo_start = 0x184400, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA24_PTR1, + .ptr2_reg = MO_DMA24_PTR2, + .cnt1_reg = MO_DMA24_CNT1, + .cnt2_reg = MO_DMA24_CNT2, + }, + [SRAM_CH25] = { + .name = "audio from", + .cmds_start = 0x180140, + .ctrl_start = 0x180680, + .cdt = 0x180680 + 64, + .fifo_start = 0x185400, + .fifo_size = 0x000200, + .ptr1_reg = MO_DMA25_PTR1, + .ptr2_reg = MO_DMA25_PTR2, + .cnt1_reg = MO_DMA25_CNT1, + .cnt2_reg = MO_DMA25_CNT2, + }, + [SRAM_CH26] = { + .name = "audio to", + .cmds_start = 0x180180, + .ctrl_start = 0x180720, + .cdt = 0x180680 + 64, /* same as audio IN */ + .fifo_start = 0x185400, /* same as audio IN */ + .fifo_size = 0x000200, /* same as audio IN */ + .ptr1_reg = MO_DMA26_PTR1, + .ptr2_reg = MO_DMA26_PTR2, + .cnt1_reg = MO_DMA26_CNT1, + .cnt2_reg = MO_DMA26_CNT2, + }, + [SRAM_CH28] = { + .name = "mpeg", + .cmds_start = 0x180200, + .ctrl_start = 0x1807C0, + .cdt = 0x1807C0 + 64, + .fifo_start = 0x185600, + .fifo_size = 0x001000, + .ptr1_reg = MO_DMA28_PTR1, + .ptr2_reg = MO_DMA28_PTR2, + .cnt1_reg = MO_DMA28_CNT1, + .cnt2_reg = MO_DMA28_CNT2, + }, +}; + +int cx88_sram_channel_setup(struct cx88_core *core, + struct sram_channel *ch, + unsigned int bpl, u32 risc) +{ + unsigned int i,lines; + u32 cdt; + + bpl = (bpl + 7) & ~7; /* alignment */ + cdt = ch->cdt; + lines = ch->fifo_size / bpl; + if (lines > 6) + lines = 6; + BUG_ON(lines < 2); + + /* write CDT */ + for (i = 0; i < lines; i++) + cx_write(cdt + 16*i, ch->fifo_start + bpl*i); + + /* write CMDS */ + cx_write(ch->cmds_start + 0, risc); + cx_write(ch->cmds_start + 4, cdt); + cx_write(ch->cmds_start + 8, (lines*16) >> 3); + cx_write(ch->cmds_start + 12, ch->ctrl_start); + cx_write(ch->cmds_start + 16, 64 >> 2); + for (i = 20; i < 64; i += 4) + cx_write(ch->cmds_start + i, 0); + + /* fill registers */ + cx_write(ch->ptr1_reg, ch->fifo_start); + cx_write(ch->ptr2_reg, cdt); + cx_write(ch->cnt1_reg, (bpl >> 3) -1); + cx_write(ch->cnt2_reg, (lines*16) >> 3); + + dprintk(2,"sram setup %s: bpl=%d lines=%d\n", ch->name, bpl, lines); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* debug helper code */ + +int cx88_risc_decode(u32 risc) +{ + static char *instr[16] = { + [ RISC_SYNC >> 28 ] = "sync", + [ RISC_WRITE >> 28 ] = "write", + [ RISC_WRITEC >> 28 ] = "writec", + [ RISC_READ >> 28 ] = "read", + [ RISC_READC >> 28 ] = "readc", + [ RISC_JUMP >> 28 ] = "jump", + [ RISC_SKIP >> 28 ] = "skip", + [ RISC_WRITERM >> 28 ] = "writerm", + [ RISC_WRITECM >> 28 ] = "writecm", + [ RISC_WRITECR >> 28 ] = "writecr", + }; + static int incr[16] = { + [ RISC_WRITE >> 28 ] = 2, + [ RISC_JUMP >> 28 ] = 2, + [ RISC_WRITERM >> 28 ] = 3, + [ RISC_WRITECM >> 28 ] = 3, + [ RISC_WRITECR >> 28 ] = 4, + }; + static char *bits[] = { + "12", "13", "14", "resync", + "cnt0", "cnt1", "18", "19", + "20", "21", "22", "23", + "irq1", "irq2", "eol", "sol", + }; + int i; + + printk("0x%08x [ %s", risc, + instr[risc >> 28] ? instr[risc >> 28] : "INVALID"); + for (i = ARRAY_SIZE(bits)-1; i >= 0; i--) + if (risc & (1 << (i + 12))) + printk(" %s",bits[i]); + printk(" count=%d ]\n", risc & 0xfff); + return incr[risc >> 28] ? incr[risc >> 28] : 1; +} + +#if 0 /* currently unused, but useful for debugging */ +void cx88_risc_disasm(struct cx88_core *core, + struct btcx_riscmem *risc) +{ + unsigned int i,j,n; + + printk("%s: risc disasm: %p [dma=0x%08lx]\n", + core->name, risc->cpu, (unsigned long)risc->dma); + for (i = 0; i < (risc->size >> 2); i += n) { + printk("%s: %04d: ", core->name, i); + n = cx88_risc_decode(risc->cpu[i]); + for (j = 1; j < n; j++) + printk("%s: %04d: 0x%08x [ arg #%d ]\n", + core->name, i+j, risc->cpu[i+j], j); + if (risc->cpu[i] == RISC_JUMP) + break; + } +} +#endif + +void cx88_sram_channel_dump(struct cx88_core *core, + struct sram_channel *ch) +{ + static char *name[] = { + "initial risc", + "cdt base", + "cdt size", + "iq base", + "iq size", + "risc pc", + "iq wr ptr", + "iq rd ptr", + "cdt current", + "pci target", + "line / byte", + }; + u32 risc; + unsigned int i,j,n; + + printk("%s: %s - dma channel status dump\n", + core->name,ch->name); + for (i = 0; i < ARRAY_SIZE(name); i++) + printk("%s: cmds: %-12s: 0x%08x\n", + core->name,name[i], + cx_read(ch->cmds_start + 4*i)); + for (i = 0; i < 4; i++) { + risc = cx_read(ch->cmds_start + 4 * (i+11)); + printk("%s: risc%d: ", core->name, i); + cx88_risc_decode(risc); + } + for (i = 0; i < 16; i += n) { + risc = cx_read(ch->ctrl_start + 4 * i); + printk("%s: iq %x: ", core->name, i); + n = cx88_risc_decode(risc); + for (j = 1; j < n; j++) { + risc = cx_read(ch->ctrl_start + 4 * (i+j)); + printk("%s: iq %x: 0x%08x [ arg #%d ]\n", + core->name, i+j, risc, j); + } + } + + printk("%s: fifo: 0x%08x -> 0x%x\n", + core->name, ch->fifo_start, ch->fifo_start+ch->fifo_size); + printk("%s: ctrl: 0x%08x -> 0x%x\n", + core->name, ch->ctrl_start, ch->ctrl_start+6*16); + printk("%s: ptr1_reg: 0x%08x\n", + core->name,cx_read(ch->ptr1_reg)); + printk("%s: ptr2_reg: 0x%08x\n", + core->name,cx_read(ch->ptr2_reg)); + printk("%s: cnt1_reg: 0x%08x\n", + core->name,cx_read(ch->cnt1_reg)); + printk("%s: cnt2_reg: 0x%08x\n", + core->name,cx_read(ch->cnt2_reg)); +} + +char *cx88_pci_irqs[32] = { + "vid", "aud", "ts", "vip", "hst", "5", "6", "tm1", + "src_dma", "dst_dma", "risc_rd_err", "risc_wr_err", + "brdg_err", "src_dma_err", "dst_dma_err", "ipb_dma_err", + "i2c", "i2c_rack", "ir_smp", "gpio0", "gpio1" +}; +char *cx88_vid_irqs[32] = { + "y_risci1", "u_risci1", "v_risci1", "vbi_risc1", + "y_risci2", "u_risci2", "v_risci2", "vbi_risc2", + "y_oflow", "u_oflow", "v_oflow", "vbi_oflow", + "y_sync", "u_sync", "v_sync", "vbi_sync", + "opc_err", "par_err", "rip_err", "pci_abort", +}; +char *cx88_mpeg_irqs[32] = { + "ts_risci1", NULL, NULL, NULL, + "ts_risci2", NULL, NULL, NULL, + "ts_oflow", NULL, NULL, NULL, + "ts_sync", NULL, NULL, NULL, + "opc_err", "par_err", "rip_err", "pci_abort", + "ts_err?", +}; + +void cx88_print_irqbits(char *name, char *tag, char **strings, + u32 bits, u32 mask) +{ + unsigned int i; + + printk(KERN_DEBUG "%s: %s [0x%x]", name, tag, bits); + for (i = 0; i < 32; i++) { + if (!(bits & (1 << i))) + continue; + if (strings[i]) + printk(" %s", strings[i]); + else + printk(" %d", i); + if (!(mask & (1 << i))) + continue; + printk("*"); + } + printk("\n"); +} + +/* ------------------------------------------------------------------ */ + +int cx88_core_irq(struct cx88_core *core, u32 status) +{ + int handled = 0; + + if (status & (1<<18)) { + cx88_ir_irq(core); + handled++; + } + if (!handled) + cx88_print_irqbits(core->name, "irq pci", + cx88_pci_irqs, status, + core->pci_irqmask); + return handled; +} + +void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count) +{ + struct cx88_buffer *buf; + int bc; + + for (bc = 0;; bc++) { + if (list_empty(&q->active)) + break; + buf = list_entry(q->active.next, + struct cx88_buffer, vb.queue); +#if 0 + if (buf->count > count) + break; +#else + /* count comes from the hw and is is 16bit wide -- + * this trick handles wrap-arounds correctly for + * up to 32767 buffers in flight... */ + if ((s16) (count - buf->count) < 0) + break; +#endif + do_gettimeofday(&buf->vb.ts); + dprintk(2,"[%p/%d] wakeup reg=%d buf=%d\n",buf,buf->vb.i, + count, buf->count); + buf->vb.state = STATE_DONE; + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); + } + if (list_empty(&q->active)) { + del_timer(&q->timeout); + } else { + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + } + if (bc != 1) + printk("%s: %d buffers handled (should be 1)\n",__FUNCTION__,bc); +} + +void cx88_shutdown(struct cx88_core *core) +{ + /* disable RISC controller + IRQs */ + cx_write(MO_DEV_CNTRL2, 0); + + /* stop dma transfers */ + cx_write(MO_VID_DMACNTRL, 0x0); + cx_write(MO_AUD_DMACNTRL, 0x0); + cx_write(MO_TS_DMACNTRL, 0x0); + cx_write(MO_VIP_DMACNTRL, 0x0); + cx_write(MO_GPHST_DMACNTRL, 0x0); + + /* stop interrupts */ + cx_write(MO_PCI_INTMSK, 0x0); + cx_write(MO_VID_INTMSK, 0x0); + cx_write(MO_AUD_INTMSK, 0x0); + cx_write(MO_TS_INTMSK, 0x0); + cx_write(MO_VIP_INTMSK, 0x0); + cx_write(MO_GPHST_INTMSK, 0x0); + + /* stop capturing */ + cx_write(VID_CAPTURE_CONTROL, 0); +} + +int cx88_reset(struct cx88_core *core) +{ + dprintk(1,"%s\n",__FUNCTION__); + cx88_shutdown(core); + + /* clear irq status */ + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* wait a bit */ + msleep(100); + + /* init sram */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH21], 720*4, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH22], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH23], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH24], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH25], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH26], 128, 0); + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], 188*4, 0); + + /* misc init ... */ + cx_write(MO_INPUT_FORMAT, ((1 << 13) | // agc enable + (1 << 12) | // agc gain + (1 << 11) | // adaptibe agc + (0 << 10) | // chroma agc + (0 << 9) | // ckillen + (7))); + + /* setup image format */ + cx_andor(MO_COLOR_CTRL, 0x4000, 0x4000); + + /* setup FIFO Threshholds */ + cx_write(MO_PDMA_STHRSH, 0x0807); + cx_write(MO_PDMA_DTHRSH, 0x0807); + + /* fixes flashing of image */ + cx_write(MO_AGC_SYNC_TIP1, 0x0380000F); + cx_write(MO_AGC_BACK_VBI, 0x00E00555); + + cx_write(MO_VID_INTSTAT, 0xFFFFFFFF); // Clear PIV int + cx_write(MO_PCI_INTSTAT, 0xFFFFFFFF); // Clear PCI int + cx_write(MO_INT1_STAT, 0xFFFFFFFF); // Clear RISC int + + /* Reset on-board parts */ + cx_write(MO_SRST_IO, 0); + msleep(10); + cx_write(MO_SRST_IO, 1); + + return 0; +} + +/* ------------------------------------------------------------------ */ + +static unsigned int inline norm_swidth(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 922 : 754; +} + +static unsigned int inline norm_hdelay(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 186 : 135; +} + +static unsigned int inline norm_vdelay(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 0x24 : 0x18; +} + +static unsigned int inline norm_fsc8(struct cx88_tvnorm *norm) +{ + static const unsigned int ntsc = 28636360; + static const unsigned int pal = 35468950; + + return (norm->id & V4L2_STD_625_50) ? pal : ntsc; +} + +static unsigned int inline norm_notchfilter(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) + ? HLNotchFilter135PAL + : HLNotchFilter135NTSC; +} + +static unsigned int inline norm_htotal(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 1135 : 910; +} + +static unsigned int inline norm_vbipack(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 511 : 288; +} + +int cx88_set_scale(struct cx88_core *core, unsigned int width, unsigned int height, + enum v4l2_field field) +{ + unsigned int swidth = norm_swidth(core->tvnorm); + unsigned int sheight = norm_maxh(core->tvnorm); + u32 value; + + dprintk(1,"set_scale: %dx%d [%s%s,%s]\n", width, height, + V4L2_FIELD_HAS_TOP(field) ? "T" : "", + V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", + core->tvnorm->name); + if (!V4L2_FIELD_HAS_BOTH(field)) + height *= 2; + + // recalc H delay and scale registers + value = (width * norm_hdelay(core->tvnorm)) / swidth; + value &= 0x3fe; + cx_write(MO_HDELAY_EVEN, value); + cx_write(MO_HDELAY_ODD, value); + dprintk(1,"set_scale: hdelay 0x%04x\n", value); + + value = (swidth * 4096 / width) - 4096; + cx_write(MO_HSCALE_EVEN, value); + cx_write(MO_HSCALE_ODD, value); + dprintk(1,"set_scale: hscale 0x%04x\n", value); + + cx_write(MO_HACTIVE_EVEN, width); + cx_write(MO_HACTIVE_ODD, width); + dprintk(1,"set_scale: hactive 0x%04x\n", width); + + // recalc V scale Register (delay is constant) + cx_write(MO_VDELAY_EVEN, norm_vdelay(core->tvnorm)); + cx_write(MO_VDELAY_ODD, norm_vdelay(core->tvnorm)); + dprintk(1,"set_scale: vdelay 0x%04x\n", norm_vdelay(core->tvnorm)); + + value = (0x10000 - (sheight * 512 / height - 512)) & 0x1fff; + cx_write(MO_VSCALE_EVEN, value); + cx_write(MO_VSCALE_ODD, value); + dprintk(1,"set_scale: vscale 0x%04x\n", value); + + cx_write(MO_VACTIVE_EVEN, sheight); + cx_write(MO_VACTIVE_ODD, sheight); + dprintk(1,"set_scale: vactive 0x%04x\n", sheight); + + // setup filters + value = 0; + value |= (1 << 19); // CFILT (default) + if (core->tvnorm->id & V4L2_STD_SECAM) { + value |= (1 << 15); + value |= (1 << 16); + } + if (INPUT(core->input)->type == CX88_VMUX_SVIDEO) + value |= (1 << 13) | (1 << 5); + if (V4L2_FIELD_INTERLACED == field) + value |= (1 << 3); // VINT (interlaced vertical scaling) + if (width < 385) + value |= (1 << 0); // 3-tap interpolation + if (width < 193) + value |= (1 << 1); // 5-tap interpolation + if (nocomb) + value |= (3 << 5); // disable comb filter + + cx_write(MO_FILTER_EVEN, value); + cx_write(MO_FILTER_ODD, value); + dprintk(1,"set_scale: filter 0x%04x\n", value); + + return 0; +} + +static const u32 xtal = 28636363; + +static int set_pll(struct cx88_core *core, int prescale, u32 ofreq) +{ + static u32 pre[] = { 0, 0, 0, 3, 2, 1 }; + u64 pll; + u32 reg; + int i; + + if (prescale < 2) + prescale = 2; + if (prescale > 5) + prescale = 5; + + pll = ofreq * 8 * prescale * (u64)(1 << 20); + do_div(pll,xtal); + reg = (pll & 0x3ffffff) | (pre[prescale] << 26); + if (((reg >> 20) & 0x3f) < 14) { + printk("%s/0: pll out of range\n",core->name); + return -1; + } + + dprintk(1,"set_pll: MO_PLL_REG 0x%08x [old=0x%08x,freq=%d]\n", + reg, cx_read(MO_PLL_REG), ofreq); + cx_write(MO_PLL_REG, reg); + for (i = 0; i < 100; i++) { + reg = cx_read(MO_DEVICE_STATUS); + if (reg & (1<<2)) { + dprintk(1,"pll locked [pre=%d,ofreq=%d]\n", + prescale,ofreq); + return 0; + } + dprintk(1,"pll not locked yet, waiting ...\n"); + msleep(10); + } + dprintk(1,"pll NOT locked [pre=%d,ofreq=%d]\n",prescale,ofreq); + return -1; +} + +static int set_tvaudio(struct cx88_core *core) +{ + struct cx88_tvnorm *norm = core->tvnorm; + + if (CX88_VMUX_TELEVISION != INPUT(core->input)->type) + return 0; + + if (V4L2_STD_PAL_BG & norm->id) { + core->tvaudio = nicam ? WW_NICAM_BGDKL : WW_A2_BG; + + } else if (V4L2_STD_PAL_DK & norm->id) { + core->tvaudio = nicam ? WW_NICAM_BGDKL : WW_A2_DK; + + } else if (V4L2_STD_PAL_I & norm->id) { + core->tvaudio = WW_NICAM_I; + + } else if (V4L2_STD_SECAM_L & norm->id) { + core->tvaudio = WW_SYSTEM_L_AM; + + } else if (V4L2_STD_SECAM_DK & norm->id) { + core->tvaudio = WW_A2_DK; + + } else if ((V4L2_STD_NTSC_M & norm->id) || + (V4L2_STD_PAL_M & norm->id)) { + core->tvaudio = WW_BTSC; + + } else if (V4L2_STD_NTSC_M_JP & norm->id) { + core->tvaudio = WW_EIAJ; + + } else { + printk("%s/0: tvaudio support needs work for this tv norm [%s], sorry\n", + core->name, norm->name); + core->tvaudio = 0; + return 0; + } + + cx_andor(MO_AFECFG_IO, 0x1f, 0x0); + cx88_set_tvaudio(core); + // cx88_set_stereo(dev,V4L2_TUNER_MODE_STEREO); + + cx_write(MO_AUDD_LNGTH, 128); /* fifo size */ + cx_write(MO_AUDR_LNGTH, 128); /* fifo size */ + cx_write(MO_AUD_DMACNTRL, 0x03); /* need audio fifo */ + return 0; +} + +int cx88_set_tvnorm(struct cx88_core *core, struct cx88_tvnorm *norm) +{ + u32 fsc8; + u32 adc_clock; + u32 vdec_clock; + u32 step_db,step_dr; + u64 tmp64; + u32 bdelay,agcdelay,htotal; + + core->tvnorm = norm; + fsc8 = norm_fsc8(norm); + adc_clock = xtal; + vdec_clock = fsc8; + step_db = fsc8; + step_dr = fsc8; + + if (norm->id & V4L2_STD_SECAM) { + step_db = 4250000 * 8; + step_dr = 4406250 * 8; + } + + dprintk(1,"set_tvnorm: \"%s\" fsc8=%d adc=%d vdec=%d db/dr=%d/%d\n", + norm->name, fsc8, adc_clock, vdec_clock, step_db, step_dr); + set_pll(core,2,vdec_clock); + + dprintk(1,"set_tvnorm: MO_INPUT_FORMAT 0x%08x [old=0x%08x]\n", + norm->cxiformat, cx_read(MO_INPUT_FORMAT) & 0x0f); + cx_andor(MO_INPUT_FORMAT, 0xf, norm->cxiformat); + +#if 1 + // FIXME: as-is from DScaler + dprintk(1,"set_tvnorm: MO_OUTPUT_FORMAT 0x%08x [old=0x%08x]\n", + norm->cxoformat, cx_read(MO_OUTPUT_FORMAT)); + cx_write(MO_OUTPUT_FORMAT, norm->cxoformat); +#endif + + // MO_SCONV_REG = adc clock / video dec clock * 2^17 + tmp64 = adc_clock * (u64)(1 << 17); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SCONV_REG 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SCONV_REG)); + cx_write(MO_SCONV_REG, (u32)tmp64); + + // MO_SUB_STEP = 8 * fsc / video dec clock * 2^22 + tmp64 = step_db * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SUB_STEP 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP)); + cx_write(MO_SUB_STEP, (u32)tmp64); + + // MO_SUB_STEP_DR = 8 * 4406250 / video dec clock * 2^22 + tmp64 = step_dr * (u64)(1 << 22); + do_div(tmp64, vdec_clock); + dprintk(1,"set_tvnorm: MO_SUB_STEP_DR 0x%08x [old=0x%08x]\n", + (u32)tmp64, cx_read(MO_SUB_STEP_DR)); + cx_write(MO_SUB_STEP_DR, (u32)tmp64); + + // bdelay + agcdelay + bdelay = vdec_clock * 65 / 20000000 + 21; + agcdelay = vdec_clock * 68 / 20000000 + 15; + dprintk(1,"set_tvnorm: MO_AGC_BURST 0x%08x [old=0x%08x,bdelay=%d,agcdelay=%d]\n", + (bdelay << 8) | agcdelay, cx_read(MO_AGC_BURST), bdelay, agcdelay); + cx_write(MO_AGC_BURST, (bdelay << 8) | agcdelay); + + // htotal + tmp64 = norm_htotal(norm) * (u64)vdec_clock; + do_div(tmp64, fsc8); + htotal = (u32)tmp64 | (norm_notchfilter(norm) << 11); + dprintk(1,"set_tvnorm: MO_HTOTAL 0x%08x [old=0x%08x,htotal=%d]\n", + htotal, cx_read(MO_HTOTAL), (u32)tmp64); + cx_write(MO_HTOTAL, htotal); + + // vbi stuff + cx_write(MO_VBI_PACKET, ((1 << 11) | /* (norm_vdelay(norm) << 11) | */ + norm_vbipack(norm))); + + // this is needed as well to set all tvnorm parameter + cx88_set_scale(core, 320, 240, V4L2_FIELD_INTERLACED); + + // audio + set_tvaudio(core); + + // tell i2c chips +#ifdef V4L2_I2C_CLIENTS + cx88_call_i2c_clients(core,VIDIOC_S_STD,&norm->id); +#else + { + struct video_channel c; + memset(&c,0,sizeof(c)); + c.channel = core->input; + c.norm = VIDEO_MODE_PAL; + if ((norm->id & (V4L2_STD_NTSC_M|V4L2_STD_NTSC_M_JP))) + c.norm = VIDEO_MODE_NTSC; + if (norm->id & V4L2_STD_SECAM) + c.norm = VIDEO_MODE_SECAM; + cx88_call_i2c_clients(core,VIDIOCSCHAN,&c); + } +#endif + + // done + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int cx88_pci_quirks(char *name, struct pci_dev *pci) +{ + unsigned int lat = UNSET; + u8 ctrl = 0; + u8 value; + + /* check pci quirks */ + if (pci_pci_problems & PCIPCI_TRITON) { + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_NATOMA) { + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VIAETBF) { + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF -- set TBFX\n", + name); + ctrl |= CX88X_EN_TBFX; + } + if (pci_pci_problems & PCIPCI_VSFX) { + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX -- set VSFX\n", + name); + ctrl |= CX88X_EN_VSFX; + } +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + name); + lat = 0x0A; + } +#endif + + /* check insmod options */ + if (UNSET != latency) + lat = latency; + + /* apply stuff */ + if (ctrl) { + pci_read_config_byte(pci, CX88X_DEVCTRL, &value); + value |= ctrl; + pci_write_config_byte(pci, CX88X_DEVCTRL, value); + } + if (UNSET != lat) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + name, latency); + pci_write_config_byte(pci, PCI_LATENCY_TIMER, latency); + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +struct video_device *cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; + vfd->dev = &pci->dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + core->name, type, cx88_boards[core->board].name); + return vfd; +} + +static int get_ressources(struct cx88_core *core, struct pci_dev *pci) +{ + if (request_mem_region(pci_resource_start(pci,0), + pci_resource_len(pci,0), + core->name)) + return 0; + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%lx\n", + core->name,pci_resource_start(pci,0)); + return -EBUSY; +} + +struct cx88_core* cx88_core_get(struct pci_dev *pci) +{ + struct cx88_core *core; + struct list_head *item; + int i; + + down(&devlist); + list_for_each(item,&cx88_devlist) { + core = list_entry(item, struct cx88_core, devlist); + if (pci->bus->number != core->pci_bus) + continue; + if (PCI_SLOT(pci->devfn) != core->pci_slot) + continue; + + if (0 != get_ressources(core,pci)) + goto fail_unlock; + atomic_inc(&core->refcount); + up(&devlist); + return core; + } + core = kmalloc(sizeof(*core),GFP_KERNEL); + if (NULL == core) + goto fail_unlock; + + memset(core,0,sizeof(*core)); + atomic_inc(&core->refcount); + core->pci_bus = pci->bus->number; + core->pci_slot = PCI_SLOT(pci->devfn); + core->pci_irqmask = 0x00fc00; + + core->nr = cx88_devcount++; + sprintf(core->name,"cx88[%d]",core->nr); + if (0 != get_ressources(core,pci)) { + cx88_devcount--; + goto fail_free; + } + list_add_tail(&core->devlist,&cx88_devlist); + + /* PCI stuff */ + cx88_pci_quirks(core->name, pci); + core->lmmio = ioremap(pci_resource_start(pci,0), + pci_resource_len(pci,0)); + core->bmmio = (u8 __iomem *)core->lmmio; + + /* board config */ + core->board = UNSET; + if (card[core->nr] < cx88_bcount) + core->board = card[core->nr]; + for (i = 0; UNSET == core->board && i < cx88_idcount; i++) + if (pci->subsystem_vendor == cx88_subids[i].subvendor && + pci->subsystem_device == cx88_subids[i].subdevice) + core->board = cx88_subids[i].card; + if (UNSET == core->board) { + core->board = CX88_BOARD_UNKNOWN; + cx88_card_list(core,pci); + } + printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + core->name,pci->subsystem_vendor, + pci->subsystem_device,cx88_boards[core->board].name, + core->board, card[core->nr] == core->board ? + "insmod option" : "autodetected"); + + core->tuner_type = tuner[core->nr]; + if (UNSET == core->tuner_type) + core->tuner_type = cx88_boards[core->board].tuner_type; + core->tda9887_conf = cx88_boards[core->board].tda9887_conf; + + /* init hardware */ + cx88_reset(core); + cx88_i2c_init(core,pci); + cx88_card_setup(core); + cx88_ir_init(core,pci); + + up(&devlist); + return core; + +fail_free: + kfree(core); +fail_unlock: + up(&devlist); + return NULL; +} + +void cx88_core_put(struct cx88_core *core, struct pci_dev *pci) +{ + release_mem_region(pci_resource_start(pci,0), + pci_resource_len(pci,0)); + + if (!atomic_dec_and_test(&core->refcount)) + return; + + down(&devlist); + cx88_ir_fini(core); + if (0 == core->i2c_rc) + i2c_bit_del_bus(&core->i2c_adap); + list_del(&core->devlist); + iounmap(core->lmmio); + cx88_devcount--; + up(&devlist); + kfree(core); +} + +/* ------------------------------------------------------------------ */ + +EXPORT_SYMBOL(cx88_print_ioctl); +EXPORT_SYMBOL(cx88_pci_irqs); +EXPORT_SYMBOL(cx88_vid_irqs); +EXPORT_SYMBOL(cx88_mpeg_irqs); +EXPORT_SYMBOL(cx88_print_irqbits); + +EXPORT_SYMBOL(cx88_core_irq); +EXPORT_SYMBOL(cx88_wakeup); +EXPORT_SYMBOL(cx88_reset); +EXPORT_SYMBOL(cx88_shutdown); + +EXPORT_SYMBOL(cx88_risc_buffer); +EXPORT_SYMBOL(cx88_risc_databuffer); +EXPORT_SYMBOL(cx88_risc_stopper); +EXPORT_SYMBOL(cx88_free_buffer); + +EXPORT_SYMBOL(cx88_sram_channels); +EXPORT_SYMBOL(cx88_sram_channel_setup); +EXPORT_SYMBOL(cx88_sram_channel_dump); + +EXPORT_SYMBOL(cx88_set_tvnorm); +EXPORT_SYMBOL(cx88_set_scale); + +EXPORT_SYMBOL(cx88_vdev_init); +EXPORT_SYMBOL(cx88_core_get); +EXPORT_SYMBOL(cx88_core_put); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-dvb.c b/drivers/media/video/cx88/cx88-dvb.c new file mode 100644 index 000000000000..bc6f18c45357 --- /dev/null +++ b/drivers/media/video/cx88/cx88-dvb.c @@ -0,0 +1,381 @@ +/* + * $Id: cx88-dvb.c,v 1.31 2005/03/07 15:58:05 kraxel Exp $ + * + * device driver for Conexant 2388x based TV cards + * MPEG Transport Stream (DVB) routines + * + * (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au> + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kthread.h> +#include <linux/file.h> +#include <linux/suspend.h> + +/* those two frontends need merging via linuxtv cvs ... */ +#define HAVE_CX22702 0 +#define HAVE_OR51132 1 + +#include "cx88.h" +#include "dvb-pll.h" +#include "mt352.h" +#include "mt352_priv.h" +#if HAVE_CX22702 +# include "cx22702.h" +#endif +#if HAVE_OR51132 +# include "or51132.h" +#endif + +MODULE_DESCRIPTION("driver for cx2388x based DVB cards"); +MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages [dvb]"); + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2-dvb: " fmt, dev->core->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int dvb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct cx8802_dev *dev = q->priv_data; + + dev->ts_packet_size = 188 * 4; + dev->ts_packet_count = 32; + + *size = dev->ts_packet_size * dev->ts_packet_count; + *count = 32; + return 0; +} + +static int dvb_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8802_dev *dev = q->priv_data; + return cx8802_buf_prepare(dev, (struct cx88_buffer*)vb); +} + +static void dvb_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_dev *dev = q->priv_data; + cx8802_buf_queue(dev, (struct cx88_buffer*)vb); +} + +static void dvb_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx8802_dev *dev = q->priv_data; + cx88_free_buffer(dev->pci, (struct cx88_buffer*)vb); +} + +struct videobuf_queue_ops dvb_qops = { + .buf_setup = dvb_buf_setup, + .buf_prepare = dvb_buf_prepare, + .buf_queue = dvb_buf_queue, + .buf_release = dvb_buf_release, +}; + +/* ------------------------------------------------------------------ */ + +static int dvico_fusionhdtv_demod_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x38, 0x39 }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0x24, 0x20 }; + static u8 gpp_ctl_cfg [] = { GPP_CTL, 0x33 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + return 0; +} + +static int dntv_live_dvbt_demod_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { 0x89, 0x38, 0x39 }; + static u8 reset [] = { 0x50, 0x80 }; + static u8 adc_ctl_1_cfg [] = { 0x8E, 0x40 }; + static u8 agc_cfg [] = { 0x67, 0x10, 0x23, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0x40, 0x40 }; + static u8 dntv_extra[] = { 0xB5, 0x7A }; + static u8 capt_range_cfg[] = { 0x75, 0x32 }; + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(2000); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + udelay(2000); + mt352_write(fe, dntv_extra, sizeof(dntv_extra)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + + return 0; +} + +static int mt352_pll_set(struct dvb_frontend* fe, + struct dvb_frontend_parameters* params, + u8* pllbuf) +{ + struct cx8802_dev *dev= fe->dvb->priv; + + pllbuf[0] = dev->core->pll_addr << 1; + dvb_pll_configure(dev->core->pll_desc, pllbuf+1, + params->frequency, + params->u.ofdm.bandwidth); + return 0; +} + +static struct mt352_config dvico_fusionhdtv = { + .demod_address = 0x0F, + .demod_init = dvico_fusionhdtv_demod_init, + .pll_set = mt352_pll_set, +}; + +static struct mt352_config dntv_live_dvbt_config = { + .demod_address = 0x0f, + .demod_init = dntv_live_dvbt_demod_init, + .pll_set = mt352_pll_set, +}; + +#if HAVE_CX22702 +static struct cx22702_config connexant_refboard_config = { + .demod_address = 0x43, + .pll_address = 0x60, + .pll_desc = &dvb_pll_thomson_dtt7579, +}; + +static struct cx22702_config hauppauge_novat_config = { + .demod_address = 0x43, + .pll_address = 0x61, + .pll_desc = &dvb_pll_thomson_dtt759x, +}; +#endif + +#if HAVE_OR51132 +static int or51132_set_ts_param(struct dvb_frontend* fe, + int is_punctured) +{ + struct cx8802_dev *dev= fe->dvb->priv; + dev->ts_gen_cntrl = is_punctured ? 0x04 : 0x00; + return 0; +} + +struct or51132_config pchdtv_hd3000 = { + .demod_address = 0x15, + .pll_address = 0x61, + .pll_desc = &dvb_pll_thomson_dtt7610, + .set_ts_params = or51132_set_ts_param, +}; +#endif + +static int dvb_register(struct cx8802_dev *dev) +{ + /* init struct videobuf_dvb */ + dev->dvb.name = dev->core->name; + dev->ts_gen_cntrl = 0x0c; + + /* init frontend */ + switch (dev->core->board) { +#if HAVE_CX22702 + case CX88_BOARD_HAUPPAUGE_DVB_T1: + dev->dvb.frontend = cx22702_attach(&hauppauge_novat_config, + &dev->core->i2c_adap); + break; + case CX88_BOARD_CONEXANT_DVB_T1: + dev->dvb.frontend = cx22702_attach(&connexant_refboard_config, + &dev->core->i2c_adap); + break; +#endif + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1: + dev->core->pll_addr = 0x61; + dev->core->pll_desc = &dvb_pll_lg_z201; + dev->dvb.frontend = mt352_attach(&dvico_fusionhdtv, + &dev->core->i2c_adap); + break; + case CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS: + dev->core->pll_addr = 0x60; + dev->core->pll_desc = &dvb_pll_thomson_dtt7579; + dev->dvb.frontend = mt352_attach(&dvico_fusionhdtv, + &dev->core->i2c_adap); + break; + case CX88_BOARD_KWORLD_DVB_T: + case CX88_BOARD_DNTV_LIVE_DVB_T: + dev->core->pll_addr = 0x61; + dev->core->pll_desc = &dvb_pll_unknown_1; + dev->dvb.frontend = mt352_attach(&dntv_live_dvbt_config, + &dev->core->i2c_adap); + break; +#if HAVE_OR51132 + case CX88_BOARD_PCHDTV_HD3000: + dev->dvb.frontend = or51132_attach(&pchdtv_hd3000, + &dev->core->i2c_adap); + break; +#endif + default: + printk("%s: The frontend of your DVB/ATSC card isn't supported yet\n" + "%s: you might want to look out for patches here:\n" + "%s: http://dl.bytesex.org/patches/\n", + dev->core->name, dev->core->name, dev->core->name); + break; + } + if (NULL == dev->dvb.frontend) { + printk("%s: frontend initialization failed\n",dev->core->name); + return -1; + } + + if (dev->core->pll_desc) { + dev->dvb.frontend->ops->info.frequency_min = dev->core->pll_desc->min; + dev->dvb.frontend->ops->info.frequency_max = dev->core->pll_desc->max; + } + + /* Copy the board name into the DVB structure */ + strlcpy(dev->dvb.frontend->ops->info.name, + cx88_boards[dev->core->board].name, + sizeof(dev->dvb.frontend->ops->info.name)); + + /* register everything */ + return videobuf_dvb_register(&dev->dvb, THIS_MODULE, dev); +} + +/* ----------------------------------------------------------- */ + +static int __devinit dvb_probe(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8802_dev *dev; + struct cx88_core *core; + int err; + + /* general setup */ + core = cx88_core_get(pci_dev); + if (NULL == core) + return -EINVAL; + + err = -ENODEV; + if (!cx88_boards[core->board].dvb) + goto fail_core; + + err = -ENOMEM; + dev = kmalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + goto fail_core; + memset(dev,0,sizeof(*dev)); + dev->pci = pci_dev; + dev->core = core; + + err = cx8802_init_common(dev); + if (0 != err) + goto fail_free; + + /* dvb stuff */ + printk("%s/2: cx2388x based dvb card\n", core->name); + videobuf_queue_init(&dev->dvb.dvbq, &dvb_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_TOP, + sizeof(struct cx88_buffer), + dev); + err = dvb_register(dev); + if (0 != err) + goto fail_free; + return 0; + + fail_free: + kfree(dev); + fail_core: + cx88_core_put(core,pci_dev); + return err; +} + +static void __devexit dvb_remove(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + + /* dvb */ + videobuf_dvb_unregister(&dev->dvb); + + /* common */ + cx8802_fini_common(dev); + cx88_core_put(dev->core,dev->pci); + kfree(dev); +} + +static struct pci_device_id cx8802_pci_tbl[] = { + { + .vendor = 0x14f1, + .device = 0x8802, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8802_pci_tbl); + +static struct pci_driver dvb_pci_driver = { + .name = "cx88-dvb", + .id_table = cx8802_pci_tbl, + .probe = dvb_probe, + .remove = __devexit_p(dvb_remove), + .suspend = cx8802_suspend_common, + .resume = cx8802_resume_common, +}; + +static int dvb_init(void) +{ + printk(KERN_INFO "cx2388x dvb driver version %d.%d.%d loaded\n", + (CX88_VERSION_CODE >> 16) & 0xff, + (CX88_VERSION_CODE >> 8) & 0xff, + CX88_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "cx2388x: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_register_driver(&dvb_pci_driver); +} + +static void dvb_fini(void) +{ + pci_unregister_driver(&dvb_pci_driver); +} + +module_init(dvb_init); +module_exit(dvb_fini); + +/* + * Local variables: + * c-basic-offset: 8 + * compile-command: "make DVB=1" + * End: + */ diff --git a/drivers/media/video/cx88/cx88-i2c.c b/drivers/media/video/cx88/cx88-i2c.c new file mode 100644 index 000000000000..60800172c026 --- /dev/null +++ b/drivers/media/video/cx88/cx88-i2c.c @@ -0,0 +1,213 @@ +/* + $Id: cx88-i2c.c,v 1.20 2005/02/15 15:59:35 kraxel Exp $ + + cx88-i2c.c -- all the i2c code is here + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 2002 Yurij Sysoev <yurij@naturesoft.net> + (c) 1999-2003 Gerd Knorr <kraxel@bytesex.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include "cx88.h" + +static unsigned int i2c_debug = 0; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug,"enable debug messages [i2c]"); + +static unsigned int i2c_scan = 0; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +#define dprintk(level,fmt, arg...) if (i2c_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, core->name , ## arg) + +/* ----------------------------------------------------------------------- */ + +void cx8800_bit_setscl(void *data, int state) +{ + struct cx88_core *core = data; + + if (state) + core->i2c_state |= 0x02; + else + core->i2c_state &= ~0x02; + cx_write(MO_I2C, core->i2c_state); + cx_read(MO_I2C); +} + +void cx8800_bit_setsda(void *data, int state) +{ + struct cx88_core *core = data; + + if (state) + core->i2c_state |= 0x01; + else + core->i2c_state &= ~0x01; + cx_write(MO_I2C, core->i2c_state); + cx_read(MO_I2C); +} + +static int cx8800_bit_getscl(void *data) +{ + struct cx88_core *core = data; + u32 state; + + state = cx_read(MO_I2C); + return state & 0x02 ? 1 : 0; +} + +static int cx8800_bit_getsda(void *data) +{ + struct cx88_core *core = data; + u32 state; + + state = cx_read(MO_I2C); + return state & 0x01; +} + +/* ----------------------------------------------------------------------- */ + +static int attach_inform(struct i2c_client *client) +{ + struct cx88_core *core = i2c_get_adapdata(client->adapter); + + dprintk(1, "i2c attach [addr=0x%x,client=%s]\n", + client->addr, i2c_clientname(client)); + if (!client->driver->command) + return 0; + + if (core->tuner_type != UNSET) + client->driver->command(client, TUNER_SET_TYPE, &core->tuner_type); + if (core->tda9887_conf) + client->driver->command(client, TDA9887_SET_CONFIG, &core->tda9887_conf); + return 0; +} + +static int detach_inform(struct i2c_client *client) +{ + struct cx88_core *core = i2c_get_adapdata(client->adapter); + + dprintk(1, "i2c detach [client=%s]\n", i2c_clientname(client)); + return 0; +} + +void cx88_call_i2c_clients(struct cx88_core *core, unsigned int cmd, void *arg) +{ + if (0 != core->i2c_rc) + return; + i2c_clients_command(&core->i2c_adap, cmd, arg); +} + +static struct i2c_algo_bit_data cx8800_i2c_algo_template = { + .setsda = cx8800_bit_setsda, + .setscl = cx8800_bit_setscl, + .getsda = cx8800_bit_getsda, + .getscl = cx8800_bit_getscl, + .udelay = 16, + .mdelay = 10, + .timeout = 200, +}; + +/* ----------------------------------------------------------------------- */ + +static struct i2c_adapter cx8800_i2c_adap_template = { + I2C_DEVNAME("cx2388x"), + .owner = THIS_MODULE, + .id = I2C_HW_B_CX2388x, + .client_register = attach_inform, + .client_unregister = detach_inform, +}; + +static struct i2c_client cx8800_i2c_client_template = { + I2C_DEVNAME("cx88xx internal"), +}; + +static char *i2c_devs[128] = { + [ 0x86 >> 1 ] = "tda9887/cx22702", + [ 0xa0 >> 1 ] = "eeprom", + [ 0xc0 >> 1 ] = "tuner (analog)", + [ 0xc2 >> 1 ] = "tuner (analog/dvb)", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i,rc; + + for (i = 0; i < 128; i++) { + c->addr = i; + rc = i2c_master_recv(c,&buf,0); + if (rc < 0) + continue; + printk("%s: i2c scan: found device @ 0x%x [%s]\n", + name, i << 1, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +/* init + register i2c algo-bit adapter */ +int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci) +{ + memcpy(&core->i2c_adap, &cx8800_i2c_adap_template, + sizeof(core->i2c_adap)); + memcpy(&core->i2c_algo, &cx8800_i2c_algo_template, + sizeof(core->i2c_algo)); + memcpy(&core->i2c_client, &cx8800_i2c_client_template, + sizeof(core->i2c_client)); + + if (core->tuner_type != TUNER_ABSENT) + core->i2c_adap.class |= I2C_CLASS_TV_ANALOG; + if (cx88_boards[core->board].dvb) + core->i2c_adap.class |= I2C_CLASS_TV_DIGITAL; + + core->i2c_adap.dev.parent = &pci->dev; + strlcpy(core->i2c_adap.name,core->name,sizeof(core->i2c_adap.name)); + core->i2c_algo.data = core; + i2c_set_adapdata(&core->i2c_adap,core); + core->i2c_adap.algo_data = &core->i2c_algo; + core->i2c_client.adapter = &core->i2c_adap; + + cx8800_bit_setscl(core,1); + cx8800_bit_setsda(core,1); + + core->i2c_rc = i2c_bit_add_bus(&core->i2c_adap); + if (0 == core->i2c_rc) { + dprintk(1, "i2c register ok\n"); + if (i2c_scan) + do_i2c_scan(core->name,&core->i2c_client); + } else + printk("%s: i2c register FAILED\n", core->name); + return core->i2c_rc; +} + +/* ----------------------------------------------------------------------- */ + +EXPORT_SYMBOL(cx88_call_i2c_clients); +EXPORT_SYMBOL(cx88_i2c_init); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-input.c b/drivers/media/video/cx88/cx88-input.c new file mode 100644 index 000000000000..af6ad8cdbdb7 --- /dev/null +++ b/drivers/media/video/cx88/cx88-input.c @@ -0,0 +1,396 @@ +/* + * $Id: cx88-input.c,v 1.9 2005/03/04 09:12:23 kraxel Exp $ + * + * Device driver for GPIO attached remote control interfaces + * on Conexant 2388x based TV/DVB cards. + * + * Copyright (c) 2003 Pavel Machek + * Copyright (c) 2004 Gerd Knorr + * Copyright (c) 2004 Chris Pascoe + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#include <media/ir-common.h> + +#include "cx88.h" + +/* ---------------------------------------------------------------------- */ + +/* DigitalNow DNTV Live DVB-T Remote */ +static IR_KEYTAB_TYPE ir_codes_dntv_live_dvb_t[IR_KEYTAB_SIZE] = { + [ 0x00 ] = KEY_ESC, // 'go up a level?' + [ 0x01 ] = KEY_KP1, // '1' + [ 0x02 ] = KEY_KP2, // '2' + [ 0x03 ] = KEY_KP3, // '3' + [ 0x04 ] = KEY_KP4, // '4' + [ 0x05 ] = KEY_KP5, // '5' + [ 0x06 ] = KEY_KP6, // '6' + [ 0x07 ] = KEY_KP7, // '7' + [ 0x08 ] = KEY_KP8, // '8' + [ 0x09 ] = KEY_KP9, // '9' + [ 0x0a ] = KEY_KP0, // '0' + [ 0x0b ] = KEY_TUNER, // 'tv/fm' + [ 0x0c ] = KEY_SEARCH, // 'scan' + [ 0x0d ] = KEY_STOP, // 'stop' + [ 0x0e ] = KEY_PAUSE, // 'pause' + [ 0x0f ] = KEY_LIST, // 'source' + + [ 0x10 ] = KEY_MUTE, // 'mute' + [ 0x11 ] = KEY_REWIND, // 'backward <<' + [ 0x12 ] = KEY_POWER, // 'power' + [ 0x13 ] = KEY_S, // 'snap' + [ 0x14 ] = KEY_AUDIO, // 'stereo' + [ 0x15 ] = KEY_CLEAR, // 'reset' + [ 0x16 ] = KEY_PLAY, // 'play' + [ 0x17 ] = KEY_ENTER, // 'enter' + [ 0x18 ] = KEY_ZOOM, // 'full screen' + [ 0x19 ] = KEY_FASTFORWARD, // 'forward >>' + [ 0x1a ] = KEY_CHANNELUP, // 'channel +' + [ 0x1b ] = KEY_VOLUMEUP, // 'volume +' + [ 0x1c ] = KEY_INFO, // 'preview' + [ 0x1d ] = KEY_RECORD, // 'record' + [ 0x1e ] = KEY_CHANNELDOWN, // 'channel -' + [ 0x1f ] = KEY_VOLUMEDOWN, // 'volume -' +}; + +/* ---------------------------------------------------------------------- */ + +/* IO-DATA BCTV7E Remote */ +static IR_KEYTAB_TYPE ir_codes_iodata_bctv7e[IR_KEYTAB_SIZE] = { + [ 0x40 ] = KEY_TV, // TV + [ 0x20 ] = KEY_RADIO, // FM + [ 0x60 ] = KEY_EPG, // EPG + [ 0x00 ] = KEY_POWER, // power + + [ 0x50 ] = KEY_KP1, // 1 + [ 0x30 ] = KEY_KP2, // 2 + [ 0x70 ] = KEY_KP3, // 3 + [ 0x10 ] = KEY_L, // Live + + [ 0x48 ] = KEY_KP4, // 4 + [ 0x28 ] = KEY_KP5, // 5 + [ 0x68 ] = KEY_KP6, // 6 + [ 0x08 ] = KEY_T, // Time Shift + + [ 0x58 ] = KEY_KP7, // 7 + [ 0x38 ] = KEY_KP8, // 8 + [ 0x78 ] = KEY_KP9, // 9 + [ 0x18 ] = KEY_PLAYPAUSE, // Play + + [ 0x44 ] = KEY_KP0, // 10 + [ 0x24 ] = KEY_ENTER, // 11 + [ 0x64 ] = KEY_ESC, // 12 + [ 0x04 ] = KEY_M, // Multi + + [ 0x54 ] = KEY_VIDEO, // VIDEO + [ 0x34 ] = KEY_CHANNELUP, // channel + + [ 0x74 ] = KEY_VOLUMEUP, // volume + + [ 0x14 ] = KEY_MUTE, // Mute + + [ 0x4c ] = KEY_S, // SVIDEO + [ 0x2c ] = KEY_CHANNELDOWN, // channel - + [ 0x6c ] = KEY_VOLUMEDOWN, // volume - + [ 0x0c ] = KEY_ZOOM, // Zoom + + [ 0x5c ] = KEY_PAUSE, // pause + [ 0x3c ] = KEY_C, // || (red) + [ 0x7c ] = KEY_RECORD, // recording + [ 0x1c ] = KEY_STOP, // stop + + [ 0x41 ] = KEY_REWIND, // backward << + [ 0x21 ] = KEY_PLAY, // play + [ 0x61 ] = KEY_FASTFORWARD, // forward >> + [ 0x01 ] = KEY_NEXT, // skip >| +}; + +/* ---------------------------------------------------------------------- */ + +struct cx88_IR { + struct cx88_core *core; + struct input_dev input; + struct ir_input_state ir; + char name[32]; + char phys[32]; + + /* sample from gpio pin 16 */ + int sampling; + u32 samples[16]; + int scount; + unsigned long release; + + /* poll external decoder */ + int polling; + struct work_struct work; + struct timer_list timer; + u32 gpio_addr; + u32 last_gpio; + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; +}; + +static int ir_debug = 0; +module_param(ir_debug, int, 0644); /* debug level [IR] */ +MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]"); + +#define ir_dprintk(fmt, arg...) if (ir_debug) \ + printk(KERN_DEBUG "%s IR: " fmt , ir->core->name, ## arg) + +/* ---------------------------------------------------------------------- */ + +static void cx88_ir_handle_key(struct cx88_IR *ir) +{ + struct cx88_core *core = ir->core; + u32 gpio, data; + + /* read gpio value */ + gpio = cx_read(ir->gpio_addr); + if (ir->polling) { + if (ir->last_gpio == gpio) + return; + ir->last_gpio = gpio; + } + + /* extract data */ + data = ir_extract_bits(gpio, ir->mask_keycode); + ir_dprintk("irq gpio=0x%x code=%d | %s%s%s\n", + gpio, data, + ir->polling ? "poll" : "irq", + (gpio & ir->mask_keydown) ? " down" : "", + (gpio & ir->mask_keyup) ? " up" : ""); + + if (ir->mask_keydown) { + /* bit set on keydown */ + if (gpio & ir->mask_keydown) { + ir_input_keydown(&ir->input,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->input,&ir->ir); + } + + } else if (ir->mask_keyup) { + /* bit cleared on keydown */ + if (0 == (gpio & ir->mask_keyup)) { + ir_input_keydown(&ir->input,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->input,&ir->ir); + } + + } else { + /* can't distinguish keydown/up :-/ */ + ir_input_keydown(&ir->input,&ir->ir,data,data); + ir_input_nokey(&ir->input,&ir->ir); + } +} + +static void ir_timer(unsigned long data) +{ + struct cx88_IR *ir = (struct cx88_IR*)data; + + schedule_work(&ir->work); +} + +static void cx88_ir_work(void *data) +{ + struct cx88_IR *ir = data; + unsigned long timeout; + + cx88_ir_handle_key(ir); + timeout = jiffies + (ir->polling * HZ / 1000); + mod_timer(&ir->timer, timeout); +} + +/* ---------------------------------------------------------------------- */ + +int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci) +{ + struct cx88_IR *ir; + IR_KEYTAB_TYPE *ir_codes = NULL; + int ir_type = IR_TYPE_OTHER; + + ir = kmalloc(sizeof(*ir),GFP_KERNEL); + if (NULL == ir) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + + /* detect & configure */ + switch (core->board) { + case CX88_BOARD_DNTV_LIVE_DVB_T: + ir_codes = ir_codes_dntv_live_dvb_t; + ir->gpio_addr = MO_GP1_IO; + ir->mask_keycode = 0x1f; + ir->mask_keyup = 0x60; + ir->polling = 50; // ms + break; + case CX88_BOARD_HAUPPAUGE: + case CX88_BOARD_HAUPPAUGE_DVB_T1: + ir_codes = ir_codes_hauppauge_new; + ir_type = IR_TYPE_RC5; + ir->sampling = 1; + break; + case CX88_BOARD_WINFAST2000XP_EXPERT: + ir_codes = ir_codes_winfast; + ir->gpio_addr = MO_GP0_IO; + ir->mask_keycode = 0x8f8; + ir->mask_keyup = 0x100; + ir->polling = 1; // ms + break; + case CX88_BOARD_IODATA_GVBCTV7E: + ir_codes = ir_codes_iodata_bctv7e; + ir->gpio_addr = MO_GP0_IO; + ir->mask_keycode = 0xfd; + ir->mask_keydown = 0x02; + ir->polling = 5; // ms + break; + } + if (NULL == ir_codes) { + kfree(ir); + return -ENODEV; + } + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "cx88 IR (%s)", + cx88_boards[core->board].name); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(pci)); + + ir_input_init(&ir->input, &ir->ir, ir_type, ir_codes); + ir->input.name = ir->name; + ir->input.phys = ir->phys; + ir->input.id.bustype = BUS_PCI; + ir->input.id.version = 1; + if (pci->subsystem_vendor) { + ir->input.id.vendor = pci->subsystem_vendor; + ir->input.id.product = pci->subsystem_device; + } else { + ir->input.id.vendor = pci->vendor; + ir->input.id.product = pci->device; + } + + /* record handles to ourself */ + ir->core = core; + core->ir = ir; + + if (ir->polling) { + INIT_WORK(&ir->work, cx88_ir_work, ir); + init_timer(&ir->timer); + ir->timer.function = ir_timer; + ir->timer.data = (unsigned long)ir; + schedule_work(&ir->work); + } + if (ir->sampling) { + core->pci_irqmask |= (1<<18); // IR_SMP_INT + cx_write(MO_DDS_IO, 0xa80a80); // 4 kHz sample rate + cx_write(MO_DDSCFG_IO, 0x5); // enable + } + + /* all done */ + input_register_device(&ir->input); + printk("%s: registered IR remote control\n", core->name); + + return 0; +} + +int cx88_ir_fini(struct cx88_core *core) +{ + struct cx88_IR *ir = core->ir; + + /* skip detach on non attached boards */ + if (NULL == ir) + return 0; + + if (ir->polling) { + del_timer(&ir->timer); + flush_scheduled_work(); + } + + input_unregister_device(&ir->input); + kfree(ir); + + /* done */ + core->ir = NULL; + return 0; +} + +/* ---------------------------------------------------------------------- */ + +void cx88_ir_irq(struct cx88_core *core) +{ + struct cx88_IR *ir = core->ir; + u32 samples,rc5; + int i; + + if (NULL == ir) + return; + if (!ir->sampling) + return; + + samples = cx_read(MO_SAMPLE_IO); + if (0 != samples && 0xffffffff != samples) { + /* record sample data */ + if (ir->scount < ARRAY_SIZE(ir->samples)) + ir->samples[ir->scount++] = samples; + return; + } + if (!ir->scount) { + /* nothing to sample */ + if (ir->ir.keypressed && time_after(jiffies,ir->release)) + ir_input_nokey(&ir->input,&ir->ir); + return; + } + + /* have a complete sample */ + if (ir->scount < ARRAY_SIZE(ir->samples)) + ir->samples[ir->scount++] = samples; + for (i = 0; i < ir->scount; i++) + ir->samples[i] = ~ir->samples[i]; + if (ir_debug) + ir_dump_samples(ir->samples,ir->scount); + + /* decode it */ + switch (core->board) { + case CX88_BOARD_HAUPPAUGE: + case CX88_BOARD_HAUPPAUGE_DVB_T1: + rc5 = ir_decode_biphase(ir->samples,ir->scount,5,7); + ir_dprintk("biphase decoded: %x\n",rc5); + if ((rc5 & 0xfffff000) != 0x3000) + break; + ir_input_keydown(&ir->input, &ir->ir, rc5 & 0x3f, rc5); + ir->release = jiffies + msecs_to_jiffies(120); + break; + } + + ir->scount = 0; + return; +} + +/* ---------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Pavel Machek, Chris Pascoe"); +MODULE_DESCRIPTION("input driver for cx88 GPIO-based IR remote controls"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-mpeg.c b/drivers/media/video/cx88/cx88-mpeg.c new file mode 100644 index 000000000000..07aae1899e17 --- /dev/null +++ b/drivers/media/video/cx88/cx88-mpeg.c @@ -0,0 +1,466 @@ +/* + * $Id: cx88-mpeg.c,v 1.25 2005/03/07 14:18:00 kraxel Exp $ + * + * Support for the mpeg transport stream transfers + * PCI function #2 of the cx2388x. + * + * (c) 2004 Jelle Foks <jelle@foks.8m.com> + * (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au> + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <asm/delay.h> + +#include "cx88.h" + +/* ------------------------------------------------------------------ */ + +MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards"); +MODULE_AUTHOR("Jelle Foks <jelle@foks.8m.com>"); +MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int debug = 0; +module_param(debug,int,0644); +MODULE_PARM_DESC(debug,"enable debug messages [mpeg]"); + +#define dprintk(level,fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "%s/2: " fmt, dev->core->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int cx8802_start_dma(struct cx8802_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + dprintk(1, "cx8802_start_mpegport_dma %d\n", buf->vb.width); + + /* setup fifo + format */ + cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28], + dev->ts_packet_size, buf->risc.dma); + + /* write TS length to chip */ + cx_write(MO_TS_LNGTH, buf->vb.width); + +#if 1 + /* FIXME: this needs a review. + * also: move to cx88-blackbird + cx88-dvb source files? */ + + if (cx88_boards[core->board].dvb) { + /* negedge driven & software reset */ + cx_write(TS_GEN_CNTRL, 0x40); + udelay(100); + cx_write(MO_PINMUX_IO, 0x00); + cx_write(TS_HW_SOP_CNTRL,47<<16|188<<4|0x00); + cx_write(TS_SOP_STAT,0x00); + cx_write(TS_GEN_CNTRL, dev->ts_gen_cntrl); + udelay(100); + } + + if (cx88_boards[core->board].blackbird) { + cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */ + + // cx_write(TS_F2_CMD_STAT_MM, 0x2900106); /* F2_CMD_STAT_MM defaults + master + memory space */ + cx_write(TS_GEN_CNTRL, 0x46); /* punctured clock TS & posedge driven & software reset */ + udelay(100); + + cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */ + //cx_write(TS_HW_SOP_CNTRL, 0x2F0BC0); /* mpeg start byte ts: 0x2F0BC0 ? */ + cx_write(TS_VALERR_CNTRL, 0x2000); + + cx_write(TS_GEN_CNTRL, 0x06); /* punctured clock TS & posedge driven */ + udelay(100); + } +#endif + + /* reset counter */ + cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET); + q->count = 1; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, core->pci_irqmask | 0x04); + cx_write(MO_TS_INTMSK, 0x1f0011); + + /* start dma */ + cx_write(MO_DEV_CNTRL2, (1<<5)); /* FIXME: s/write/set/ ??? */ + cx_write(MO_TS_DMACNTRL, 0x11); + return 0; +} + +static int cx8802_stop_dma(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* stop dma */ + cx_clear(MO_TS_DMACNTRL, 0x11); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, 0x000004); + cx_clear(MO_TS_INTMSK, 0x1f0011); + + /* Reset the controller */ + cx_write(TS_GEN_CNTRL, 0xcd); + return 0; +} + +static int cx8802_restart_queue(struct cx8802_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + struct list_head *item; + + if (list_empty(&q->active)) + return 0; + + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + cx8802_start_dma(dev, q, buf); + list_for_each(item,&q->active) { + buf = list_entry(item, struct cx88_buffer, vb.queue); + buf->count = q->count++; + } + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +/* ------------------------------------------------------------------ */ + +int cx8802_buf_prepare(struct cx8802_dev *dev, struct cx88_buffer *buf) +{ + int size = dev->ts_packet_size * dev->ts_packet_count; + int rc; + + dprintk(1, "%s: %p\n", __FUNCTION__, buf); + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = dev->ts_packet_size; + buf->vb.height = dev->ts_packet_count; + buf->vb.size = size; + buf->vb.field = V4L2_FIELD_TOP; + + if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) + goto fail; + cx88_risc_databuffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + buf->vb.width, buf->vb.height); + } + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + cx88_free_buffer(dev->pci,buf); + return rc; +} + +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf) +{ + struct cx88_buffer *prev; + struct cx88_dmaqueue *q = &dev->mpegq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + cx8802_start_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] %s - first active\n", + buf, buf->vb.i, __FUNCTION__); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] %s - append to active\n", + buf, buf->vb.i, __FUNCTION__); + } +} + +/* ----------------------------------------------------------- */ + +static void do_cancel_buffers(struct cx8802_dev *dev, char *reason, int restart) +{ + struct cx88_dmaqueue *q = &dev->mpegq; + struct cx88_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = STATE_ERROR; + wake_up(&buf->vb.done); + dprintk(1,"[%p/%d] %s - dma=0x%08lx\n", + buf, buf->vb.i, reason, (unsigned long)buf->risc.dma); + } + if (restart) + cx8802_restart_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +void cx8802_cancel_buffers(struct cx8802_dev *dev) +{ + struct cx88_dmaqueue *q = &dev->mpegq; + + del_timer_sync(&q->timeout); + cx8802_stop_dma(dev); + do_cancel_buffers(dev,"cancel",0); +} + +static void cx8802_timeout(unsigned long data) +{ + struct cx8802_dev *dev = (struct cx8802_dev*)data; + + dprintk(1, "%s\n",__FUNCTION__); + + if (debug) + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); + cx8802_stop_dma(dev); + do_cancel_buffers(dev,"timeout",1); +} + +static void cx8802_mpeg_irq(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + u32 status, mask, count; + + status = cx_read(MO_TS_INTSTAT); + mask = cx_read(MO_TS_INTMSK); + if (0 == (status & mask)) + return; + + cx_write(MO_TS_INTSTAT, status); + if (debug || (status & mask & ~0xff)) + cx88_print_irqbits(core->name, "irq mpeg ", + cx88_mpeg_irqs, status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + printk(KERN_WARNING "%s: mpeg risc op code error\n",core->name); + cx_clear(MO_TS_DMACNTRL, 0x11); + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]); + } + + /* risc1 y */ + if (status & 0x01) { + spin_lock(&dev->slock); + count = cx_read(MO_TS_GPCNT); + cx88_wakeup(dev->core, &dev->mpegq, count); + spin_unlock(&dev->slock); + } + + /* risc2 y */ + if (status & 0x10) { + spin_lock(&dev->slock); + cx8802_restart_queue(dev,&dev->mpegq); + spin_unlock(&dev->slock); + } + + /* other general errors */ + if (status & 0x1f0100) { + spin_lock(&dev->slock); + cx8802_stop_dma(dev); + cx8802_restart_queue(dev,&dev->mpegq); + spin_unlock(&dev->slock); + } +} + +static irqreturn_t cx8802_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cx8802_dev *dev = dev_id; + struct cx88_core *core = dev->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + status = cx_read(MO_PCI_INTSTAT) & (core->pci_irqmask | 0x04); + if (0 == status) + goto out; + handled = 1; + cx_write(MO_PCI_INTSTAT, status); + + if (status & core->pci_irqmask) + cx88_core_irq(core,status); + if (status & 0x04) + cx8802_mpeg_irq(dev); + }; + if (10 == loop) { + printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", + core->name); + cx_write(MO_PCI_INTMSK,0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +int cx8802_init_common(struct cx8802_dev *dev) +{ + struct cx88_core *core = dev->core; + int err; + + /* pci init */ + if (pci_enable_device(dev->pci)) + return -EIO; + pci_set_master(dev->pci); + if (!pci_dma_supported(dev->pci,0xffffffff)) { + printk("%s/2: Oops: no 32bit PCI DMA ???\n",dev->core->name); + return -EIO; + } + + pci_read_config_byte(dev->pci, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/2: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%lx\n", dev->core->name, + pci_name(dev->pci), dev->pci_rev, dev->pci->irq, + dev->pci_lat,pci_resource_start(dev->pci,0)); + + /* initialize driver struct */ + init_MUTEX(&dev->lock); + spin_lock_init(&dev->slock); + + /* init dma queue */ + INIT_LIST_HEAD(&dev->mpegq.active); + INIT_LIST_HEAD(&dev->mpegq.queued); + dev->mpegq.timeout.function = cx8802_timeout; + dev->mpegq.timeout.data = (unsigned long)dev; + init_timer(&dev->mpegq.timeout); + cx88_risc_stopper(dev->pci,&dev->mpegq.stopper, + MO_TS_DMACNTRL,0x11,0x00); + + /* get irq */ + err = request_irq(dev->pci->irq, cx8802_irq, + SA_SHIRQ | SA_INTERRUPT, dev->core->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + dev->core->name, dev->pci->irq); + return err; + } + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + /* everything worked */ + pci_set_drvdata(dev->pci,dev); + return 0; +} + +void cx8802_fini_common(struct cx8802_dev *dev) +{ + cx8802_stop_dma(dev); + pci_disable_device(dev->pci); + + /* unregister stuff */ + free_irq(dev->pci->irq, dev); + pci_set_drvdata(dev->pci, NULL); + + /* free memory */ + btcx_riscmem_free(dev->pci,&dev->mpegq.stopper); +} + +/* ----------------------------------------------------------- */ + +int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop mpeg dma */ + spin_lock(&dev->slock); + if (!list_empty(&dev->mpegq.active)) { + printk("%s: suspend mpeg\n", core->name); + cx8802_stop_dma(dev); + del_timer(&dev->mpegq.timeout); + } + spin_unlock(&dev->slock); + +#if 1 + /* FIXME -- shutdown device */ + cx88_shutdown(dev->core); +#endif + + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +int cx8802_resume_common(struct pci_dev *pci_dev) +{ + struct cx8802_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + if (dev->state.disabled) { + pci_enable_device(pci_dev); + dev->state.disabled = 0; + } + pci_set_power_state(pci_dev, PCI_D0); + pci_restore_state(pci_dev); + +#if 1 + /* FIXME: re-initialize hardware */ + cx88_reset(dev->core); +#endif + + /* restart video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->mpegq.active)) { + printk("%s: resume mpeg\n", core->name); + cx8802_restart_queue(dev,&dev->mpegq); + } + spin_unlock(&dev->slock); + + return 0; +} + +/* ----------------------------------------------------------- */ + +EXPORT_SYMBOL(cx8802_buf_prepare); +EXPORT_SYMBOL(cx8802_buf_queue); +EXPORT_SYMBOL(cx8802_cancel_buffers); + +EXPORT_SYMBOL(cx8802_init_common); +EXPORT_SYMBOL(cx8802_fini_common); + +EXPORT_SYMBOL(cx8802_suspend_common); +EXPORT_SYMBOL(cx8802_resume_common); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-reg.h b/drivers/media/video/cx88/cx88-reg.h new file mode 100644 index 000000000000..8638ce57d84c --- /dev/null +++ b/drivers/media/video/cx88/cx88-reg.h @@ -0,0 +1,787 @@ +/* + $Id: cx88-reg.h,v 1.6 2004/10/13 10:39:00 kraxel Exp $ + + cx88x-hw.h - CX2388x register offsets + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + 2001 Michael Eskin + 2002 Yurij Sysoev <yurij@naturesoft.net> + 2003 Gerd Knorr <kraxel@bytesex.org> + + 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. +*/ + +#ifndef _CX88_REG_H_ +#define _CX88_REG_H_ + +/* ---------------------------------------------------------------------- */ +/* PCI IDs and config space */ + +#ifndef PCI_VENDOR_ID_CONEXANT +# define PCI_VENDOR_ID_CONEXANT 0x14F1 +#endif +#ifndef PCI_DEVICE_ID_CX2300_VID +# define PCI_DEVICE_ID_CX2300_VID 0x8800 +#endif + +#define CX88X_DEVCTRL 0x40 +#define CX88X_EN_TBFX 0x02 +#define CX88X_EN_VSFX 0x04 + + +/* ---------------------------------------------------------------------- */ +/* DMA Controller registers */ + +#define MO_PDMA_STHRSH 0x200000 // Source threshold +#define MO_PDMA_STADRS 0x200004 // Source target address +#define MO_PDMA_SIADRS 0x200008 // Source internal address +#define MO_PDMA_SCNTRL 0x20000C // Source control +#define MO_PDMA_DTHRSH 0x200010 // Destination threshold +#define MO_PDMA_DTADRS 0x200014 // Destination target address +#define MO_PDMA_DIADRS 0x200018 // Destination internal address +#define MO_PDMA_DCNTRL 0x20001C // Destination control +#define MO_LD_SSID 0x200030 // Load subsystem ID +#define MO_DEV_CNTRL2 0x200034 // Device control +#define MO_PCI_INTMSK 0x200040 // PCI interrupt mask +#define MO_PCI_INTSTAT 0x200044 // PCI interrupt status +#define MO_PCI_INTMSTAT 0x200048 // PCI interrupt masked status +#define MO_VID_INTMSK 0x200050 // Video interrupt mask +#define MO_VID_INTSTAT 0x200054 // Video interrupt status +#define MO_VID_INTMSTAT 0x200058 // Video interrupt masked status +#define MO_VID_INTSSTAT 0x20005C // Video interrupt set status +#define MO_AUD_INTMSK 0x200060 // Audio interrupt mask +#define MO_AUD_INTSTAT 0x200064 // Audio interrupt status +#define MO_AUD_INTMSTAT 0x200068 // Audio interrupt masked status +#define MO_AUD_INTSSTAT 0x20006C // Audio interrupt set status +#define MO_TS_INTMSK 0x200070 // Transport stream interrupt mask +#define MO_TS_INTSTAT 0x200074 // Transport stream interrupt status +#define MO_TS_INTMSTAT 0x200078 // Transport stream interrupt mask status +#define MO_TS_INTSSTAT 0x20007C // Transport stream interrupt set status +#define MO_VIP_INTMSK 0x200080 // VIP interrupt mask +#define MO_VIP_INTSTAT 0x200084 // VIP interrupt status +#define MO_VIP_INTMSTAT 0x200088 // VIP interrupt masked status +#define MO_VIP_INTSSTAT 0x20008C // VIP interrupt set status +#define MO_GPHST_INTMSK 0x200090 // Host interrupt mask +#define MO_GPHST_INTSTAT 0x200094 // Host interrupt status +#define MO_GPHST_INTMSTAT 0x200098 // Host interrupt masked status +#define MO_GPHST_INTSSTAT 0x20009C // Host interrupt set status + +// DMA Channels 1-6 belong to SPIPE +#define MO_DMA7_PTR1 0x300018 // {24}RW* DMA Current Ptr : Ch#7 +#define MO_DMA8_PTR1 0x30001C // {24}RW* DMA Current Ptr : Ch#8 + +// DMA Channels 9-20 belong to SPIPE +#define MO_DMA21_PTR1 0x300080 // {24}R0* DMA Current Ptr : Ch#21 +#define MO_DMA22_PTR1 0x300084 // {24}R0* DMA Current Ptr : Ch#22 +#define MO_DMA23_PTR1 0x300088 // {24}R0* DMA Current Ptr : Ch#23 +#define MO_DMA24_PTR1 0x30008C // {24}R0* DMA Current Ptr : Ch#24 +#define MO_DMA25_PTR1 0x300090 // {24}R0* DMA Current Ptr : Ch#25 +#define MO_DMA26_PTR1 0x300094 // {24}R0* DMA Current Ptr : Ch#26 +#define MO_DMA27_PTR1 0x300098 // {24}R0* DMA Current Ptr : Ch#27 +#define MO_DMA28_PTR1 0x30009C // {24}R0* DMA Current Ptr : Ch#28 +#define MO_DMA29_PTR1 0x3000A0 // {24}R0* DMA Current Ptr : Ch#29 +#define MO_DMA30_PTR1 0x3000A4 // {24}R0* DMA Current Ptr : Ch#30 +#define MO_DMA31_PTR1 0x3000A8 // {24}R0* DMA Current Ptr : Ch#31 +#define MO_DMA32_PTR1 0x3000AC // {24}R0* DMA Current Ptr : Ch#32 + +#define MO_DMA21_PTR2 0x3000C0 // {24}RW* DMA Tab Ptr : Ch#21 +#define MO_DMA22_PTR2 0x3000C4 // {24}RW* DMA Tab Ptr : Ch#22 +#define MO_DMA23_PTR2 0x3000C8 // {24}RW* DMA Tab Ptr : Ch#23 +#define MO_DMA24_PTR2 0x3000CC // {24}RW* DMA Tab Ptr : Ch#24 +#define MO_DMA25_PTR2 0x3000D0 // {24}RW* DMA Tab Ptr : Ch#25 +#define MO_DMA26_PTR2 0x3000D4 // {24}RW* DMA Tab Ptr : Ch#26 +#define MO_DMA27_PTR2 0x3000D8 // {24}RW* DMA Tab Ptr : Ch#27 +#define MO_DMA28_PTR2 0x3000DC // {24}RW* DMA Tab Ptr : Ch#28 +#define MO_DMA29_PTR2 0x3000E0 // {24}RW* DMA Tab Ptr : Ch#29 +#define MO_DMA30_PTR2 0x3000E4 // {24}RW* DMA Tab Ptr : Ch#30 +#define MO_DMA31_PTR2 0x3000E8 // {24}RW* DMA Tab Ptr : Ch#31 +#define MO_DMA32_PTR2 0x3000EC // {24}RW* DMA Tab Ptr : Ch#32 + +#define MO_DMA21_CNT1 0x300100 // {11}RW* DMA Buffer Size : Ch#21 +#define MO_DMA22_CNT1 0x300104 // {11}RW* DMA Buffer Size : Ch#22 +#define MO_DMA23_CNT1 0x300108 // {11}RW* DMA Buffer Size : Ch#23 +#define MO_DMA24_CNT1 0x30010C // {11}RW* DMA Buffer Size : Ch#24 +#define MO_DMA25_CNT1 0x300110 // {11}RW* DMA Buffer Size : Ch#25 +#define MO_DMA26_CNT1 0x300114 // {11}RW* DMA Buffer Size : Ch#26 +#define MO_DMA27_CNT1 0x300118 // {11}RW* DMA Buffer Size : Ch#27 +#define MO_DMA28_CNT1 0x30011C // {11}RW* DMA Buffer Size : Ch#28 +#define MO_DMA29_CNT1 0x300120 // {11}RW* DMA Buffer Size : Ch#29 +#define MO_DMA30_CNT1 0x300124 // {11}RW* DMA Buffer Size : Ch#30 +#define MO_DMA31_CNT1 0x300128 // {11}RW* DMA Buffer Size : Ch#31 +#define MO_DMA32_CNT1 0x30012C // {11}RW* DMA Buffer Size : Ch#32 + +#define MO_DMA21_CNT2 0x300140 // {11}RW* DMA Table Size : Ch#21 +#define MO_DMA22_CNT2 0x300144 // {11}RW* DMA Table Size : Ch#22 +#define MO_DMA23_CNT2 0x300148 // {11}RW* DMA Table Size : Ch#23 +#define MO_DMA24_CNT2 0x30014C // {11}RW* DMA Table Size : Ch#24 +#define MO_DMA25_CNT2 0x300150 // {11}RW* DMA Table Size : Ch#25 +#define MO_DMA26_CNT2 0x300154 // {11}RW* DMA Table Size : Ch#26 +#define MO_DMA27_CNT2 0x300158 // {11}RW* DMA Table Size : Ch#27 +#define MO_DMA28_CNT2 0x30015C // {11}RW* DMA Table Size : Ch#28 +#define MO_DMA29_CNT2 0x300160 // {11}RW* DMA Table Size : Ch#29 +#define MO_DMA30_CNT2 0x300164 // {11}RW* DMA Table Size : Ch#30 +#define MO_DMA31_CNT2 0x300168 // {11}RW* DMA Table Size : Ch#31 +#define MO_DMA32_CNT2 0x30016C // {11}RW* DMA Table Size : Ch#32 + + +/* ---------------------------------------------------------------------- */ +/* Video registers */ + +#define MO_VIDY_DMA 0x310000 // {64}RWp Video Y +#define MO_VIDU_DMA 0x310008 // {64}RWp Video U +#define MO_VIDV_DMA 0x310010 // {64}RWp Video V +#define MO_VBI_DMA 0x310018 // {64}RWp VBI (Vertical blanking interval) + +#define MO_DEVICE_STATUS 0x310100 +#define MO_INPUT_FORMAT 0x310104 +#define MO_AGC_BURST 0x31010c +#define MO_CONTR_BRIGHT 0x310110 +#define MO_UV_SATURATION 0x310114 +#define MO_HUE 0x310118 +#define MO_HTOTAL 0x310120 +#define MO_HDELAY_EVEN 0x310124 +#define MO_HDELAY_ODD 0x310128 +#define MO_VDELAY_ODD 0x31012c +#define MO_VDELAY_EVEN 0x310130 +#define MO_HACTIVE_EVEN 0x31013c +#define MO_HACTIVE_ODD 0x310140 +#define MO_VACTIVE_EVEN 0x310144 +#define MO_VACTIVE_ODD 0x310148 +#define MO_HSCALE_EVEN 0x31014c +#define MO_HSCALE_ODD 0x310150 +#define MO_VSCALE_EVEN 0x310154 +#define MO_FILTER_EVEN 0x31015c +#define MO_VSCALE_ODD 0x310158 +#define MO_FILTER_ODD 0x310160 +#define MO_OUTPUT_FORMAT 0x310164 + +#define MO_PLL_REG 0x310168 // PLL register +#define MO_PLL_ADJ_CTRL 0x31016c // PLL adjust control register +#define MO_SCONV_REG 0x310170 // sample rate conversion register +#define MO_SCONV_FIFO 0x310174 // sample rate conversion fifo +#define MO_SUB_STEP 0x310178 // subcarrier step size +#define MO_SUB_STEP_DR 0x31017c // subcarrier step size for DR line + +#define MO_CAPTURE_CTRL 0x310180 // capture control +#define MO_COLOR_CTRL 0x310184 +#define MO_VBI_PACKET 0x310188 // vbi packet size / delay +#define MO_FIELD_COUNT 0x310190 // field counter +#define MO_VIP_CONFIG 0x310194 +#define MO_VBOS_CONTROL 0x3101a8 + +#define MO_AGC_BACK_VBI 0x310200 +#define MO_AGC_SYNC_TIP1 0x310208 + +#define MO_VIDY_GPCNT 0x31C020 // {16}RO Video Y general purpose counter +#define MO_VIDU_GPCNT 0x31C024 // {16}RO Video U general purpose counter +#define MO_VIDV_GPCNT 0x31C028 // {16}RO Video V general purpose counter +#define MO_VBI_GPCNT 0x31C02C // {16}RO VBI general purpose counter +#define MO_VIDY_GPCNTRL 0x31C030 // {2}WO Video Y general purpose control +#define MO_VIDU_GPCNTRL 0x31C034 // {2}WO Video U general purpose control +#define MO_VIDV_GPCNTRL 0x31C038 // {2}WO Video V general purpose control +#define MO_VBI_GPCNTRL 0x31C03C // {2}WO VBI general purpose counter +#define MO_VID_DMACNTRL 0x31C040 // {8}RW Video DMA control +#define MO_VID_XFR_STAT 0x31C044 // {1}RO Video transfer status + + +/* ---------------------------------------------------------------------- */ +/* audio registers */ + +#define MO_AUDD_DMA 0x320000 // {64}RWp Audio downstream +#define MO_AUDU_DMA 0x320008 // {64}RWp Audio upstream +#define MO_AUDR_DMA 0x320010 // {64}RWp Audio RDS (downstream) +#define MO_AUDD_GPCNT 0x32C020 // {16}RO Audio down general purpose counter +#define MO_AUDU_GPCNT 0x32C024 // {16}RO Audio up general purpose counter +#define MO_AUDR_GPCNT 0x32C028 // {16}RO Audio RDS general purpose counter +#define MO_AUDD_GPCNTRL 0x32C030 // {2}WO Audio down general purpose control +#define MO_AUDU_GPCNTRL 0x32C034 // {2}WO Audio up general purpose control +#define MO_AUDR_GPCNTRL 0x32C038 // {2}WO Audio RDS general purpose control +#define MO_AUD_DMACNTRL 0x32C040 // {6}RW Audio DMA control +#define MO_AUD_XFR_STAT 0x32C044 // {1}RO Audio transfer status +#define MO_AUDD_LNGTH 0x32C048 // {12}RW Audio down line length +#define MO_AUDR_LNGTH 0x32C04C // {12}RW Audio RDS line length + +#define AUD_INIT 0x320100 +#define AUD_INIT_LD 0x320104 +#define AUD_SOFT_RESET 0x320108 +#define AUD_I2SINPUTCNTL 0x320120 +#define AUD_BAUDRATE 0x320124 +#define AUD_I2SOUTPUTCNTL 0x320128 +#define AAGC_HYST 0x320134 +#define AAGC_GAIN 0x320138 +#define AAGC_DEF 0x32013c +#define AUD_IIR1_0_SEL 0x320150 +#define AUD_IIR1_0_SHIFT 0x320154 +#define AUD_IIR1_1_SEL 0x320158 +#define AUD_IIR1_1_SHIFT 0x32015c +#define AUD_IIR1_2_SEL 0x320160 +#define AUD_IIR1_2_SHIFT 0x320164 +#define AUD_IIR1_3_SEL 0x320168 +#define AUD_IIR1_3_SHIFT 0x32016c +#define AUD_IIR1_4_SEL 0x320170 +#define AUD_IIR1_4_SHIFT 0x32017c +#define AUD_IIR1_5_SEL 0x320180 +#define AUD_IIR1_5_SHIFT 0x320184 +#define AUD_IIR2_0_SEL 0x320190 +#define AUD_IIR2_0_SHIFT 0x320194 +#define AUD_IIR2_1_SEL 0x320198 +#define AUD_IIR2_1_SHIFT 0x32019c +#define AUD_IIR2_2_SEL 0x3201a0 +#define AUD_IIR2_2_SHIFT 0x3201a4 +#define AUD_IIR2_3_SEL 0x3201a8 +#define AUD_IIR2_3_SHIFT 0x3201ac +#define AUD_IIR3_0_SEL 0x3201c0 +#define AUD_IIR3_0_SHIFT 0x3201c4 +#define AUD_IIR3_1_SEL 0x3201c8 +#define AUD_IIR3_1_SHIFT 0x3201cc +#define AUD_IIR3_2_SEL 0x3201d0 +#define AUD_IIR3_2_SHIFT 0x3201d4 +#define AUD_IIR4_0_SEL 0x3201e0 +#define AUD_IIR4_0_SHIFT 0x3201e4 +#define AUD_IIR4_1_SEL 0x3201e8 +#define AUD_IIR4_1_SHIFT 0x3201ec +#define AUD_IIR4_2_SEL 0x3201f0 +#define AUD_IIR4_2_SHIFT 0x3201f4 +#define AUD_IIR4_0_CA0 0x320200 +#define AUD_IIR4_0_CA1 0x320204 +#define AUD_IIR4_0_CA2 0x320208 +#define AUD_IIR4_0_CB0 0x32020c +#define AUD_IIR4_0_CB1 0x320210 +#define AUD_IIR4_1_CA0 0x320214 +#define AUD_IIR4_1_CA1 0x320218 +#define AUD_IIR4_1_CA2 0x32021c +#define AUD_IIR4_1_CB0 0x320220 +#define AUD_IIR4_1_CB1 0x320224 +#define AUD_IIR4_2_CA0 0x320228 +#define AUD_IIR4_2_CA1 0x32022c +#define AUD_IIR4_2_CA2 0x320230 +#define AUD_IIR4_2_CB0 0x320234 +#define AUD_IIR4_2_CB1 0x320238 +#define AUD_HP_MD_IIR4_1 0x320250 +#define AUD_HP_PROG_IIR4_1 0x320254 +#define AUD_FM_MODE_ENABLE 0x320258 +#define AUD_POLY0_DDS_CONSTANT 0x320270 +#define AUD_DN0_FREQ 0x320274 +#define AUD_DN1_FREQ 0x320278 +#define AUD_DN1_FREQ_SHIFT 0x32027c +#define AUD_DN1_AFC 0x320280 +#define AUD_DN1_SRC_SEL 0x320284 +#define AUD_DN1_SHFT 0x320288 +#define AUD_DN2_FREQ 0x32028c +#define AUD_DN2_FREQ_SHIFT 0x320290 +#define AUD_DN2_AFC 0x320294 +#define AUD_DN2_SRC_SEL 0x320298 +#define AUD_DN2_SHFT 0x32029c +#define AUD_CRDC0_SRC_SEL 0x320300 +#define AUD_CRDC0_SHIFT 0x320304 +#define AUD_CORDIC_SHIFT_0 0x320308 +#define AUD_CRDC1_SRC_SEL 0x32030c +#define AUD_CRDC1_SHIFT 0x320310 +#define AUD_CORDIC_SHIFT_1 0x320314 +#define AUD_DCOC_0_SRC 0x320320 +#define AUD_DCOC0_SHIFT 0x320324 +#define AUD_DCOC_0_SHIFT_IN0 0x320328 +#define AUD_DCOC_0_SHIFT_IN1 0x32032c +#define AUD_DCOC_1_SRC 0x320330 +#define AUD_DCOC1_SHIFT 0x320334 +#define AUD_DCOC_1_SHIFT_IN0 0x320338 +#define AUD_DCOC_1_SHIFT_IN1 0x32033c +#define AUD_DCOC_2_SRC 0x320340 +#define AUD_DCOC2_SHIFT 0x320344 +#define AUD_DCOC_2_SHIFT_IN0 0x320348 +#define AUD_DCOC_2_SHIFT_IN1 0x32034c +#define AUD_DCOC_PASS_IN 0x320350 +#define AUD_PDET_SRC 0x320370 +#define AUD_PDET_SHIFT 0x320374 +#define AUD_PILOT_BQD_1_K0 0x320380 +#define AUD_PILOT_BQD_1_K1 0x320384 +#define AUD_PILOT_BQD_1_K2 0x320388 +#define AUD_PILOT_BQD_1_K3 0x32038c +#define AUD_PILOT_BQD_1_K4 0x320390 +#define AUD_PILOT_BQD_2_K0 0x320394 +#define AUD_PILOT_BQD_2_K1 0x320398 +#define AUD_PILOT_BQD_2_K2 0x32039c +#define AUD_PILOT_BQD_2_K3 0x3203a0 +#define AUD_PILOT_BQD_2_K4 0x3203a4 +#define AUD_THR_FR 0x3203c0 +#define AUD_X_PROG 0x3203c4 +#define AUD_Y_PROG 0x3203c8 +#define AUD_HARMONIC_MULT 0x3203cc +#define AUD_C1_UP_THR 0x3203d0 +#define AUD_C1_LO_THR 0x3203d4 +#define AUD_C2_UP_THR 0x3203d8 +#define AUD_C2_LO_THR 0x3203dc +#define AUD_PLL_EN 0x320400 +#define AUD_PLL_SRC 0x320404 +#define AUD_PLL_SHIFT 0x320408 +#define AUD_PLL_IF_SEL 0x32040c +#define AUD_PLL_IF_SHIFT 0x320410 +#define AUD_BIQUAD_PLL_K0 0x320414 +#define AUD_BIQUAD_PLL_K1 0x320418 +#define AUD_BIQUAD_PLL_K2 0x32041c +#define AUD_BIQUAD_PLL_K3 0x320420 +#define AUD_BIQUAD_PLL_K4 0x320424 +#define AUD_DEEMPH0_SRC_SEL 0x320440 +#define AUD_DEEMPH0_SHIFT 0x320444 +#define AUD_DEEMPH0_G0 0x320448 +#define AUD_DEEMPH0_A0 0x32044c +#define AUD_DEEMPH0_B0 0x320450 +#define AUD_DEEMPH0_A1 0x320454 +#define AUD_DEEMPH0_B1 0x320458 +#define AUD_DEEMPH1_SRC_SEL 0x32045c +#define AUD_DEEMPH1_SHIFT 0x320460 +#define AUD_DEEMPH1_G0 0x320464 +#define AUD_DEEMPH1_A0 0x320468 +#define AUD_DEEMPH1_B0 0x32046c +#define AUD_DEEMPH1_A1 0x320470 +#define AUD_DEEMPH1_B1 0x320474 +#define AUD_OUT0_SEL 0x320490 +#define AUD_OUT0_SHIFT 0x320494 +#define AUD_OUT1_SEL 0x320498 +#define AUD_OUT1_SHIFT 0x32049c +#define AUD_RDSI_SEL 0x3204a0 +#define AUD_RDSI_SHIFT 0x3204a4 +#define AUD_RDSQ_SEL 0x3204a8 +#define AUD_RDSQ_SHIFT 0x3204ac +#define AUD_DBX_IN_GAIN 0x320500 +#define AUD_DBX_WBE_GAIN 0x320504 +#define AUD_DBX_SE_GAIN 0x320508 +#define AUD_DBX_RMS_WBE 0x32050c +#define AUD_DBX_RMS_SE 0x320510 +#define AUD_DBX_SE_BYPASS 0x320514 +#define AUD_FAWDETCTL 0x320530 +#define AUD_FAWDETWINCTL 0x320534 +#define AUD_DEEMPHGAIN_R 0x320538 +#define AUD_DEEMPHNUMER1_R 0x32053c +#define AUD_DEEMPHNUMER2_R 0x320540 +#define AUD_DEEMPHDENOM1_R 0x320544 +#define AUD_DEEMPHDENOM2_R 0x320548 +#define AUD_ERRLOGPERIOD_R 0x32054c +#define AUD_ERRINTRPTTHSHLD1_R 0x320550 +#define AUD_ERRINTRPTTHSHLD2_R 0x320554 +#define AUD_ERRINTRPTTHSHLD3_R 0x320558 +#define AUD_NICAM_STATUS1 0x32055c +#define AUD_NICAM_STATUS2 0x320560 +#define AUD_ERRLOG1 0x320564 +#define AUD_ERRLOG2 0x320568 +#define AUD_ERRLOG3 0x32056c +#define AUD_DAC_BYPASS_L 0x320580 +#define AUD_DAC_BYPASS_R 0x320584 +#define AUD_DAC_BYPASS_CTL 0x320588 +#define AUD_CTL 0x32058c +#define AUD_STATUS 0x320590 +#define AUD_VOL_CTL 0x320594 +#define AUD_BAL_CTL 0x320598 +#define AUD_START_TIMER 0x3205b0 +#define AUD_MODE_CHG_TIMER 0x3205b4 +#define AUD_POLYPH80SCALEFAC 0x3205b8 +#define AUD_DMD_RA_DDS 0x3205bc +#define AUD_I2S_RA_DDS 0x3205c0 +#define AUD_RATE_THRES_DMD 0x3205d0 +#define AUD_RATE_THRES_I2S 0x3205d4 +#define AUD_RATE_ADJ1 0x3205d8 +#define AUD_RATE_ADJ2 0x3205dc +#define AUD_RATE_ADJ3 0x3205e0 +#define AUD_RATE_ADJ4 0x3205e4 +#define AUD_RATE_ADJ5 0x3205e8 +#define AUD_APB_IN_RATE_ADJ 0x3205ec +#define AUD_PHASE_FIX_CTL 0x3205f0 +#define AUD_PLL_PRESCALE 0x320600 +#define AUD_PLL_DDS 0x320604 +#define AUD_PLL_INT 0x320608 +#define AUD_PLL_FRAC 0x32060c +#define AUD_PLL_JTAG 0x320620 +#define AUD_PLL_SPMP 0x320624 +#define AUD_AFE_12DB_EN 0x320628 + +// Audio QAM Register Addresses +#define AUD_PDF_DDS_CNST_BYTE2 0x320d01 +#define AUD_PDF_DDS_CNST_BYTE1 0x320d02 +#define AUD_PDF_DDS_CNST_BYTE0 0x320d03 +#define AUD_PHACC_FREQ_8MSB 0x320d2a +#define AUD_PHACC_FREQ_8LSB 0x320d2b +#define AUD_QAM_MODE 0x320d04 + + +/* ---------------------------------------------------------------------- */ +/* transport stream registers */ + +#define MO_TS_DMA 0x330000 // {64}RWp Transport stream downstream +#define MO_TS_GPCNT 0x33C020 // {16}RO TS general purpose counter +#define MO_TS_GPCNTRL 0x33C030 // {2}WO TS general purpose control +#define MO_TS_DMACNTRL 0x33C040 // {6}RW TS DMA control +#define MO_TS_XFR_STAT 0x33C044 // {1}RO TS transfer status +#define MO_TS_LNGTH 0x33C048 // {12}RW TS line length + +#define TS_HW_SOP_CNTRL 0x33C04C +#define TS_GEN_CNTRL 0x33C050 +#define TS_BD_PKT_STAT 0x33C054 +#define TS_SOP_STAT 0x33C058 +#define TS_FIFO_OVFL_STAT 0x33C05C +#define TS_VALERR_CNTRL 0x33C060 + + +/* ---------------------------------------------------------------------- */ +/* VIP registers */ + +#define MO_VIPD_DMA 0x340000 // {64}RWp VIP downstream +#define MO_VIPU_DMA 0x340008 // {64}RWp VIP upstream +#define MO_VIPD_GPCNT 0x34C020 // {16}RO VIP down general purpose counter +#define MO_VIPU_GPCNT 0x34C024 // {16}RO VIP up general purpose counter +#define MO_VIPD_GPCNTRL 0x34C030 // {2}WO VIP down general purpose control +#define MO_VIPU_GPCNTRL 0x34C034 // {2}WO VIP up general purpose control +#define MO_VIP_DMACNTRL 0x34C040 // {6}RW VIP DMA control +#define MO_VIP_XFR_STAT 0x34C044 // {1}RO VIP transfer status +#define MO_VIP_CFG 0x340048 // VIP configuration +#define MO_VIPU_CNTRL 0x34004C // VIP upstream control #1 +#define MO_VIPD_CNTRL 0x340050 // VIP downstream control #2 +#define MO_VIPD_LNGTH 0x340054 // VIP downstream line length +#define MO_VIP_BRSTLN 0x340058 // VIP burst length +#define MO_VIP_INTCNTRL 0x34C05C // VIP Interrupt Control +#define MO_VIP_XFTERM 0x340060 // VIP transfer terminate + + +/* ---------------------------------------------------------------------- */ +/* misc registers */ + +#define MO_M2M_DMA 0x350000 // {64}RWp Mem2Mem DMA Bfr +#define MO_GP0_IO 0x350010 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP1_IO 0x350014 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP2_IO 0x350018 // {32}RW* GPIOoutput enablesdata I/O +#define MO_GP3_IO 0x35001C // {32}RW* GPIO Mode/Ctrloutput enables +#define MO_GPIO 0x350020 // {32}RW* GPIO I2C Ctrldata I/O +#define MO_GPOE 0x350024 // {32}RW GPIO I2C Ctrloutput enables +#define MO_GP_ISM 0x350028 // {16}WO GPIO Intr Sens/Pol + +#define MO_PLL_B 0x35C008 // {32}RW* PLL Control for ASB bus clks +#define MO_M2M_CNT 0x35C024 // {32}RW Mem2Mem DMA Cnt +#define MO_M2M_XSUM 0x35C028 // {32}RO M2M XOR-Checksum +#define MO_CRC 0x35C02C // {16}RW CRC16 init/result +#define MO_CRC_D 0x35C030 // {32}WO CRC16 new data in +#define MO_TM_CNT_LDW 0x35C034 // {32}RO Timer : Counter low dword +#define MO_TM_CNT_UW 0x35C038 // {16}RO Timer : Counter high word +#define MO_TM_LMT_LDW 0x35C03C // {32}RW Timer : Limit low dword +#define MO_TM_LMT_UW 0x35C040 // {32}RW Timer : Limit high word +#define MO_PINMUX_IO 0x35C044 // {8}RW Pin Mux Control +#define MO_TSTSEL_IO 0x35C048 // {2}RW Pin Mux Control +#define MO_AFECFG_IO 0x35C04C // AFE configuration reg +#define MO_DDS_IO 0x35C050 // DDS Increment reg +#define MO_DDSCFG_IO 0x35C054 // DDS Configuration reg +#define MO_SAMPLE_IO 0x35C058 // IRIn sample reg +#define MO_SRST_IO 0x35C05C // Output system reset reg + +#define MO_INT1_MSK 0x35C060 // DMA RISC interrupt mask +#define MO_INT1_STAT 0x35C064 // DMA RISC interrupt status +#define MO_INT1_MSTAT 0x35C068 // DMA RISC interrupt masked status + + +/* ---------------------------------------------------------------------- */ +/* i2c bus registers */ + +#define MO_I2C 0x368000 // I2C data/control +#define MO_I2C_DIV (0xf<<4) +#define MO_I2C_SYNC (1<<3) +#define MO_I2C_W3B (1<<2) +#define MO_I2C_SCL (1<<1) +#define MO_I2C_SDA (1<<0) + + +/* ---------------------------------------------------------------------- */ +/* general purpose host registers */ +/* FIXME: tyops? s/0x35/0x38/ ?? */ + +#define MO_GPHSTD_DMA 0x350000 // {64}RWp Host downstream +#define MO_GPHSTU_DMA 0x350008 // {64}RWp Host upstream +#define MO_GPHSTU_CNTRL 0x380048 // Host upstream control #1 +#define MO_GPHSTD_CNTRL 0x38004C // Host downstream control #2 +#define MO_GPHSTD_LNGTH 0x380050 // Host downstream line length +#define MO_GPHST_WSC 0x380054 // Host wait state control +#define MO_GPHST_XFR 0x380058 // Host transfer control +#define MO_GPHST_WDTH 0x38005C // Host interface width +#define MO_GPHST_HDSHK 0x380060 // Host peripheral handshake +#define MO_GPHST_MUX16 0x380064 // Host muxed 16-bit transfer parameters +#define MO_GPHST_MODE 0x380068 // Host mode select + +#define MO_GPHSTD_GPCNT 0x35C020 // Host down general purpose counter +#define MO_GPHSTU_GPCNT 0x35C024 // Host up general purpose counter +#define MO_GPHSTD_GPCNTRL 0x38C030 // Host down general purpose control +#define MO_GPHSTU_GPCNTRL 0x38C034 // Host up general purpose control +#define MO_GPHST_DMACNTRL 0x38C040 // Host DMA control +#define MO_GPHST_XFR_STAT 0x38C044 // Host transfer status +#define MO_GPHST_SOFT_RST 0x38C06C // Host software reset + + +/* ---------------------------------------------------------------------- */ +/* RISC instructions */ + +#define RISC_SYNC 0x80000000 +#define RISC_SYNC_ODD 0x80000000 +#define RISC_SYNC_EVEN 0x80000200 +#define RISC_RESYNC 0x80008000 +#define RISC_RESYNC_ODD 0x80008000 +#define RISC_RESYNC_EVEN 0x80008200 +#define RISC_WRITE 0x10000000 +#define RISC_WRITEC 0x50000000 +#define RISC_READ 0x90000000 +#define RISC_READC 0xA0000000 +#define RISC_JUMP 0x70000000 +#define RISC_SKIP 0x20000000 +#define RISC_WRITERM 0xB0000000 +#define RISC_WRITECM 0xC0000000 +#define RISC_WRITECR 0xD0000000 +#define RISC_IMM 0x00000001 + +#define RISC_SOL 0x08000000 +#define RISC_EOL 0x04000000 + +#define RISC_IRQ2 0x02000000 +#define RISC_IRQ1 0x01000000 + +#define RISC_CNT_NONE 0x00000000 +#define RISC_CNT_INC 0x00010000 +#define RISC_CNT_RSVR 0x00020000 +#define RISC_CNT_RESET 0x00030000 +#define RISC_JMP_SRP 0x01 + + +/* ---------------------------------------------------------------------- */ +/* various constants */ + +#define SEL_BTSC 0x01 +#define SEL_EIAJ 0x02 +#define SEL_A2 0x04 +#define SEL_SAP 0x08 +#define SEL_NICAM 0x10 +#define SEL_FMRADIO 0x20 + +// AUD_CTL +#define EN_BTSC_FORCE_MONO 0 +#define EN_BTSC_FORCE_STEREO 1 +#define EN_BTSC_FORCE_SAP 2 +#define EN_BTSC_AUTO_STEREO 3 +#define EN_BTSC_AUTO_SAP 4 + +#define EN_A2_FORCE_MONO1 8 +#define EN_A2_FORCE_MONO2 9 +#define EN_A2_FORCE_STEREO 10 +#define EN_A2_AUTO_MONO2 11 +#define EN_A2_AUTO_STEREO 12 + +#define EN_EIAJ_FORCE_MONO1 16 +#define EN_EIAJ_FORCE_MONO2 17 +#define EN_EIAJ_FORCE_STEREO 18 +#define EN_EIAJ_AUTO_MONO2 19 +#define EN_EIAJ_AUTO_STEREO 20 + +#define EN_NICAM_FORCE_MONO1 32 +#define EN_NICAM_FORCE_MONO2 33 +#define EN_NICAM_FORCE_STEREO 34 +#define EN_NICAM_AUTO_MONO2 35 +#define EN_NICAM_AUTO_STEREO 36 + +#define EN_FMRADIO_FORCE_MONO 24 +#define EN_FMRADIO_FORCE_STEREO 25 +#define EN_FMRADIO_AUTO_STEREO 26 + +#define EN_NICAM_AUTO_FALLBACK 0x00000040 +#define EN_FMRADIO_EN_RDS 0x00000200 +#define EN_NICAM_TRY_AGAIN_BIT 0x00000400 +#define EN_DAC_ENABLE 0x00001000 +#define EN_I2SOUT_ENABLE 0x00002000 +#define EN_I2SIN_STR2DAC 0x00004000 +#define EN_I2SIN_ENABLE 0x00008000 + +#if 0 +/* old */ +#define EN_DMTRX_SUMDIFF 0x00000800 +#define EN_DMTRX_SUMR 0x00000880 +#define EN_DMTRX_LR 0x00000900 +#define EN_DMTRX_MONO 0x00000980 +#else +/* dscaler cvs */ +#define EN_DMTRX_SUMDIFF (0 << 7) +#define EN_DMTRX_SUMR (1 << 7) +#define EN_DMTRX_LR (2 << 7) +#define EN_DMTRX_MONO (3 << 7) +#define EN_DMTRX_BYPASS (1 << 11) +#endif + +// Video +#define VID_CAPTURE_CONTROL 0x310180 + +#define CX23880_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define CX23880_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define CX23880_CAP_CTL_CAPTURE_ODD (1<<1) +#define CX23880_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define VideoInputMux0 0x0 +#define VideoInputMux1 0x1 +#define VideoInputMux2 0x2 +#define VideoInputMux3 0x3 +#define VideoInputTuner 0x0 +#define VideoInputComposite 0x1 +#define VideoInputSVideo 0x2 +#define VideoInputOther 0x3 + +#define Xtal0 0x1 +#define Xtal1 0x2 +#define XtalAuto 0x3 + +#define VideoFormatAuto 0x0 +#define VideoFormatNTSC 0x1 +#define VideoFormatNTSCJapan 0x2 +#define VideoFormatNTSC443 0x3 +#define VideoFormatPAL 0x4 +#define VideoFormatPALB 0x4 +#define VideoFormatPALD 0x4 +#define VideoFormatPALG 0x4 +#define VideoFormatPALH 0x4 +#define VideoFormatPALI 0x4 +#define VideoFormatPALBDGHI 0x4 +#define VideoFormatPALM 0x5 +#define VideoFormatPALN 0x6 +#define VideoFormatPALNC 0x7 +#define VideoFormatPAL60 0x8 +#define VideoFormatSECAM 0x9 + +#define VideoFormatAuto27MHz 0x10 +#define VideoFormatNTSC27MHz 0x11 +#define VideoFormatNTSCJapan27MHz 0x12 +#define VideoFormatNTSC44327MHz 0x13 +#define VideoFormatPAL27MHz 0x14 +#define VideoFormatPALB27MHz 0x14 +#define VideoFormatPALD27MHz 0x14 +#define VideoFormatPALG27MHz 0x14 +#define VideoFormatPALH27MHz 0x14 +#define VideoFormatPALI27MHz 0x14 +#define VideoFormatPALBDGHI27MHz 0x14 +#define VideoFormatPALM27MHz 0x15 +#define VideoFormatPALN27MHz 0x16 +#define VideoFormatPALNC27MHz 0x17 +#define VideoFormatPAL6027MHz 0x18 +#define VideoFormatSECAM27MHz 0x19 + +#define NominalUSECAM 0x87 +#define NominalVSECAM 0x85 +#define NominalUNTSC 0xFE +#define NominalVNTSC 0xB4 + +#define NominalContrast 0xD8 + +#define HFilterAutoFormat 0x0 +#define HFilterCIF 0x1 +#define HFilterQCIF 0x2 +#define HFilterICON 0x3 + +#define VFilter2TapInterpolate 0 +#define VFilter3TapInterpolate 1 +#define VFilter4TapInterpolate 2 +#define VFilter5TapInterpolate 3 +#define VFilter2TapNoInterpolate 4 +#define VFilter3TapNoInterpolate 5 +#define VFilter4TapNoInterpolate 6 +#define VFilter5TapNoInterpolate 7 + +#define ColorFormatRGB32 0x0000 +#define ColorFormatRGB24 0x0011 +#define ColorFormatRGB16 0x0022 +#define ColorFormatRGB15 0x0033 +#define ColorFormatYUY2 0x0044 +#define ColorFormatBTYUV 0x0055 +#define ColorFormatY8 0x0066 +#define ColorFormatRGB8 0x0077 +#define ColorFormatPL422 0x0088 +#define ColorFormatPL411 0x0099 +#define ColorFormatYUV12 0x00AA +#define ColorFormatYUV9 0x00BB +#define ColorFormatRAW 0x00EE +#define ColorFormatBSWAP 0x0300 +#define ColorFormatWSWAP 0x0c00 +#define ColorFormatEvenMask 0x050f +#define ColorFormatOddMask 0x0af0 +#define ColorFormatGamma 0x1000 + +#define Interlaced 0x1 +#define NonInterlaced 0x0 + +#define FieldEven 0x1 +#define FieldOdd 0x0 + +#define TGReadWriteMode 0x0 +#define TGEnableMode 0x1 + +#define DV_CbAlign 0x0 +#define DV_Y0Align 0x1 +#define DV_CrAlign 0x2 +#define DV_Y1Align 0x3 + +#define DVF_Analog 0x0 +#define DVF_CCIR656 0x1 +#define DVF_ByteStream 0x2 +#define DVF_ExtVSYNC 0x4 +#define DVF_ExtField 0x5 + +#define CHANNEL_VID_Y 0x1 +#define CHANNEL_VID_U 0x2 +#define CHANNEL_VID_V 0x3 +#define CHANNEL_VID_VBI 0x4 +#define CHANNEL_AUD_DN 0x5 +#define CHANNEL_AUD_UP 0x6 +#define CHANNEL_AUD_RDS_DN 0x7 +#define CHANNEL_MPEG_DN 0x8 +#define CHANNEL_VIP_DN 0x9 +#define CHANNEL_VIP_UP 0xA +#define CHANNEL_HOST_DN 0xB +#define CHANNEL_HOST_UP 0xC +#define CHANNEL_FIRST 0x1 +#define CHANNEL_LAST 0xC + +#define GP_COUNT_CONTROL_NONE 0x0 +#define GP_COUNT_CONTROL_INC 0x1 +#define GP_COUNT_CONTROL_RESERVED 0x2 +#define GP_COUNT_CONTROL_RESET 0x3 + +#define PLL_PRESCALE_BY_2 2 +#define PLL_PRESCALE_BY_3 3 +#define PLL_PRESCALE_BY_4 4 +#define PLL_PRESCALE_BY_5 5 + +#define HLNotchFilter4xFsc 0 +#define HLNotchFilterSquare 1 +#define HLNotchFilter135NTSC 2 +#define HLNotchFilter135PAL 3 + +#define NTSC_8x_SUB_CARRIER 28.63636E6 +#define PAL_8x_SUB_CARRIER 35.46895E6 + +// Default analog settings +#define DEFAULT_HUE_NTSC 0x00 +#define DEFAULT_BRIGHTNESS_NTSC 0x00 +#define DEFAULT_CONTRAST_NTSC 0x39 +#define DEFAULT_SAT_U_NTSC 0x7F +#define DEFAULT_SAT_V_NTSC 0x5A + +typedef enum +{ + SOURCE_TUNER = 0, + SOURCE_COMPOSITE, + SOURCE_SVIDEO, + SOURCE_OTHER1, + SOURCE_OTHER2, + SOURCE_COMPVIASVIDEO, + SOURCE_CCIR656 +} VIDEOSOURCETYPE; + +#endif /* _CX88_REG_H_ */ diff --git a/drivers/media/video/cx88/cx88-tvaudio.c b/drivers/media/video/cx88/cx88-tvaudio.c new file mode 100644 index 000000000000..f2a9475a2fee --- /dev/null +++ b/drivers/media/video/cx88/cx88-tvaudio.c @@ -0,0 +1,1032 @@ +/* + $Id: cx88-tvaudio.c,v 1.34 2005/03/07 16:10:51 kraxel Exp $ + + cx88x-audio.c - Conexant CX23880/23881 audio downstream driver driver + + (c) 2001 Michael Eskin, Tom Zakrajsek [Windows version] + (c) 2002 Yurij Sysoev <yurij@naturesoft.net> + (c) 2003 Gerd Knorr <kraxel@bytesex.org> + + ----------------------------------------------------------------------- + + Lot of voodoo here. Even the data sheet doesn't help to + understand what is going on here, the documentation for the audio + part of the cx2388x chip is *very* bad. + + Some of this comes from party done linux driver sources I got from + [undocumented]. + + Some comes from the dscaler sources, one of the dscaler driver guy works + for Conexant ... + + ----------------------------------------------------------------------- + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/delay.h> +#include <linux/kthread.h> + +#include "cx88.h" + +static unsigned int audio_debug = 0; +module_param(audio_debug,int,0644); +MODULE_PARM_DESC(audio_debug,"enable debug messages [audio]"); + +#define dprintk(fmt, arg...) if (audio_debug) \ + printk(KERN_DEBUG "%s/0: " fmt, core->name , ## arg) + +/* ----------------------------------------------------------- */ + +static char *aud_ctl_names[64] = +{ + [ EN_BTSC_FORCE_MONO ] = "BTSC_FORCE_MONO", + [ EN_BTSC_FORCE_STEREO ] = "BTSC_FORCE_STEREO", + [ EN_BTSC_FORCE_SAP ] = "BTSC_FORCE_SAP", + [ EN_BTSC_AUTO_STEREO ] = "BTSC_AUTO_STEREO", + [ EN_BTSC_AUTO_SAP ] = "BTSC_AUTO_SAP", + [ EN_A2_FORCE_MONO1 ] = "A2_FORCE_MONO1", + [ EN_A2_FORCE_MONO2 ] = "A2_FORCE_MONO2", + [ EN_A2_FORCE_STEREO ] = "A2_FORCE_STEREO", + [ EN_A2_AUTO_MONO2 ] = "A2_AUTO_MONO2", + [ EN_A2_AUTO_STEREO ] = "A2_AUTO_STEREO", + [ EN_EIAJ_FORCE_MONO1 ] = "EIAJ_FORCE_MONO1", + [ EN_EIAJ_FORCE_MONO2 ] = "EIAJ_FORCE_MONO2", + [ EN_EIAJ_FORCE_STEREO ] = "EIAJ_FORCE_STEREO", + [ EN_EIAJ_AUTO_MONO2 ] = "EIAJ_AUTO_MONO2", + [ EN_EIAJ_AUTO_STEREO ] = "EIAJ_AUTO_STEREO", + [ EN_NICAM_FORCE_MONO1 ] = "NICAM_FORCE_MONO1", + [ EN_NICAM_FORCE_MONO2 ] = "NICAM_FORCE_MONO2", + [ EN_NICAM_FORCE_STEREO ] = "NICAM_FORCE_STEREO", + [ EN_NICAM_AUTO_MONO2 ] = "NICAM_AUTO_MONO2", + [ EN_NICAM_AUTO_STEREO ] = "NICAM_AUTO_STEREO", + [ EN_FMRADIO_FORCE_MONO ] = "FMRADIO_FORCE_MONO", + [ EN_FMRADIO_FORCE_STEREO ] = "FMRADIO_FORCE_STEREO", + [ EN_FMRADIO_AUTO_STEREO ] = "FMRADIO_AUTO_STEREO", +}; + +struct rlist { + u32 reg; + u32 val; +}; + +static void set_audio_registers(struct cx88_core *core, + const struct rlist *l) +{ + int i; + + for (i = 0; l[i].reg; i++) { + switch (l[i].reg) { + case AUD_PDF_DDS_CNST_BYTE2: + case AUD_PDF_DDS_CNST_BYTE1: + case AUD_PDF_DDS_CNST_BYTE0: + case AUD_QAM_MODE: + case AUD_PHACC_FREQ_8MSB: + case AUD_PHACC_FREQ_8LSB: + cx_writeb(l[i].reg, l[i].val); + break; + default: + cx_write(l[i].reg, l[i].val); + break; + } + } +} + +static void set_audio_start(struct cx88_core *core, + u32 mode, u32 ctl) +{ + // mute + cx_write(AUD_VOL_CTL, (1 << 6)); + + // increase level of input by 12dB + cx_write(AUD_AFE_12DB_EN, 0x0001); + + // start programming + cx_write(AUD_CTL, 0x0000); + cx_write(AUD_INIT, mode); + cx_write(AUD_INIT_LD, 0x0001); + cx_write(AUD_SOFT_RESET, 0x0001); + + cx_write(AUD_CTL, ctl); +} + +static void set_audio_finish(struct cx88_core *core) +{ + u32 volume; + + if (cx88_boards[core->board].blackbird) { + // 'pass-thru mode': this enables the i2s output to the mpeg encoder + cx_set(AUD_CTL, 0x2000); + cx_write(AUD_I2SOUTPUTCNTL, 1); + //cx_write(AUD_APB_IN_RATE_ADJ, 0); + } + + // finish programming + cx_write(AUD_SOFT_RESET, 0x0000); + + // start audio processing + cx_set(AUD_CTL, EN_DAC_ENABLE); + + // unmute + volume = cx_sread(SHADOW_AUD_VOL_CTL); + cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, volume); +} + +/* ----------------------------------------------------------- */ + +static void set_audio_standard_BTSC(struct cx88_core *core, unsigned int sap) +{ + static const struct rlist btsc[] = { + /* from dscaler */ + { AUD_OUT1_SEL, 0x00000013 }, + { AUD_OUT1_SHIFT, 0x00000000 }, + { AUD_POLY0_DDS_CONSTANT, 0x0012010c }, + { AUD_DMD_RA_DDS, 0x00c3e7aa }, + { AUD_DBX_IN_GAIN, 0x00004734 }, + { AUD_DBX_WBE_GAIN, 0x00004640 }, + { AUD_DBX_SE_GAIN, 0x00008d31 }, + { AUD_DCOC_0_SRC, 0x0000001a }, + { AUD_IIR1_4_SEL, 0x00000021 }, + { AUD_DCOC_PASS_IN, 0x00000003 }, + { AUD_DCOC_0_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_0_SHIFT_IN1, 0x00000008 }, + { AUD_DCOC_1_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_1_SHIFT_IN1, 0x00000008 }, + { AUD_DN0_FREQ, 0x0000283b }, + { AUD_DN2_SRC_SEL, 0x00000008 }, + { AUD_DN2_FREQ, 0x00003000 }, + { AUD_DN2_AFC, 0x00000002 }, + { AUD_DN2_SHFT, 0x00000000 }, + { AUD_IIR2_2_SEL, 0x00000020 }, + { AUD_IIR2_2_SHIFT, 0x00000000 }, + { AUD_IIR2_3_SEL, 0x0000001f }, + { AUD_IIR2_3_SHIFT, 0x00000000 }, + { AUD_CRDC1_SRC_SEL, 0x000003ce }, + { AUD_CRDC1_SHIFT, 0x00000000 }, + { AUD_CORDIC_SHIFT_1, 0x00000007 }, + { AUD_DCOC_1_SRC, 0x0000001b }, + { AUD_DCOC1_SHIFT, 0x00000000 }, + { AUD_RDSI_SEL, 0x00000008 }, + { AUD_RDSQ_SEL, 0x00000008 }, + { AUD_RDSI_SHIFT, 0x00000000 }, + { AUD_RDSQ_SHIFT, 0x00000000 }, + { AUD_POLYPH80SCALEFAC, 0x00000003 }, + { /* end of list */ }, + }; + static const struct rlist btsc_sap[] = { + { AUD_DBX_IN_GAIN, 0x00007200 }, + { AUD_DBX_WBE_GAIN, 0x00006200 }, + { AUD_DBX_SE_GAIN, 0x00006200 }, + { AUD_IIR1_1_SEL, 0x00000000 }, + { AUD_IIR1_3_SEL, 0x00000001 }, + { AUD_DN1_SRC_SEL, 0x00000007 }, + { AUD_IIR1_4_SHIFT, 0x00000006 }, + { AUD_IIR2_1_SHIFT, 0x00000000 }, + { AUD_IIR2_2_SHIFT, 0x00000000 }, + { AUD_IIR3_0_SHIFT, 0x00000000 }, + { AUD_IIR3_1_SHIFT, 0x00000000 }, + { AUD_IIR3_0_SEL, 0x0000000d }, + { AUD_IIR3_1_SEL, 0x0000000e }, + { AUD_DEEMPH1_SRC_SEL, 0x00000014 }, + { AUD_DEEMPH1_SHIFT, 0x00000000 }, + { AUD_DEEMPH1_G0, 0x00004000 }, + { AUD_DEEMPH1_A0, 0x00000000 }, + { AUD_DEEMPH1_B0, 0x00000000 }, + { AUD_DEEMPH1_A1, 0x00000000 }, + { AUD_DEEMPH1_B1, 0x00000000 }, + { AUD_OUT0_SEL, 0x0000003f }, + { AUD_OUT1_SEL, 0x0000003f }, + { AUD_DN1_AFC, 0x00000002 }, + { AUD_DCOC_0_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_0_SHIFT_IN1, 0x00000008 }, + { AUD_DCOC_1_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_1_SHIFT_IN1, 0x00000008 }, + { AUD_IIR1_0_SEL, 0x0000001d }, + { AUD_IIR1_2_SEL, 0x0000001e }, + { AUD_IIR2_1_SEL, 0x00000002 }, + { AUD_IIR2_2_SEL, 0x00000004 }, + { AUD_IIR3_2_SEL, 0x0000000f }, + { AUD_DCOC2_SHIFT, 0x00000001 }, + { AUD_IIR3_2_SHIFT, 0x00000001 }, + { AUD_DEEMPH0_SRC_SEL, 0x00000014 }, + { AUD_CORDIC_SHIFT_1, 0x00000006 }, + { AUD_POLY0_DDS_CONSTANT, 0x000e4db2 }, + { AUD_DMD_RA_DDS, 0x00f696e6 }, + { AUD_IIR2_3_SEL, 0x00000025 }, + { AUD_IIR1_4_SEL, 0x00000021 }, + { AUD_DN1_FREQ, 0x0000c965 }, + { AUD_DCOC_PASS_IN, 0x00000003 }, + { AUD_DCOC_0_SRC, 0x0000001a }, + { AUD_DCOC_1_SRC, 0x0000001b }, + { AUD_DCOC1_SHIFT, 0x00000000 }, + { AUD_RDSI_SEL, 0x00000009 }, + { AUD_RDSQ_SEL, 0x00000009 }, + { AUD_RDSI_SHIFT, 0x00000000 }, + { AUD_RDSQ_SHIFT, 0x00000000 }, + { AUD_POLYPH80SCALEFAC, 0x00000003 }, + { /* end of list */ }, + }; + + // dscaler: exactly taken from driver, + // dscaler: don't know why to set EN_FMRADIO_EN_RDS + if (sap) { + dprintk("%s SAP (status: unknown)\n",__FUNCTION__); + set_audio_start(core, 0x0001, + EN_FMRADIO_EN_RDS | EN_BTSC_FORCE_SAP); + set_audio_registers(core, btsc_sap); + } else { + dprintk("%s (status: known-good)\n",__FUNCTION__); + set_audio_start(core, 0x0001, + EN_FMRADIO_EN_RDS | EN_BTSC_AUTO_STEREO); + set_audio_registers(core, btsc); + } + set_audio_finish(core); +} + +#if 0 +static void set_audio_standard_NICAM(struct cx88_core *core) +{ + static const struct rlist nicam_common[] = { + /* from dscaler */ + { AUD_RATE_ADJ1, 0x00000010 }, + { AUD_RATE_ADJ2, 0x00000040 }, + { AUD_RATE_ADJ3, 0x00000100 }, + { AUD_RATE_ADJ4, 0x00000400 }, + { AUD_RATE_ADJ5, 0x00001000 }, + // { AUD_DMD_RA_DDS, 0x00c0d5ce }, + + // Deemphasis 1: + { AUD_DEEMPHGAIN_R, 0x000023c2 }, + { AUD_DEEMPHNUMER1_R, 0x0002a7bc }, + { AUD_DEEMPHNUMER2_R, 0x0003023e }, + { AUD_DEEMPHDENOM1_R, 0x0000f3d0 }, + { AUD_DEEMPHDENOM2_R, 0x00000000 }, + +#if 0 + // Deemphasis 2: (other tv norm?) + { AUD_DEEMPHGAIN_R, 0x0000c600 }, + { AUD_DEEMPHNUMER1_R, 0x00066738 }, + { AUD_DEEMPHNUMER2_R, 0x00066739 }, + { AUD_DEEMPHDENOM1_R, 0x0001e88c }, + { AUD_DEEMPHDENOM2_R, 0x0001e88c }, +#endif + + { AUD_DEEMPHDENOM2_R, 0x00000000 }, + { AUD_ERRLOGPERIOD_R, 0x00000fff }, + { AUD_ERRINTRPTTHSHLD1_R, 0x000003ff }, + { AUD_ERRINTRPTTHSHLD2_R, 0x000000ff }, + { AUD_ERRINTRPTTHSHLD3_R, 0x0000003f }, + { AUD_POLYPH80SCALEFAC, 0x00000003 }, + + // setup QAM registers + { AUD_PDF_DDS_CNST_BYTE2, 0x06 }, + { AUD_PDF_DDS_CNST_BYTE1, 0x82 }, + { AUD_PDF_DDS_CNST_BYTE0, 0x16 }, + { AUD_QAM_MODE, 0x05 }, + + { /* end of list */ }, + }; + static const struct rlist nicam_pal_i[] = { + { AUD_PDF_DDS_CNST_BYTE0, 0x12 }, + { AUD_PHACC_FREQ_8MSB, 0x3a }, + { AUD_PHACC_FREQ_8LSB, 0x93 }, + + { /* end of list */ }, + }; + static const struct rlist nicam_default[] = { + { AUD_PDF_DDS_CNST_BYTE0, 0x16 }, + { AUD_PHACC_FREQ_8MSB, 0x34 }, + { AUD_PHACC_FREQ_8LSB, 0x4c }, + + { /* end of list */ }, + }; + + set_audio_start(core, 0x0010, + EN_DMTRX_LR | EN_DMTRX_BYPASS | EN_NICAM_AUTO_STEREO); + set_audio_registers(core, nicam_common); + switch (core->tvaudio) { + case WW_NICAM_I: + dprintk("%s PAL-I NICAM (status: unknown)\n",__FUNCTION__); + set_audio_registers(core, nicam_pal_i); + break; + case WW_NICAM_BGDKL: + dprintk("%s PAL-BGDK NICAM (status: unknown)\n",__FUNCTION__); + set_audio_registers(core, nicam_default); + break; + }; + set_audio_finish(core); +} +#endif + +static void set_audio_standard_NICAM_L(struct cx88_core *core, int stereo) +{ + /* This is probably weird.. + * Let's operate and find out. */ + + static const struct rlist nicam_l_mono[] = { + { AUD_ERRLOGPERIOD_R, 0x00000064 }, + { AUD_ERRINTRPTTHSHLD1_R, 0x00000FFF }, + { AUD_ERRINTRPTTHSHLD2_R, 0x0000001F }, + { AUD_ERRINTRPTTHSHLD3_R, 0x0000000F }, + + { AUD_PDF_DDS_CNST_BYTE2, 0x48 }, + { AUD_PDF_DDS_CNST_BYTE1, 0x3D }, + { AUD_QAM_MODE, 0x00 }, + { AUD_PDF_DDS_CNST_BYTE0, 0xf5 }, + { AUD_PHACC_FREQ_8MSB, 0x3a }, + { AUD_PHACC_FREQ_8LSB, 0x4a }, + + { AUD_DEEMPHGAIN_R, 0x6680 }, + { AUD_DEEMPHNUMER1_R, 0x353DE }, + { AUD_DEEMPHNUMER2_R, 0x1B1 }, + { AUD_DEEMPHDENOM1_R, 0x0F3D0 }, + { AUD_DEEMPHDENOM2_R, 0x0 }, + { AUD_FM_MODE_ENABLE, 0x7 }, + { AUD_POLYPH80SCALEFAC, 0x3 }, + { AUD_AFE_12DB_EN, 0x1 }, + { AAGC_GAIN, 0x0 }, + { AAGC_HYST, 0x18 }, + { AAGC_DEF, 0x20 }, + { AUD_DN0_FREQ, 0x0 }, + { AUD_POLY0_DDS_CONSTANT, 0x0E4DB2 }, + { AUD_DCOC_0_SRC, 0x21 }, + { AUD_IIR1_0_SEL, 0x0 }, + { AUD_IIR1_0_SHIFT, 0x7 }, + { AUD_IIR1_1_SEL, 0x2 }, + { AUD_IIR1_1_SHIFT, 0x0 }, + { AUD_DCOC_1_SRC, 0x3 }, + { AUD_DCOC1_SHIFT, 0x0 }, + { AUD_DCOC_PASS_IN, 0x0 }, + { AUD_IIR1_2_SEL, 0x23 }, + { AUD_IIR1_2_SHIFT, 0x0 }, + { AUD_IIR1_3_SEL, 0x4 }, + { AUD_IIR1_3_SHIFT, 0x7 }, + { AUD_IIR1_4_SEL, 0x5 }, + { AUD_IIR1_4_SHIFT, 0x7 }, + { AUD_IIR3_0_SEL, 0x7 }, + { AUD_IIR3_0_SHIFT, 0x0 }, + { AUD_DEEMPH0_SRC_SEL, 0x11 }, + { AUD_DEEMPH0_SHIFT, 0x0 }, + { AUD_DEEMPH0_G0, 0x7000 }, + { AUD_DEEMPH0_A0, 0x0 }, + { AUD_DEEMPH0_B0, 0x0 }, + { AUD_DEEMPH0_A1, 0x0 }, + { AUD_DEEMPH0_B1, 0x0 }, + { AUD_DEEMPH1_SRC_SEL, 0x11 }, + { AUD_DEEMPH1_SHIFT, 0x0 }, + { AUD_DEEMPH1_G0, 0x7000 }, + { AUD_DEEMPH1_A0, 0x0 }, + { AUD_DEEMPH1_B0, 0x0 }, + { AUD_DEEMPH1_A1, 0x0 }, + { AUD_DEEMPH1_B1, 0x0 }, + { AUD_OUT0_SEL, 0x3F }, + { AUD_OUT1_SEL, 0x3F }, + { AUD_DMD_RA_DDS, 0x0F5C285 }, + { AUD_PLL_INT, 0x1E }, + { AUD_PLL_DDS, 0x0 }, + { AUD_PLL_FRAC, 0x0E542 }, + + // setup QAM registers + { AUD_RATE_ADJ1, 0x00000100 }, + { AUD_RATE_ADJ2, 0x00000200 }, + { AUD_RATE_ADJ3, 0x00000300 }, + { AUD_RATE_ADJ4, 0x00000400 }, + { AUD_RATE_ADJ5, 0x00000500 }, + { AUD_RATE_THRES_DMD, 0x000000C0 }, + { /* end of list */ }, + }; + + static const struct rlist nicam_l[] = { + // setup QAM registers + { AUD_RATE_ADJ1, 0x00000060 }, + { AUD_RATE_ADJ2, 0x000000F9 }, + { AUD_RATE_ADJ3, 0x000001CC }, + { AUD_RATE_ADJ4, 0x000002B3 }, + { AUD_RATE_ADJ5, 0x00000726 }, + { AUD_DEEMPHDENOM1_R, 0x0000F3D0 }, + { AUD_DEEMPHDENOM2_R, 0x00000000 }, + { AUD_ERRLOGPERIOD_R, 0x00000064 }, + { AUD_ERRINTRPTTHSHLD1_R, 0x00000FFF }, + { AUD_ERRINTRPTTHSHLD2_R, 0x0000001F }, + { AUD_ERRINTRPTTHSHLD3_R, 0x0000000F }, + { AUD_POLYPH80SCALEFAC, 0x00000003 }, + { AUD_DMD_RA_DDS, 0x00C00000 }, + { AUD_PLL_INT, 0x0000001E }, + { AUD_PLL_DDS, 0x00000000 }, + { AUD_PLL_FRAC, 0x0000E542 }, + { AUD_START_TIMER, 0x00000000 }, + { AUD_DEEMPHNUMER1_R, 0x000353DE }, + { AUD_DEEMPHNUMER2_R, 0x000001B1 }, + { AUD_PDF_DDS_CNST_BYTE2, 0x06 }, + { AUD_PDF_DDS_CNST_BYTE1, 0x82 }, + { AUD_QAM_MODE, 0x05 }, + { AUD_PDF_DDS_CNST_BYTE0, 0x12 }, + { AUD_PHACC_FREQ_8MSB, 0x34 }, + { AUD_PHACC_FREQ_8LSB, 0x4C }, + { AUD_DEEMPHGAIN_R, 0x00006680 }, + { AUD_RATE_THRES_DMD, 0x000000C0 }, + { /* end of list */ }, + } ; + dprintk("%s (status: devel), stereo : %d\n",__FUNCTION__,stereo); + + if (!stereo) { + /* AM mono sound */ + set_audio_start(core, 0x0004, + 0x100c /* FIXME again */); + set_audio_registers(core, nicam_l_mono); + } else { + set_audio_start(core, 0x0010, + 0x1924 /* FIXME again */); + set_audio_registers(core, nicam_l); + } + set_audio_finish(core); + +} + +static void set_audio_standard_PAL_I(struct cx88_core *core, int stereo) +{ + static const struct rlist pal_i_fm_mono[] = { + {AUD_ERRLOGPERIOD_R, 0x00000064}, + {AUD_ERRINTRPTTHSHLD1_R, 0x00000fff}, + {AUD_ERRINTRPTTHSHLD2_R, 0x0000001f}, + {AUD_ERRINTRPTTHSHLD3_R, 0x0000000f}, + {AUD_PDF_DDS_CNST_BYTE2, 0x06}, + {AUD_PDF_DDS_CNST_BYTE1, 0x82}, + {AUD_PDF_DDS_CNST_BYTE0, 0x12}, + {AUD_QAM_MODE, 0x05}, + {AUD_PHACC_FREQ_8MSB, 0x3a}, + {AUD_PHACC_FREQ_8LSB, 0x93}, + {AUD_DMD_RA_DDS, 0x002a4f2f}, + {AUD_PLL_INT, 0x0000001e}, + {AUD_PLL_DDS, 0x00000004}, + {AUD_PLL_FRAC, 0x0000e542}, + {AUD_RATE_ADJ1, 0x00000100}, + {AUD_RATE_ADJ2, 0x00000200}, + {AUD_RATE_ADJ3, 0x00000300}, + {AUD_RATE_ADJ4, 0x00000400}, + {AUD_RATE_ADJ5, 0x00000500}, + {AUD_THR_FR, 0x00000000}, + {AUD_PILOT_BQD_1_K0, 0x0000755b}, + {AUD_PILOT_BQD_1_K1, 0x00551340}, + {AUD_PILOT_BQD_1_K2, 0x006d30be}, + {AUD_PILOT_BQD_1_K3, 0xffd394af}, + {AUD_PILOT_BQD_1_K4, 0x00400000}, + {AUD_PILOT_BQD_2_K0, 0x00040000}, + {AUD_PILOT_BQD_2_K1, 0x002a4841}, + {AUD_PILOT_BQD_2_K2, 0x00400000}, + {AUD_PILOT_BQD_2_K3, 0x00000000}, + {AUD_PILOT_BQD_2_K4, 0x00000000}, + {AUD_MODE_CHG_TIMER, 0x00000060}, + {AUD_AFE_12DB_EN, 0x00000001}, + {AAGC_HYST, 0x0000000a}, + {AUD_CORDIC_SHIFT_0, 0x00000007}, + {AUD_CORDIC_SHIFT_1, 0x00000007}, + {AUD_C1_UP_THR, 0x00007000}, + {AUD_C1_LO_THR, 0x00005400}, + {AUD_C2_UP_THR, 0x00005400}, + {AUD_C2_LO_THR, 0x00003000}, + {AUD_DCOC_0_SRC, 0x0000001a}, + {AUD_DCOC0_SHIFT, 0x00000000}, + {AUD_DCOC_0_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_0_SHIFT_IN1, 0x00000008}, + {AUD_DCOC_PASS_IN, 0x00000003}, + {AUD_IIR3_0_SEL, 0x00000021}, + {AUD_DN2_AFC, 0x00000002}, + {AUD_DCOC_1_SRC, 0x0000001b}, + {AUD_DCOC1_SHIFT, 0x00000000}, + {AUD_DCOC_1_SHIFT_IN0, 0x0000000a}, + {AUD_DCOC_1_SHIFT_IN1, 0x00000008}, + {AUD_IIR3_1_SEL, 0x00000023}, + {AUD_DN0_FREQ, 0x000035a3}, + {AUD_DN2_FREQ, 0x000029c7}, + {AUD_CRDC0_SRC_SEL, 0x00000511}, + {AUD_IIR1_0_SEL, 0x00000001}, + {AUD_IIR1_1_SEL, 0x00000000}, + {AUD_IIR3_2_SEL, 0x00000003}, + {AUD_IIR3_2_SHIFT, 0x00000000}, + {AUD_IIR3_0_SEL, 0x00000002}, + {AUD_IIR2_0_SEL, 0x00000021}, + {AUD_IIR2_0_SHIFT, 0x00000002}, + {AUD_DEEMPH0_SRC_SEL, 0x0000000b}, + {AUD_DEEMPH1_SRC_SEL, 0x0000000b}, + {AUD_POLYPH80SCALEFAC, 0x00000001}, + {AUD_START_TIMER, 0x00000000}, + { /* end of list */ }, + }; + + static const struct rlist pal_i_nicam[] = { + { AUD_RATE_ADJ1, 0x00000010 }, + { AUD_RATE_ADJ2, 0x00000040 }, + { AUD_RATE_ADJ3, 0x00000100 }, + { AUD_RATE_ADJ4, 0x00000400 }, + { AUD_RATE_ADJ5, 0x00001000 }, + // { AUD_DMD_RA_DDS, 0x00c0d5ce }, + { AUD_DEEMPHGAIN_R, 0x000023c2 }, + { AUD_DEEMPHNUMER1_R, 0x0002a7bc }, + { AUD_DEEMPHNUMER2_R, 0x0003023e }, + { AUD_DEEMPHDENOM1_R, 0x0000f3d0 }, + { AUD_DEEMPHDENOM2_R, 0x00000000 }, + { AUD_DEEMPHDENOM2_R, 0x00000000 }, + { AUD_ERRLOGPERIOD_R, 0x00000fff }, + { AUD_ERRINTRPTTHSHLD1_R, 0x000003ff }, + { AUD_ERRINTRPTTHSHLD2_R, 0x000000ff }, + { AUD_ERRINTRPTTHSHLD3_R, 0x0000003f }, + { AUD_POLYPH80SCALEFAC, 0x00000003 }, + { AUD_PDF_DDS_CNST_BYTE2, 0x06 }, + { AUD_PDF_DDS_CNST_BYTE1, 0x82 }, + { AUD_PDF_DDS_CNST_BYTE0, 0x16 }, + { AUD_QAM_MODE, 0x05 }, + { AUD_PDF_DDS_CNST_BYTE0, 0x12 }, + { AUD_PHACC_FREQ_8MSB, 0x3a }, + { AUD_PHACC_FREQ_8LSB, 0x93 }, + { /* end of list */ }, + }; + + dprintk("%s (status: devel), stereo : %d\n",__FUNCTION__,stereo); + + if (!stereo) { + // FM mono + set_audio_start(core, 0x0004, EN_DMTRX_SUMDIFF | EN_A2_FORCE_MONO1); + set_audio_registers(core, pal_i_fm_mono); + } else { + // Nicam Stereo + set_audio_start(core, 0x0010, EN_DMTRX_LR | EN_DMTRX_BYPASS | EN_NICAM_AUTO_STEREO); + set_audio_registers(core, pal_i_nicam); + } + set_audio_finish(core); +} + +static void set_audio_standard_A2(struct cx88_core *core) +{ + /* from dscaler cvs */ + static const struct rlist a2_common[] = { + { AUD_PDF_DDS_CNST_BYTE2, 0x06 }, + { AUD_PDF_DDS_CNST_BYTE1, 0x82 }, + { AUD_PDF_DDS_CNST_BYTE0, 0x12 }, + { AUD_QAM_MODE, 0x05 }, + { AUD_PHACC_FREQ_8MSB, 0x34 }, + { AUD_PHACC_FREQ_8LSB, 0x4c }, + + { AUD_RATE_ADJ1, 0x00001000 }, + { AUD_RATE_ADJ2, 0x00002000 }, + { AUD_RATE_ADJ3, 0x00003000 }, + { AUD_RATE_ADJ4, 0x00004000 }, + { AUD_RATE_ADJ5, 0x00005000 }, + { AUD_THR_FR, 0x00000000 }, + { AAGC_HYST, 0x0000001a }, + { AUD_PILOT_BQD_1_K0, 0x0000755b }, + { AUD_PILOT_BQD_1_K1, 0x00551340 }, + { AUD_PILOT_BQD_1_K2, 0x006d30be }, + { AUD_PILOT_BQD_1_K3, 0xffd394af }, + { AUD_PILOT_BQD_1_K4, 0x00400000 }, + { AUD_PILOT_BQD_2_K0, 0x00040000 }, + { AUD_PILOT_BQD_2_K1, 0x002a4841 }, + { AUD_PILOT_BQD_2_K2, 0x00400000 }, + { AUD_PILOT_BQD_2_K3, 0x00000000 }, + { AUD_PILOT_BQD_2_K4, 0x00000000 }, + { AUD_MODE_CHG_TIMER, 0x00000040 }, + { AUD_START_TIMER, 0x00000200 }, + { AUD_AFE_12DB_EN, 0x00000000 }, + { AUD_CORDIC_SHIFT_0, 0x00000007 }, + { AUD_CORDIC_SHIFT_1, 0x00000007 }, + { AUD_DEEMPH0_G0, 0x00000380 }, + { AUD_DEEMPH1_G0, 0x00000380 }, + { AUD_DCOC_0_SRC, 0x0000001a }, + { AUD_DCOC0_SHIFT, 0x00000000 }, + { AUD_DCOC_0_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_0_SHIFT_IN1, 0x00000008 }, + { AUD_DCOC_PASS_IN, 0x00000003 }, + { AUD_IIR3_0_SEL, 0x00000021 }, + { AUD_DN2_AFC, 0x00000002 }, + { AUD_DCOC_1_SRC, 0x0000001b }, + { AUD_DCOC1_SHIFT, 0x00000000 }, + { AUD_DCOC_1_SHIFT_IN0, 0x0000000a }, + { AUD_DCOC_1_SHIFT_IN1, 0x00000008 }, + { AUD_IIR3_1_SEL, 0x00000023 }, + { AUD_RDSI_SEL, 0x00000017 }, + { AUD_RDSI_SHIFT, 0x00000000 }, + { AUD_RDSQ_SEL, 0x00000017 }, + { AUD_RDSQ_SHIFT, 0x00000000 }, + { AUD_POLYPH80SCALEFAC, 0x00000001 }, + + { /* end of list */ }, + }; + + static const struct rlist a2_table1[] = { + // PAL-BG + { AUD_DMD_RA_DDS, 0x002a73bd }, + { AUD_C1_UP_THR, 0x00007000 }, + { AUD_C1_LO_THR, 0x00005400 }, + { AUD_C2_UP_THR, 0x00005400 }, + { AUD_C2_LO_THR, 0x00003000 }, + { /* end of list */ }, + }; + static const struct rlist a2_table2[] = { + // PAL-DK + { AUD_DMD_RA_DDS, 0x002a73bd }, + { AUD_C1_UP_THR, 0x00007000 }, + { AUD_C1_LO_THR, 0x00005400 }, + { AUD_C2_UP_THR, 0x00005400 }, + { AUD_C2_LO_THR, 0x00003000 }, + { AUD_DN0_FREQ, 0x00003a1c }, + { AUD_DN2_FREQ, 0x0000d2e0 }, + { /* end of list */ }, + }; + static const struct rlist a2_table3[] = { + // unknown, probably NTSC-M + { AUD_DMD_RA_DDS, 0x002a2873 }, + { AUD_C1_UP_THR, 0x00003c00 }, + { AUD_C1_LO_THR, 0x00003000 }, + { AUD_C2_UP_THR, 0x00006000 }, + { AUD_C2_LO_THR, 0x00003c00 }, + { AUD_DN0_FREQ, 0x00002836 }, + { AUD_DN1_FREQ, 0x00003418 }, + { AUD_DN2_FREQ, 0x000029c7 }, + { AUD_POLY0_DDS_CONSTANT, 0x000a7540 }, + { /* end of list */ }, + }; + + set_audio_start(core, 0x0004, EN_DMTRX_SUMDIFF | EN_A2_AUTO_STEREO); + set_audio_registers(core, a2_common); + switch (core->tvaudio) { + case WW_A2_BG: + dprintk("%s PAL-BG A2 (status: known-good)\n",__FUNCTION__); + set_audio_registers(core, a2_table1); + break; + case WW_A2_DK: + dprintk("%s PAL-DK A2 (status: known-good)\n",__FUNCTION__); + set_audio_registers(core, a2_table2); + break; + case WW_A2_M: + dprintk("%s NTSC-M A2 (status: unknown)\n",__FUNCTION__); + set_audio_registers(core, a2_table3); + break; + }; + set_audio_finish(core); +} + +static void set_audio_standard_EIAJ(struct cx88_core *core) +{ + static const struct rlist eiaj[] = { + /* TODO: eiaj register settings are not there yet ... */ + + { /* end of list */ }, + }; + dprintk("%s (status: unknown)\n",__FUNCTION__); + + set_audio_start(core, 0x0002, EN_EIAJ_AUTO_STEREO); + set_audio_registers(core, eiaj); + set_audio_finish(core); +} + +static void set_audio_standard_FM(struct cx88_core *core) +{ +#if 0 /* FIXME */ + switch (dev->audio_properties.FM_deemphasis) + { + case WW_FM_DEEMPH_50: + //Set De-emphasis filter coefficients for 50 usec + cx_write(AUD_DEEMPH0_G0, 0x0C45); + cx_write(AUD_DEEMPH0_A0, 0x6262); + cx_write(AUD_DEEMPH0_B0, 0x1C29); + cx_write(AUD_DEEMPH0_A1, 0x3FC66); + cx_write(AUD_DEEMPH0_B1, 0x399A); + + cx_write(AUD_DEEMPH1_G0, 0x0D80); + cx_write(AUD_DEEMPH1_A0, 0x6262); + cx_write(AUD_DEEMPH1_B0, 0x1C29); + cx_write(AUD_DEEMPH1_A1, 0x3FC66); + cx_write(AUD_DEEMPH1_B1, 0x399A); + + break; + + case WW_FM_DEEMPH_75: + //Set De-emphasis filter coefficients for 75 usec + cx_write(AUD_DEEMPH0_G0, 0x91B ); + cx_write(AUD_DEEMPH0_A0, 0x6B68); + cx_write(AUD_DEEMPH0_B0, 0x11EC); + cx_write(AUD_DEEMPH0_A1, 0x3FC66); + cx_write(AUD_DEEMPH0_B1, 0x399A); + + cx_write(AUD_DEEMPH1_G0, 0xAA0 ); + cx_write(AUD_DEEMPH1_A0, 0x6B68); + cx_write(AUD_DEEMPH1_B0, 0x11EC); + cx_write(AUD_DEEMPH1_A1, 0x3FC66); + cx_write(AUD_DEEMPH1_B1, 0x399A); + + break; + } +#endif + + dprintk("%s (status: unknown)\n",__FUNCTION__); + set_audio_start(core, 0x0020, EN_FMRADIO_AUTO_STEREO); + + // AB: 10/2/01: this register is not being reset appropriately on occasion. + cx_write(AUD_POLYPH80SCALEFAC,3); + + set_audio_finish(core); +} + +/* ----------------------------------------------------------- */ + +void cx88_set_tvaudio(struct cx88_core *core) +{ + switch (core->tvaudio) { + case WW_BTSC: + set_audio_standard_BTSC(core,0); + break; + case WW_NICAM_BGDKL: + set_audio_standard_NICAM_L(core,0); + break; + case WW_NICAM_I: + set_audio_standard_PAL_I(core,0); + break; + case WW_A2_BG: + case WW_A2_DK: + case WW_A2_M: + set_audio_standard_A2(core); + break; + case WW_EIAJ: + set_audio_standard_EIAJ(core); + break; + case WW_FM: + set_audio_standard_FM(core); + break; + case WW_SYSTEM_L_AM: + set_audio_standard_NICAM_L(core, 1); + break; + case WW_NONE: + default: + printk("%s/0: unknown tv audio mode [%d]\n", + core->name, core->tvaudio); + break; + } + return; +} + +void cx88_newstation(struct cx88_core *core) +{ + core->audiomode_manual = UNSET; + + switch (core->tvaudio) { + case WW_SYSTEM_L_AM: + /* try nicam ... */ + core->audiomode_current = V4L2_TUNER_MODE_STEREO; + set_audio_standard_NICAM_L(core, 1); + break; + } +} + +void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t) +{ + static char *m[] = {"stereo", "dual mono", "mono", "sap"}; + static char *p[] = {"no pilot", "pilot c1", "pilot c2", "?"}; + u32 reg,mode,pilot; + + reg = cx_read(AUD_STATUS); + mode = reg & 0x03; + pilot = (reg >> 2) & 0x03; + + if (core->astat != reg) + dprintk("AUD_STATUS: 0x%x [%s/%s] ctl=%s\n", + reg, m[mode], p[pilot], + aud_ctl_names[cx_read(AUD_CTL) & 63]); + core->astat = reg; + + t->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_SAP | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + t->rxsubchans = V4L2_TUNER_SUB_MONO; + t->audmode = V4L2_TUNER_MODE_MONO; + + switch (core->tvaudio) { + case WW_BTSC: + t->capability = V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_SAP; + t->rxsubchans = V4L2_TUNER_SUB_STEREO; + if (1 == pilot) { + /* SAP */ + t->rxsubchans |= V4L2_TUNER_SUB_SAP; + } + break; + case WW_A2_BG: + case WW_A2_DK: + case WW_A2_M: + if (1 == pilot) { + /* stereo */ + t->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + if (0 == mode) + t->audmode = V4L2_TUNER_MODE_STEREO; + } + if (2 == pilot) { + /* dual language -- FIXME */ + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + t->audmode = V4L2_TUNER_MODE_LANG1; + } + break; + case WW_NICAM_BGDKL: + if (0 == mode) { + t->audmode = V4L2_TUNER_MODE_STEREO; + t->rxsubchans |= V4L2_TUNER_SUB_STEREO; + } + break; + case WW_SYSTEM_L_AM: + if (0x0 == mode && !(cx_read(AUD_INIT) & 0x04)) { + t->audmode = V4L2_TUNER_MODE_STEREO; + t->rxsubchans |= V4L2_TUNER_SUB_STEREO; + } + break ; + default: + /* nothing */ + break; + } + return; +} + +void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual) +{ + u32 ctl = UNSET; + u32 mask = UNSET; + + if (manual) { + core->audiomode_manual = mode; + } else { + if (UNSET != core->audiomode_manual) + return; + } + core->audiomode_current = mode; + + switch (core->tvaudio) { + case WW_BTSC: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + ctl = EN_BTSC_FORCE_MONO; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_SAP: + ctl = EN_BTSC_FORCE_SAP; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_BTSC_AUTO_STEREO; + mask = 0x3f; + break; + } + break; + case WW_A2_BG: + case WW_A2_DK: + case WW_A2_M: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + ctl = EN_A2_FORCE_MONO1; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_LANG2: + ctl = EN_A2_AUTO_MONO2; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_A2_AUTO_STEREO | EN_DMTRX_SUMR; + mask = 0x8bf; + break; + } + break; + case WW_NICAM_BGDKL: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + ctl = EN_NICAM_FORCE_MONO1; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_LANG1: + ctl = EN_NICAM_AUTO_MONO2; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_NICAM_FORCE_STEREO | EN_DMTRX_LR; + mask = 0x93f; + break; + } + break; + case WW_SYSTEM_L_AM: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: /* FIXME */ + set_audio_standard_NICAM_L(core, 0); + break; + case V4L2_TUNER_MODE_STEREO: + set_audio_standard_NICAM_L(core, 1); + break; + } + break; + case WW_NICAM_I: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + set_audio_standard_PAL_I(core, 0); + break; + case V4L2_TUNER_MODE_STEREO: + set_audio_standard_PAL_I(core, 1); + break; + } + break; + case WW_FM: + switch (mode) { + case V4L2_TUNER_MODE_MONO: + ctl = EN_FMRADIO_FORCE_MONO; + mask = 0x3f; + break; + case V4L2_TUNER_MODE_STEREO: + ctl = EN_FMRADIO_AUTO_STEREO; + mask = 0x3f; + break; + } + break; + } + + if (UNSET != ctl) { + dprintk("cx88_set_stereo: mask 0x%x, ctl 0x%x " + "[status=0x%x,ctl=0x%x,vol=0x%x]\n", + mask, ctl, cx_read(AUD_STATUS), + cx_read(AUD_CTL), cx_sread(SHADOW_AUD_VOL_CTL)); + cx_andor(AUD_CTL, mask, ctl); + } + return; +} + +int cx88_audio_thread(void *data) +{ + struct cx88_core *core = data; + struct v4l2_tuner t; + u32 mode = 0; + + dprintk("cx88: tvaudio thread started\n"); + for (;;) { + msleep_interruptible(1000); + if (kthread_should_stop()) + break; + + /* just monitor the audio status for now ... */ + memset(&t,0,sizeof(t)); + cx88_get_stereo(core,&t); + + if (UNSET != core->audiomode_manual) + /* manually set, don't do anything. */ + continue; + + /* monitor signal */ + if (t.rxsubchans & V4L2_TUNER_SUB_STEREO) + mode = V4L2_TUNER_MODE_STEREO; + else + mode = V4L2_TUNER_MODE_MONO; + if (mode == core->audiomode_current) + continue; + + /* automatically switch to best available mode */ + cx88_set_stereo(core, mode, 0); + } + + dprintk("cx88: tvaudio thread exiting\n"); + return 0; +} + +/* ----------------------------------------------------------- */ + +EXPORT_SYMBOL(cx88_set_tvaudio); +EXPORT_SYMBOL(cx88_newstation); +EXPORT_SYMBOL(cx88_set_stereo); +EXPORT_SYMBOL(cx88_get_stereo); +EXPORT_SYMBOL(cx88_audio_thread); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-vbi.c b/drivers/media/video/cx88/cx88-vbi.c new file mode 100644 index 000000000000..471e508b0746 --- /dev/null +++ b/drivers/media/video/cx88/cx88-vbi.c @@ -0,0 +1,248 @@ +/* + * $Id: cx88-vbi.c,v 1.16 2004/12/10 12:33:39 kraxel Exp $ + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> + +#include "cx88.h" + +static unsigned int vbibufs = 4; +module_param(vbibufs,int,0644); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32"); + +static unsigned int vbi_debug = 0; +module_param(vbi_debug,int,0644); +MODULE_PARM_DESC(vbi_debug,"enable debug messages [vbi]"); + +#define dprintk(level,fmt, arg...) if (vbi_debug >= level) \ + printk(KERN_DEBUG "%s: " fmt, dev->core->name , ## arg) + +/* ------------------------------------------------------------------ */ + +void cx8800_vbi_fmt(struct cx8800_dev *dev, struct v4l2_format *f) +{ + memset(&f->fmt.vbi,0,sizeof(f->fmt.vbi)); + + f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 244; + f->fmt.vbi.count[0] = VBI_LINE_COUNT; + f->fmt.vbi.count[1] = VBI_LINE_COUNT; + + if (dev->core->tvnorm->id & V4L2_STD_525_60) { + /* ntsc */ + f->fmt.vbi.sampling_rate = 28636363; + f->fmt.vbi.start[0] = 10 -1; + f->fmt.vbi.start[1] = 273 -1; + + } else if (dev->core->tvnorm->id & V4L2_STD_625_50) { + /* pal */ + f->fmt.vbi.sampling_rate = 35468950; + f->fmt.vbi.start[0] = 7 -1; + f->fmt.vbi.start[1] = 319 -1; + } +} + +int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + /* setup fifo + format */ + cx88_sram_channel_setup(dev->core, &cx88_sram_channels[SRAM_CH24], + buf->vb.width, buf->risc.dma); + + cx_write(MO_VBOS_CONTROL, ( (1 << 18) | // comb filter delay fixup + (1 << 15) | // enable vbi capture + (1 << 11) )); + + /* reset counter */ + cx_write(MO_VBI_GPCNTRL, GP_COUNT_CONTROL_RESET); + q->count = 1; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, core->pci_irqmask | 0x01); + cx_set(MO_VID_INTMSK, 0x0f0088); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL,0x18); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); + cx_set(MO_VID_DMACNTRL, 0x88); + + return 0; +} + +int cx8800_stop_vbi_dma(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* stop dma */ + cx_clear(MO_VID_DMACNTRL, 0x88); + + /* disable capture */ + cx_clear(VID_CAPTURE_CONTROL,0x18); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, 0x000001); + cx_clear(MO_VID_INTMSK, 0x0f0088); + return 0; +} + +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf; + struct list_head *item; + + if (list_empty(&q->active)) + return 0; + + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + cx8800_start_vbi_dma(dev, q, buf); + list_for_each(item,&q->active) { + buf = list_entry(item, struct cx88_buffer, vb.queue); + buf->count = q->count++; + } + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +void cx8800_vbi_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vbiq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH24]); + + cx_clear(MO_VID_DMACNTRL, 0x88); + cx_clear(VID_CAPTURE_CONTROL, 0x18); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = STATE_ERROR; + wake_up(&buf->vb.done); + printk("%s/0: [%p/%d] timeout - dma=0x%08lx\n", dev->core->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + cx8800_restart_vbi_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +/* ------------------------------------------------------------------ */ + +static int +vbi_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + *size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + if (0 == *count) + *count = vbibufs; + if (*count < 2) + *count = 2; + if (*count > 32) + *count = 32; + return 0; +} + +static int +vbi_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + unsigned int size; + int rc; + + size = VBI_LINE_COUNT * VBI_LINE_LENGTH * 2; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = VBI_LINE_LENGTH; + buf->vb.height = VBI_LINE_COUNT; + buf->vb.size = size; + buf->vb.field = V4L2_FIELD_SEQ_TB; + + if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) + goto fail; + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + 0, buf->vb.width * buf->vb.height, + buf->vb.width, 0, + buf->vb.height); + } + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + cx88_free_buffer(dev->pci,buf); + return rc; +} + +static void +vbi_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx88_buffer *prev; + struct cx8800_fh *fh = vq->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_dmaqueue *q = &dev->vbiq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + cx8800_start_vbi_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] vbi_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + } +} + +static void vbi_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx8800_fh *fh = q->priv_data; + + cx88_free_buffer(fh->dev->pci,buf); +} + +struct videobuf_queue_ops cx8800_vbi_qops = { + .buf_setup = vbi_setup, + .buf_prepare = vbi_prepare, + .buf_queue = vbi_queue, + .buf_release = vbi_release, +}; + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88-video.c b/drivers/media/video/cx88/cx88-video.c new file mode 100644 index 000000000000..701f594e1816 --- /dev/null +++ b/drivers/media/video/cx88/cx88-video.c @@ -0,0 +1,2277 @@ +/* + * $Id: cx88-video.c,v 1.58 2005/03/07 15:58:05 kraxel Exp $ + * + * device driver for Conexant 2388x based TV cards + * video4linux video interface + * + * (c) 2003-04 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kmod.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <asm/div64.h> + +#include "cx88.h" + +MODULE_DESCRIPTION("v4l2 driver module for cx2388x based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int video_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (CX88_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr,"video device numbers"); +MODULE_PARM_DESC(vbi_nr,"vbi device numbers"); +MODULE_PARM_DESC(radio_nr,"radio device numbers"); + +static unsigned int video_debug = 0; +module_param(video_debug,int,0644); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); + +static unsigned int irq_debug = 0; +module_param(irq_debug,int,0644); +MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]"); + +static unsigned int vid_limit = 16; +module_param(vid_limit,int,0644); +MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes"); + +#define dprintk(level,fmt, arg...) if (video_debug >= level) \ + printk(KERN_DEBUG "%s/0: " fmt, dev->core->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static LIST_HEAD(cx8800_devlist); + +/* ------------------------------------------------------------------- */ +/* static data */ + +static struct cx88_tvnorm tvnorms[] = { + { + .name = "NTSC-M", + .id = V4L2_STD_NTSC_M, + .cxiformat = VideoFormatNTSC, + .cxoformat = 0x181f0008, + },{ + .name = "NTSC-JP", + .id = V4L2_STD_NTSC_M_JP, + .cxiformat = VideoFormatNTSCJapan, + .cxoformat = 0x181f0008, +#if 0 + },{ + .name = "NTSC-4.43", + .id = FIXME, + .cxiformat = VideoFormatNTSC443, + .cxoformat = 0x181f0008, +#endif + },{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + .cxiformat = VideoFormatPAL, + .cxoformat = 0x181f0008, + },{ + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + .cxiformat = VideoFormatPAL, + .cxoformat = 0x181f0008, + },{ + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + .cxiformat = VideoFormatPAL, + .cxoformat = 0x181f0008, + },{ + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + .cxiformat = VideoFormatPALM, + .cxoformat = 0x1c1f0008, + },{ + .name = "PAL-N", + .id = V4L2_STD_PAL_N, + .cxiformat = VideoFormatPALN, + .cxoformat = 0x1c1f0008, + },{ + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + .cxiformat = VideoFormatPALNC, + .cxoformat = 0x1c1f0008, + },{ + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + .cxiformat = VideoFormatPAL60, + .cxoformat = 0x181f0008, + },{ + .name = "SECAM-L", + .id = V4L2_STD_SECAM_L, + .cxiformat = VideoFormatSECAM, + .cxoformat = 0x181f0008, + },{ + .name = "SECAM-DK", + .id = V4L2_STD_SECAM_DK, + .cxiformat = VideoFormatSECAM, + .cxoformat = 0x181f0008, + } +}; + +static struct cx8800_fmt formats[] = { + { + .name = "8 bpp, gray", + .fourcc = V4L2_PIX_FMT_GREY, + .cxformat = ColorFormatY8, + .depth = 8, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .cxformat = ColorFormatRGB15, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .cxformat = ColorFormatRGB15 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .cxformat = ColorFormatRGB16, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .cxformat = ColorFormatRGB16 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .cxformat = ColorFormatRGB24, + .depth = 24, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .cxformat = ColorFormatRGB32, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .cxformat = ColorFormatRGB32 | ColorFormatBSWAP | ColorFormatWSWAP, + .depth = 32, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .cxformat = ColorFormatYUY2, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + },{ + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .cxformat = ColorFormatYUY2 | ColorFormatBSWAP, + .depth = 16, + .flags = FORMAT_FLAGS_PACKED, + }, +}; + +static struct cx8800_fmt* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ------------------------------------------------------------------- */ + +static const struct v4l2_queryctrl no_ctl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; + +static struct cx88_ctrl cx8800_ctls[] = { + /* --- video --- */ + { + .v = { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0x00, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 128, + .reg = MO_CONTR_BRIGHT, + .mask = 0x00ff, + .shift = 0, + },{ + .v = { + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = MO_CONTR_BRIGHT, + .mask = 0xff00, + .shift = 8, + },{ + .v = { + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 0, + .reg = MO_HUE, + .mask = 0x00ff, + .shift = 0, + },{ + /* strictly, this only describes only U saturation. + * V saturation is handled specially through code. + */ + .v = { + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 0xff, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .off = 0, + .reg = MO_UV_SATURATION, + .mask = 0x00ff, + .shift = 0, + },{ + /* --- audio --- */ + .v = { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = (1 << 6), + .shift = 6, + },{ + .v = { + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = 0, + .maximum = 0x3f, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = AUD_VOL_CTL, + .sreg = SHADOW_AUD_VOL_CTL, + .mask = 0x3f, + .shift = 0, + },{ + .v = { + .id = V4L2_CID_AUDIO_BALANCE, + .name = "Balance", + .minimum = 0, + .maximum = 0x7f, + .step = 1, + .default_value = 0x40, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + .reg = AUD_BAL_CTL, + .sreg = SHADOW_AUD_BAL_CTL, + .mask = 0x7f, + .shift = 0, + } +}; +const int CX8800_CTLS = ARRAY_SIZE(cx8800_ctls); + +/* ------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + down(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + up(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk(1,"res: get %d\n",bit); + up(&dev->lock); + return 1; +} + +static +int res_check(struct cx8800_fh *fh, unsigned int bit) +{ + return (fh->resources & bit); +} + +static +int res_locked(struct cx8800_dev *dev, unsigned int bit) +{ + return (dev->resources & bit); +} + +static +void res_free(struct cx8800_dev *dev, struct cx8800_fh *fh, unsigned int bits) +{ + if ((fh->resources & bits) != bits) + BUG(); + + down(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk(1,"res: put %d\n",bits); + up(&dev->lock); +} + +/* ------------------------------------------------------------------ */ + +static int video_mux(struct cx8800_dev *dev, unsigned int input) +{ + struct cx88_core *core = dev->core; + + dprintk(1,"video_mux: %d [vmux=%d,gpio=0x%x,0x%x,0x%x,0x%x]\n", + input, INPUT(input)->vmux, + INPUT(input)->gpio0,INPUT(input)->gpio1, + INPUT(input)->gpio2,INPUT(input)->gpio3); + dev->core->input = input; + cx_andor(MO_INPUT_FORMAT, 0x03 << 14, INPUT(input)->vmux << 14); + cx_write(MO_GP3_IO, INPUT(input)->gpio3); + cx_write(MO_GP0_IO, INPUT(input)->gpio0); + cx_write(MO_GP1_IO, INPUT(input)->gpio1); + cx_write(MO_GP2_IO, INPUT(input)->gpio2); + + switch (INPUT(input)->type) { + case CX88_VMUX_SVIDEO: + cx_set(MO_AFECFG_IO, 0x00000001); + cx_set(MO_INPUT_FORMAT, 0x00010010); + cx_set(MO_FILTER_EVEN, 0x00002020); + cx_set(MO_FILTER_ODD, 0x00002020); + break; + default: + cx_clear(MO_AFECFG_IO, 0x00000001); + cx_clear(MO_INPUT_FORMAT, 0x00010010); + cx_clear(MO_FILTER_EVEN, 0x00002020); + cx_clear(MO_FILTER_ODD, 0x00002020); + break; + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int start_video_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf) +{ + struct cx88_core *core = dev->core; + + /* setup fifo + format */ + cx88_sram_channel_setup(dev->core, &cx88_sram_channels[SRAM_CH21], + buf->bpl, buf->risc.dma); + cx88_set_scale(dev->core, buf->vb.width, buf->vb.height, buf->vb.field); + cx_write(MO_COLOR_CTRL, buf->fmt->cxformat | ColorFormatGamma); + + /* reset counter */ + cx_write(MO_VIDY_GPCNTRL,GP_COUNT_CONTROL_RESET); + q->count = 1; + + /* enable irqs */ + cx_set(MO_PCI_INTMSK, core->pci_irqmask | 0x01); + cx_set(MO_VID_INTMSK, 0x0f0011); + + /* enable capture */ + cx_set(VID_CAPTURE_CONTROL,0x06); + + /* start dma */ + cx_set(MO_DEV_CNTRL2, (1<<5)); + cx_set(MO_VID_DMACNTRL, 0x11); + + return 0; +} + +static int stop_video_dma(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + + /* stop dma */ + cx_clear(MO_VID_DMACNTRL, 0x11); + + /* disable capture */ + cx_clear(VID_CAPTURE_CONTROL,0x06); + + /* disable irqs */ + cx_clear(MO_PCI_INTMSK, 0x000001); + cx_clear(MO_VID_INTMSK, 0x0f0011); + return 0; +} + +static int restart_video_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q) +{ + struct cx88_buffer *buf, *prev; + struct list_head *item; + + if (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + dprintk(2,"restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + start_video_dma(dev, q, buf); + list_for_each(item,&q->active) { + buf = list_entry(item, struct cx88_buffer, vb.queue); + buf->count = q->count++; + } + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&q->queued)) + return 0; + buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue); + if (NULL == prev) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] restart_queue - first active\n", + buf,buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_del(&buf->vb.queue); + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] restart_queue - move to active\n", + buf,buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +/* ------------------------------------------------------------------ */ + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct cx8800_fh *fh = q->priv_data; + + *size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 == *count) + *count = 32; + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + return 0; +} + +static int +buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct cx8800_fh *fh = q->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + int rc, init_buffer = 0; + + BUG_ON(NULL == fh->fmt); + if (fh->width < 48 || fh->width > norm_maxw(dev->core->tvnorm) || + fh->height < 32 || fh->height > norm_maxh(dev->core->tvnorm)) + return -EINVAL; + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + init_buffer = 1; + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + init_buffer = 1; + if (0 != (rc = videobuf_iolock(dev->pci,&buf->vb,NULL))) + goto fail; + } + + if (init_buffer) { + buf->bpl = buf->vb.width * buf->fmt->depth >> 3; + switch (buf->vb.field) { + case V4L2_FIELD_TOP: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, 0, UNSET, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_BOTTOM: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, UNSET, 0, + buf->bpl, 0, buf->vb.height); + break; + case V4L2_FIELD_INTERLACED: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, 0, buf->bpl, + buf->bpl, buf->bpl, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_TB: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + 0, buf->bpl * (buf->vb.height >> 1), + buf->bpl, 0, + buf->vb.height >> 1); + break; + case V4L2_FIELD_SEQ_BT: + cx88_risc_buffer(dev->pci, &buf->risc, + buf->vb.dma.sglist, + buf->bpl * (buf->vb.height >> 1), 0, + buf->bpl, 0, + buf->vb.height >> 1); + break; + default: + BUG(); + } + } + dprintk(2,"[%p/%d] buffer_prepare - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", + buf, buf->vb.i, + fh->width, fh->height, fh->fmt->depth, fh->fmt->name, + (unsigned long)buf->risc.dma); + + buf->vb.state = STATE_PREPARED; + return 0; + + fail: + cx88_free_buffer(dev->pci,buf); + return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx88_buffer *prev; + struct cx8800_fh *fh = vq->priv_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_dmaqueue *q = &dev->vidq; + + /* add jump to stopper */ + buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC); + buf->risc.jmp[1] = cpu_to_le32(q->stopper.dma); + + if (!list_empty(&q->queued)) { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = STATE_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + + } else if (list_empty(&q->active)) { + list_add_tail(&buf->vb.queue,&q->active); + start_video_dma(dev, q, buf); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2,"[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + } else { + prev = list_entry(q->active.prev, struct cx88_buffer, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue,&q->active); + buf->vb.state = STATE_ACTIVE; + buf->count = q->count++; + prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma); + dprintk(2,"[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + + } else { + list_add_tail(&buf->vb.queue,&q->queued); + buf->vb.state = STATE_QUEUED; + dprintk(2,"[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct cx88_buffer *buf = container_of(vb,struct cx88_buffer,vb); + struct cx8800_fh *fh = q->priv_data; + + cx88_free_buffer(fh->dev->pci,buf); +} + +struct videobuf_queue_ops cx8800_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +#if 0 /* overlay support not finished yet */ +static u32* ov_risc_field(struct cx8800_dev *dev, struct cx8800_fh *fh, + u32 *rp, struct btcx_skiplist *skips, + u32 sync_line, int skip_even, int skip_odd) +{ + int line,maxy,start,end,skip,nskips; + u32 ri,ra; + u32 addr; + + /* sync instruction */ + *(rp++) = cpu_to_le32(RISC_RESYNC | sync_line); + + addr = (unsigned long)dev->fbuf.base; + addr += dev->fbuf.fmt.bytesperline * fh->win.w.top; + addr += (fh->fmt->depth >> 3) * fh->win.w.left; + + /* scan lines */ + for (maxy = -1, line = 0; line < fh->win.w.height; + line++, addr += dev->fbuf.fmt.bytesperline) { + if ((line%2) == 0 && skip_even) + continue; + if ((line%2) == 1 && skip_odd) + continue; + + /* calculate clipping */ + if (line > maxy) + btcx_calc_skips(line, fh->win.w.width, &maxy, + skips, &nskips, fh->clips, fh->nclips); + + /* write out risc code */ + for (start = 0, skip = 0; start < fh->win.w.width; start = end) { + if (skip >= nskips) { + ri = RISC_WRITE; + end = fh->win.w.width; + } else if (start < skips[skip].start) { + ri = RISC_WRITE; + end = skips[skip].start; + } else { + ri = RISC_SKIP; + end = skips[skip].end; + skip++; + } + if (RISC_WRITE == ri) + ra = addr + (fh->fmt->depth>>3)*start; + else + ra = 0; + + if (0 == start) + ri |= RISC_SOL; + if (fh->win.w.width == end) + ri |= RISC_EOL; + ri |= (fh->fmt->depth>>3) * (end-start); + + *(rp++)=cpu_to_le32(ri); + if (0 != ra) + *(rp++)=cpu_to_le32(ra); + } + } + kfree(skips); + return rp; +} + +static int ov_risc_frame(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct cx88_buffer *buf) +{ + struct btcx_skiplist *skips; + u32 instructions,fields; + u32 *rp; + int rc; + + /* skip list for window clipping */ + if (NULL == (skips = kmalloc(sizeof(*skips) * fh->nclips,GFP_KERNEL))) + return -ENOMEM; + + fields = 0; + if (V4L2_FIELD_HAS_TOP(fh->win.field)) + fields++; + if (V4L2_FIELD_HAS_BOTTOM(fh->win.field)) + fields++; + + /* estimate risc mem: worst case is (clip+1) * lines instructions + + syncs + jump (all 2 dwords) */ + instructions = (fh->nclips+1) * fh->win.w.height; + instructions += 3 + 4; + if ((rc = btcx_riscmem_alloc(dev->pci,&buf->risc,instructions*8)) < 0) { + kfree(skips); + return rc; + } + + /* write risc instructions */ + rp = buf->risc.cpu; + switch (fh->win.field) { + case V4L2_FIELD_TOP: + rp = ov_risc_field(dev, fh, rp, skips, 0, 0, 0); + break; + case V4L2_FIELD_BOTTOM: + rp = ov_risc_field(dev, fh, rp, skips, 0x200, 0, 0); + break; + case V4L2_FIELD_INTERLACED: + rp = ov_risc_field(dev, fh, rp, skips, 0, 0, 1); + rp = ov_risc_field(dev, fh, rp, skips, 0x200, 1, 0); + break; + default: + BUG(); + } + + /* save pointer to jmp instruction address */ + buf->risc.jmp = rp; + kfree(skips); + return 0; +} + +static int verify_window(struct cx8800_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (NULL == dev->fbuf.base) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = norm_maxw(core->tvnorm); + maxh = norm_maxh(core->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +static int setup_window(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_window *win) +{ + struct v4l2_clip *clips = NULL; + int n,size,retval = 0; + + if (NULL == fh->fmt) + return -EINVAL; + retval = verify_window(dev,win); + if (0 != retval) + return retval; + + /* copy clips -- luckily v4l1 + v4l2 are binary + compatible here ...*/ + n = win->clipcount; + size = sizeof(*clips)*(n+4); + clips = kmalloc(size,GFP_KERNEL); + if (NULL == clips) + return -ENOMEM; + if (n > 0) { + if (copy_from_user(clips,win->clips,sizeof(struct v4l2_clip)*n)) { + kfree(clips); + return -EFAULT; + } + } + + /* clip against screen */ + if (NULL != dev->fbuf.base) + n = btcx_screen_clips(dev->fbuf.fmt.width, dev->fbuf.fmt.height, + &win->w, clips, n); + btcx_sort_clips(clips,n); + + /* 4-byte alignments */ + switch (fh->fmt->depth) { + case 8: + case 24: + btcx_align(&win->w, clips, n, 3); + break; + case 16: + btcx_align(&win->w, clips, n, 1); + break; + case 32: + /* no alignment fixups needed */ + break; + default: + BUG(); + } + + down(&fh->vidq.lock); + if (fh->clips) + kfree(fh->clips); + fh->clips = clips; + fh->nclips = n; + fh->win = *win; +#if 0 + fh->ov.setup_ok = 1; +#endif + + /* update overlay if needed */ + retval = 0; +#if 0 + if (check_btres(fh, RESOURCE_OVERLAY)) { + struct bttv_buffer *new; + + new = videobuf_alloc(sizeof(*new)); + bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new); + retval = bttv_switch_overlay(btv,fh,new); + } +#endif + up(&fh->vidq.lock); + return retval; +} +#endif + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue* get_queue(struct cx8800_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &fh->vidq; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return &fh->vbiq; + default: + BUG(); + return NULL; + } +} + +static int get_ressource(struct cx8800_fh *fh) +{ + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return RESOURCE_VIDEO; + case V4L2_BUF_TYPE_VBI_CAPTURE: + return RESOURCE_VBI; + default: + BUG(); + return 0; + } +} + +static int video_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct cx8800_dev *h,*dev = NULL; + struct cx8800_fh *fh; + struct list_head *list; + enum v4l2_buf_type type = 0; + int radio = 0; + + list_for_each(list,&cx8800_devlist) { + h = list_entry(list, struct cx8800_dev, devlist); + if (h->video_dev->minor == minor) { + dev = h; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } + if (h->vbi_dev->minor == minor) { + dev = h; + type = V4L2_BUF_TYPE_VBI_CAPTURE; + } + if (h->radio_dev && + h->radio_dev->minor == minor) { + radio = 1; + dev = h; + } + } + if (NULL == dev) + return -ENODEV; + + dprintk(1,"open minor=%d radio=%d type=%s\n", + minor,radio,v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + memset(fh,0,sizeof(*fh)); + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->width = 320; + fh->height = 240; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + + videobuf_queue_init(&fh->vidq, &cx8800_video_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct cx88_buffer), + fh); + videobuf_queue_init(&fh->vbiq, &cx8800_vbi_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct cx88_buffer), + fh); + + if (fh->radio) { + struct cx88_core *core = dev->core; + int board = core->board; + dprintk(1,"video_open: setting radio device\n"); + cx_write(MO_GP0_IO, cx88_boards[board].radio.gpio0); + cx_write(MO_GP1_IO, cx88_boards[board].radio.gpio1); + cx_write(MO_GP2_IO, cx88_boards[board].radio.gpio2); + cx_write(MO_GP3_IO, cx88_boards[board].radio.gpio3); + dev->core->tvaudio = WW_FM; + cx88_set_tvaudio(core); + cx88_set_stereo(core,V4L2_TUNER_MODE_STEREO,1); + cx88_call_i2c_clients(dev->core,AUDC_SET_RADIO,NULL); + } + + return 0; +} + +static ssize_t +video_read(struct file *file, char *data, size_t count, loff_t *ppos) +{ + struct cx8800_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(&fh->vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(&fh->vbiq, data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cx8800_fh *fh = file->private_data; + struct cx88_buffer *buf; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) { + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return POLLERR; + return videobuf_poll_stream(file, &fh->vbiq, wait); + } + + if (res_check(fh,RESOURCE_VIDEO)) { + /* streaming capture */ + if (list_empty(&fh->vidq.stream)) + return POLLERR; + buf = list_entry(fh->vidq.stream.next,struct cx88_buffer,vb.stream); + } else { + /* read() capture */ + buf = (struct cx88_buffer*)fh->vidq.read_buf; + if (NULL == buf) + return POLLERR; + } + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == STATE_DONE || + buf->vb.state == STATE_ERROR) + return POLLIN|POLLRDNORM; + return 0; +} + +static int video_release(struct inode *inode, struct file *file) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + /* FIXME */ + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_queue_cancel(&fh->vidq); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->vidq.read_buf) { + buffer_release(&fh->vidq,fh->vidq.read_buf); + kfree(fh->vidq.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + if (fh->vbiq.streaming) + videobuf_streamoff(&fh->vbiq); + if (fh->vbiq.reading) + videobuf_read_stop(&fh->vbiq); + res_free(dev,fh,RESOURCE_VBI); + } + + videobuf_mmap_free(&fh->vidq); + videobuf_mmap_free(&fh->vbiq); + file->private_data = NULL; + kfree(fh); + return 0; +} + +static int +video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct cx8800_fh *fh = file->private_data; + + return videobuf_mmap_mapper(get_queue(fh), vma); +} + +/* ------------------------------------------------------------------ */ + +static int get_control(struct cx8800_dev *dev, struct v4l2_control *ctl) +{ + struct cx88_core *core = dev->core; + struct cx88_ctrl *c = NULL; + u32 value; + int i; + + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == ctl->id) + c = &cx8800_ctls[i]; + if (NULL == c) + return -EINVAL; + + value = c->sreg ? cx_sread(c->sreg) : cx_read(c->reg); + switch (ctl->id) { + case V4L2_CID_AUDIO_BALANCE: + ctl->value = (value & 0x40) ? (value & 0x3f) : (0x40 - (value & 0x3f)); + break; + case V4L2_CID_AUDIO_VOLUME: + ctl->value = 0x3f - (value & 0x3f); + break; + default: + ctl->value = ((value + (c->off << c->shift)) & c->mask) >> c->shift; + break; + } + return 0; +} + +static int set_control(struct cx8800_dev *dev, struct v4l2_control *ctl) +{ + struct cx88_core *core = dev->core; + struct cx88_ctrl *c = NULL; + u32 v_sat_value; + u32 value; + int i; + + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == ctl->id) + c = &cx8800_ctls[i]; + if (NULL == c) + return -EINVAL; + + if (ctl->value < c->v.minimum) + return -ERANGE; + if (ctl->value > c->v.maximum) + return -ERANGE; + switch (ctl->id) { + case V4L2_CID_AUDIO_BALANCE: + value = (ctl->value < 0x40) ? (0x40 - ctl->value) : ctl->value; + break; + case V4L2_CID_AUDIO_VOLUME: + value = 0x3f - (ctl->value & 0x3f); + break; + case V4L2_CID_SATURATION: + /* special v_sat handling */ + v_sat_value = ctl->value - (0x7f - 0x5a); + if (v_sat_value > 0xff) + v_sat_value = 0xff; + if (v_sat_value < 0x00) + v_sat_value = 0x00; + cx_andor(MO_UV_SATURATION, 0xff00, v_sat_value << 8); + /* fall through to default route for u_sat */ + default: + value = ((ctl->value - c->off) << c->shift) & c->mask; + break; + } + dprintk(1,"set_control id=0x%X reg=0x%x val=0x%x%s\n", + ctl->id, c->reg, value, c->sreg ? " [shadowed]" : ""); + if (c->sreg) { + cx_sandor(c->sreg, c->reg, c->mask, value); + } else { + cx_andor(c->reg, c->mask, value); + } + return 0; +} + +static void init_controls(struct cx8800_dev *dev) +{ + static struct v4l2_control mute = { + .id = V4L2_CID_AUDIO_MUTE, + .value = 1, + }; + static struct v4l2_control volume = { + .id = V4L2_CID_AUDIO_VOLUME, + .value = 0x3f, + }; + + set_control(dev,&mute); + set_control(dev,&volume); +} + +/* ------------------------------------------------------------------ */ + +static int cx8800_g_fmt(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(&f->fmt.pix,0,sizeof(f->fmt.pix)); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + cx8800_vbi_fmt(dev, f); + return 0; + default: + return -EINVAL; + } +} + +static int cx8800_try_fmt(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + struct cx8800_fmt *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = norm_maxw(dev->core->tvnorm); + maxh = norm_maxh(dev->core->tvnorm); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; + } + case V4L2_BUF_TYPE_VBI_CAPTURE: + cx8800_vbi_fmt(dev, f); + return 0; + default: + return -EINVAL; + } +} + +static int cx8800_s_fmt(struct cx8800_dev *dev, struct cx8800_fh *fh, + struct v4l2_format *f) +{ + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + err = cx8800_try_fmt(dev,fh,f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->vidq.field = f->fmt.pix.field; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + cx8800_vbi_fmt(dev, f); + return 0; + default: + return -EINVAL; + } +} + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel pointer. + */ +static int video_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; +#if 0 + unsigned long flags; +#endif + int err; + + if (video_debug > 1) + cx88_print_ioctl(core->name,cmd); + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "cx8800"); + strlcpy(cap->card, cx88_boards[core->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = CX88_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING | + V4L2_CAP_VBI_CAPTURE | +#if 0 + V4L2_CAP_VIDEO_OVERLAY | +#endif + 0; + if (UNSET != core->tuner_type) + cap->capabilities |= V4L2_CAP_TUNER; + return 0; + } + + /* ---------- tv norms ---------- */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int i; + + i = e->index; + if (i >= ARRAY_SIZE(tvnorms)) + return -EINVAL; + err = v4l2_video_std_construct(e, tvnorms[e->index].id, + tvnorms[e->index].name); + e->index = i; + if (err < 0) + return err; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + + *id = core->tvnorm->id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + + for(i = 0; i < ARRAY_SIZE(tvnorms); i++) + if (*id & tvnorms[i].id) + break; + if (i == ARRAY_SIZE(tvnorms)) + return -EINVAL; + + down(&dev->lock); + cx88_set_tvnorm(dev->core,&tvnorms[i]); + up(&dev->lock); + return 0; + } + + /* ------ input switching ---------- */ + case VIDIOC_ENUMINPUT: + { + static const char *iname[] = { + [ CX88_VMUX_COMPOSITE1 ] = "Composite1", + [ CX88_VMUX_COMPOSITE2 ] = "Composite2", + [ CX88_VMUX_COMPOSITE3 ] = "Composite3", + [ CX88_VMUX_COMPOSITE4 ] = "Composite4", + [ CX88_VMUX_SVIDEO ] = "S-Video", + [ CX88_VMUX_TELEVISION ] = "Television", + [ CX88_VMUX_CABLE ] = "Cable TV", + [ CX88_VMUX_DVB ] = "DVB", + [ CX88_VMUX_DEBUG ] = "for debug only", + }; + struct v4l2_input *i = arg; + unsigned int n; + + n = i->index; + if (n >= 4) + return -EINVAL; + if (0 == INPUT(n)->type) + return -EINVAL; + memset(i,0,sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,iname[INPUT(n)->type]); + if ((CX88_VMUX_TELEVISION == INPUT(n)->type) || + (CX88_VMUX_CABLE == INPUT(n)->type)) + i->type = V4L2_INPUT_TYPE_TUNER; + for (n = 0; n < ARRAY_SIZE(tvnorms); n++) + i->std |= tvnorms[n].id; + return 0; + } + case VIDIOC_G_INPUT: + { + unsigned int *i = arg; + + *i = dev->core->input; + return 0; + } + case VIDIOC_S_INPUT: + { + unsigned int *i = arg; + + if (*i >= 4) + return -EINVAL; + down(&dev->lock); + cx88_newstation(core); + video_mux(dev,*i); + up(&dev->lock); + return 0; + } + + +#if 0 + /* needs review */ + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + unsigned int n = a->index; + + memset(a,0,sizeof(*a)); + a->index = n; + switch (n) { + case 0: + if ((CX88_VMUX_TELEVISION == INPUT(n)->type) + || (CX88_VMUX_CABLE == INPUT(n)->type)) { + strcpy(a->name,"Television"); + // FIXME figure out if stereo received and set V4L2_AUDCAP_STEREO. + return 0; + } + break; + case 1: + if (CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD == core->board) { + strcpy(a->name,"Line In"); + a->capability = V4L2_AUDCAP_STEREO; + return 0; + } + break; + } + // Audio input not available. + return -EINVAL; + } +#endif + + /* --- capture ioctls ---------------------------------------- */ + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + enum v4l2_buf_type type; + unsigned int index; + + index = f->index; + type = f->type; + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (index >= ARRAY_SIZE(formats)) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + strlcpy(f->description,formats[index].name,sizeof(f->description)); + f->pixelformat = formats[index].fourcc; + break; + default: + return -EINVAL; + } + return 0; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + return cx8800_g_fmt(dev,fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + return cx8800_s_fmt(dev,fh,f); + } + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + return cx8800_try_fmt(dev,fh,f); + } + + /* --- controls ---------------------------------------------- */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *c = arg; + int i; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == c->id) + break; + if (i == CX8800_CTLS) { + *c = no_ctl; + return 0; + } + *c = cx8800_ctls[i].v; + return 0; + } + case VIDIOC_G_CTRL: + return get_control(dev,arg); + case VIDIOC_S_CTRL: + return set_control(dev,arg); + + /* --- tuner ioctls ------------------------------------------ */ + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + u32 reg; + + if (UNSET == core->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + + cx88_get_stereo(core ,t); + reg = cx_read(MO_DEVICE_STATUS); + t->signal = (reg & (1<<5)) ? 0xffff : 0x0000; + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + + if (UNSET == core->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + cx88_set_stereo(core, t->audmode, 1); + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (UNSET == core->tuner_type) + return -EINVAL; + if (f->tuner != 0) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (UNSET == core->tuner_type) + return -EINVAL; + if (f->tuner != 0) + return -EINVAL; + if (0 == fh->radio && f->type != V4L2_TUNER_ANALOG_TV) + return -EINVAL; + if (1 == fh->radio && f->type != V4L2_TUNER_RADIO) + return -EINVAL; + down(&dev->lock); + dev->freq = f->frequency; + cx88_newstation(core); +#ifdef V4L2_I2C_CLIENTS + cx88_call_i2c_clients(dev->core,VIDIOC_S_FREQUENCY,f); +#else + cx88_call_i2c_clients(dev->core,VIDIOCSFREQ,&dev->freq); +#endif + up(&dev->lock); + return 0; + } + + /* --- streaming capture ------------------------------------- */ + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + struct videobuf_queue *q; + struct v4l2_requestbuffers req; + unsigned int i; + + q = get_queue(fh); + memset(&req,0,sizeof(req)); + req.type = q->type; + req.count = 8; + req.memory = V4L2_MEMORY_MMAP; + err = videobuf_reqbufs(q,&req); + if (err < 0) + return err; + memset(mbuf,0,sizeof(*mbuf)); + mbuf->frames = req.count; + mbuf->size = 0; + for (i = 0; i < mbuf->frames; i++) { + mbuf->offsets[i] = q->bufs[i]->boff; + mbuf->size += q->bufs[i]->bsize; + } + return 0; + } + case VIDIOC_REQBUFS: + return videobuf_reqbufs(get_queue(fh), arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(get_queue(fh), arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(get_queue(fh), arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(get_queue(fh), arg, + file->f_flags & O_NONBLOCK); + + case VIDIOC_STREAMON: + { + int res = get_ressource(fh); + + if (!res_get(dev,fh,res)) + return -EBUSY; + return videobuf_streamon(get_queue(fh)); + } + case VIDIOC_STREAMOFF: + { + int res = get_ressource(fh); + + err = videobuf_streamoff(get_queue(fh)); + if (err < 0) + return err; + res_free(dev,fh,res); + return 0; + } + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + video_do_ioctl); + } + return 0; +} + +static int video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, video_do_ioctl); +} + +/* ----------------------------------------------------------- */ + +static int radio_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct cx8800_fh *fh = file->private_data; + struct cx8800_dev *dev = fh->dev; + struct cx88_core *core = dev->core; + + if (video_debug > 1) + cx88_print_ioctl(core->name,cmd); + + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "cx8800"); + strlcpy(cap->card, cx88_boards[core->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s", pci_name(dev->pci)); + cap->version = CX88_VERSION_CODE; + cap->capabilities = V4L2_CAP_TUNER; + return 0; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + + if (t->index > 0) + return -EINVAL; + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Radio"); + t->rangelow = (int)(65*16); + t->rangehigh = (int)(108*16); + +#ifdef V4L2_I2C_CLIENTS + cx88_call_i2c_clients(dev->core,VIDIOC_G_TUNER,t); +#else + { + struct video_tuner vt; + memset(&vt,0,sizeof(vt)); + cx88_call_i2c_clients(dev,VIDIOCGTUNER,&vt); + t->signal = vt.signal; + } +#endif + return 0; + } + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + strcpy(i->name,"Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = 0; + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + memset(a,0,sizeof(*a)); + strcpy(a->name,"Radio"); + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = 0; + return 0; + } + case VIDIOC_S_AUDIO: + case VIDIOC_S_TUNER: + case VIDIOC_S_INPUT: + case VIDIOC_S_STD: + return 0; + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *c = arg; + int i; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + if (c->id == V4L2_CID_AUDIO_MUTE) { + for (i = 0; i < CX8800_CTLS; i++) + if (cx8800_ctls[i].v.id == c->id) + break; + *c = cx8800_ctls[i].v; + } else + *c = no_ctl; + return 0; + } + + + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + return video_do_ioctl(inode,file,cmd,arg); + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + radio_do_ioctl); + } + return 0; +}; + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, radio_do_ioctl); +}; + +/* ----------------------------------------------------------- */ + +static void cx8800_vid_timeout(unsigned long data) +{ + struct cx8800_dev *dev = (struct cx8800_dev*)data; + struct cx88_core *core = dev->core; + struct cx88_dmaqueue *q = &dev->vidq; + struct cx88_buffer *buf; + unsigned long flags; + + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH21]); + + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + + spin_lock_irqsave(&dev->slock,flags); + while (!list_empty(&q->active)) { + buf = list_entry(q->active.next, struct cx88_buffer, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = STATE_ERROR; + wake_up(&buf->vb.done); + printk("%s/0: [%p/%d] timeout - dma=0x%08lx\n", core->name, + buf, buf->vb.i, (unsigned long)buf->risc.dma); + } + restart_video_queue(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +static void cx8800_vid_irq(struct cx8800_dev *dev) +{ + struct cx88_core *core = dev->core; + u32 status, mask, count; + + status = cx_read(MO_VID_INTSTAT); + mask = cx_read(MO_VID_INTMSK); + if (0 == (status & mask)) + return; + cx_write(MO_VID_INTSTAT, status); + if (irq_debug || (status & mask & ~0xff)) + cx88_print_irqbits(core->name, "irq vid", + cx88_vid_irqs, status, mask); + + /* risc op code error */ + if (status & (1 << 16)) { + printk(KERN_WARNING "%s/0: video risc op code error\n",core->name); + cx_clear(MO_VID_DMACNTRL, 0x11); + cx_clear(VID_CAPTURE_CONTROL, 0x06); + cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH21]); + } + + /* risc1 y */ + if (status & 0x01) { + spin_lock(&dev->slock); + count = cx_read(MO_VIDY_GPCNT); + cx88_wakeup(dev->core, &dev->vidq, count); + spin_unlock(&dev->slock); + } + + /* risc1 vbi */ + if (status & 0x08) { + spin_lock(&dev->slock); + count = cx_read(MO_VBI_GPCNT); + cx88_wakeup(dev->core, &dev->vbiq, count); + spin_unlock(&dev->slock); + } + + /* risc2 y */ + if (status & 0x10) { + dprintk(2,"stopper video\n"); + spin_lock(&dev->slock); + restart_video_queue(dev,&dev->vidq); + spin_unlock(&dev->slock); + } + + /* risc2 vbi */ + if (status & 0x80) { + dprintk(2,"stopper vbi\n"); + spin_lock(&dev->slock); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + spin_unlock(&dev->slock); + } +} + +static irqreturn_t cx8800_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cx8800_dev *dev = dev_id; + struct cx88_core *core = dev->core; + u32 status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + status = cx_read(MO_PCI_INTSTAT) & (core->pci_irqmask | 0x01); + if (0 == status) + goto out; + cx_write(MO_PCI_INTSTAT, status); + handled = 1; + + if (status & core->pci_irqmask) + cx88_core_irq(core,status); + if (status & 0x01) + cx8800_vid_irq(dev); + }; + if (10 == loop) { + printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n", + core->name); + cx_write(MO_PCI_INTMSK,0); + } + + out: + return IRQ_RETVAL(handled); +} + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static struct file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl, + .llseek = no_llseek, +}; + +struct video_device cx8800_video_template = +{ + .name = "cx8800-video", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_SCALES, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +struct video_device cx8800_vbi_template = +{ + .name = "cx8800-vbi", + .type = VID_TYPE_TELETEXT|VID_TYPE_TUNER, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +static struct file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .ioctl = radio_ioctl, + .llseek = no_llseek, +}; + +struct video_device cx8800_radio_template = +{ + .name = "cx8800-radio", + .type = VID_TYPE_TUNER, + .hardware = 0, + .fops = &radio_fops, + .minor = -1, +}; + +/* ----------------------------------------------------------- */ + +static void cx8800_unregister_video(struct cx8800_dev *dev) +{ + if (dev->radio_dev) { + if (-1 != dev->radio_dev->minor) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } + if (dev->vbi_dev) { + if (-1 != dev->vbi_dev->minor) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->video_dev) { + if (-1 != dev->video_dev->minor) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } +} + +static int __devinit cx8800_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct cx8800_dev *dev; + struct cx88_core *core; + int err; + + dev = kmalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + memset(dev,0,sizeof(*dev)); + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail_free; + } + core = cx88_core_get(dev->pci); + if (NULL == core) { + err = -EINVAL; + goto fail_free; + } + dev->core = core; + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s/0: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%lx\n", core->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat,pci_resource_start(pci_dev,0)); + + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev,0xffffffff)) { + printk("%s/0: Oops: no 32bit PCI DMA ???\n",core->name); + err = -EIO; + goto fail_core; + } + + /* initialize driver struct */ + init_MUTEX(&dev->lock); + spin_lock_init(&dev->slock); + core->tvnorm = tvnorms; + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vidq.queued); + dev->vidq.timeout.function = cx8800_vid_timeout; + dev->vidq.timeout.data = (unsigned long)dev; + init_timer(&dev->vidq.timeout); + cx88_risc_stopper(dev->pci,&dev->vidq.stopper, + MO_VID_DMACNTRL,0x11,0x00); + + /* init vbi dma queues */ + INIT_LIST_HEAD(&dev->vbiq.active); + INIT_LIST_HEAD(&dev->vbiq.queued); + dev->vbiq.timeout.function = cx8800_vbi_timeout; + dev->vbiq.timeout.data = (unsigned long)dev; + init_timer(&dev->vbiq.timeout); + cx88_risc_stopper(dev->pci,&dev->vbiq.stopper, + MO_VID_DMACNTRL,0x88,0x00); + + /* get irq */ + err = request_irq(pci_dev->irq, cx8800_irq, + SA_SHIRQ | SA_INTERRUPT, core->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + core->name,pci_dev->irq); + goto fail_core; + } + cx_set(MO_PCI_INTMSK, core->pci_irqmask); + + /* load and configure helper modules */ + if (TUNER_ABSENT != core->tuner_type) + request_module("tuner"); + if (core->tda9887_conf) + request_module("tda9887"); + if (core->tuner_type != UNSET) + cx88_call_i2c_clients(dev->core,TUNER_SET_TYPE,&core->tuner_type); + if (core->tda9887_conf) + cx88_call_i2c_clients(dev->core,TDA9887_SET_CONFIG,&core->tda9887_conf); + + /* register v4l devices */ + dev->video_dev = cx88_vdev_init(core,dev->pci, + &cx8800_video_template,"video"); + err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, + video_nr[core->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device video%d [v4l2]\n", + core->name,dev->video_dev->minor & 0x1f); + + dev->vbi_dev = cx88_vdev_init(core,dev->pci,&cx8800_vbi_template,"vbi"); + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[core->nr]); + if (err < 0) { + printk(KERN_INFO "%s/0: can't register vbi device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device vbi%d\n", + core->name,dev->vbi_dev->minor & 0x1f); + + if (core->has_radio) { + dev->radio_dev = cx88_vdev_init(core,dev->pci, + &cx8800_radio_template,"radio"); + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[core->nr]); + if (err < 0) { + printk(KERN_INFO "%s/0: can't register radio device\n", + core->name); + goto fail_unreg; + } + printk(KERN_INFO "%s/0: registered device radio%d\n", + core->name,dev->radio_dev->minor & 0x1f); + } + + /* everything worked */ + list_add_tail(&dev->devlist,&cx8800_devlist); + pci_set_drvdata(pci_dev,dev); + + /* initial device configuration */ + down(&dev->lock); + init_controls(dev); + cx88_set_tvnorm(dev->core,tvnorms); + video_mux(dev,0); + up(&dev->lock); + + /* start tvaudio thread */ + if (core->tuner_type != TUNER_ABSENT) + core->kthread = kthread_run(cx88_audio_thread, core, "cx88 tvaudio"); + return 0; + +fail_unreg: + cx8800_unregister_video(dev); + free_irq(pci_dev->irq, dev); +fail_core: + cx88_core_put(core,dev->pci); +fail_free: + kfree(dev); + return err; +} + +static void __devexit cx8800_finidev(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + + /* stop thread */ + if (dev->core->kthread) { + kthread_stop(dev->core->kthread); + dev->core->kthread = NULL; + } + + cx88_shutdown(dev->core); /* FIXME */ + pci_disable_device(pci_dev); + + /* unregister stuff */ + + free_irq(pci_dev->irq, dev); + cx8800_unregister_video(dev); + pci_set_drvdata(pci_dev, NULL); + + /* free memory */ + btcx_riscmem_free(dev->pci,&dev->vidq.stopper); + list_del(&dev->devlist); + cx88_core_put(dev->core,dev->pci); + kfree(dev); +} + +static int cx8800_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + /* stop video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->vidq.active)) { + printk("%s: suspend video\n", core->name); + stop_video_dma(dev); + del_timer(&dev->vidq.timeout); + } + if (!list_empty(&dev->vbiq.active)) { + printk("%s: suspend vbi\n", core->name); + cx8800_stop_vbi_dma(dev); + del_timer(&dev->vbiq.timeout); + } + spin_unlock(&dev->slock); + +#if 1 + /* FIXME -- shutdown device */ + cx88_shutdown(dev->core); +#endif + + pci_save_state(pci_dev); + if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) { + pci_disable_device(pci_dev); + dev->state.disabled = 1; + } + return 0; +} + +static int cx8800_resume(struct pci_dev *pci_dev) +{ + struct cx8800_dev *dev = pci_get_drvdata(pci_dev); + struct cx88_core *core = dev->core; + + if (dev->state.disabled) { + pci_enable_device(pci_dev); + dev->state.disabled = 0; + } + pci_set_power_state(pci_dev, PCI_D0); + pci_restore_state(pci_dev); + +#if 1 + /* FIXME: re-initialize hardware */ + cx88_reset(dev->core); +#endif + + /* restart video+vbi capture */ + spin_lock(&dev->slock); + if (!list_empty(&dev->vidq.active)) { + printk("%s: resume video\n", core->name); + restart_video_queue(dev,&dev->vidq); + } + if (!list_empty(&dev->vbiq.active)) { + printk("%s: resume vbi\n", core->name); + cx8800_restart_vbi_queue(dev,&dev->vbiq); + } + spin_unlock(&dev->slock); + + return 0; +} + +/* ----------------------------------------------------------- */ + +struct pci_device_id cx8800_pci_tbl[] = { + { + .vendor = 0x14f1, + .device = 0x8800, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, cx8800_pci_tbl); + +static struct pci_driver cx8800_pci_driver = { + .name = "cx8800", + .id_table = cx8800_pci_tbl, + .probe = cx8800_initdev, + .remove = __devexit_p(cx8800_finidev), + + .suspend = cx8800_suspend, + .resume = cx8800_resume, +}; + +static int cx8800_init(void) +{ + printk(KERN_INFO "cx2388x v4l2 driver version %d.%d.%d loaded\n", + (CX88_VERSION_CODE >> 16) & 0xff, + (CX88_VERSION_CODE >> 8) & 0xff, + CX88_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "cx2388x: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_register_driver(&cx8800_pci_driver); +} + +static void cx8800_fini(void) +{ + pci_unregister_driver(&cx8800_pci_driver); +} + +module_init(cx8800_init); +module_exit(cx8800_fini); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/cx88/cx88.h b/drivers/media/video/cx88/cx88.h new file mode 100644 index 000000000000..b351d9eae615 --- /dev/null +++ b/drivers/media/video/cx88/cx88.h @@ -0,0 +1,551 @@ +/* + * $Id: cx88.h,v 1.56 2005/03/04 09:12:23 kraxel Exp $ + * + * v4l2 device driver for cx2388x based TV cards + * + * (c) 2003,04 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/kdev_t.h> + +#include <media/tuner.h> +#include <media/tveeprom.h> +#include <media/audiochip.h> +#include <media/video-buf.h> +#include <media/video-buf-dvb.h> + +#include "btcx-risc.h" +#include "cx88-reg.h" + +#include <linux/version.h> +#define CX88_VERSION_CODE KERNEL_VERSION(0,0,4) + +#ifndef TRUE +# define TRUE (1==1) +#endif +#ifndef FALSE +# define FALSE (1==0) +#endif +#define UNSET (-1U) + +#define CX88_MAXBOARDS 8 + +/* ----------------------------------------------------------- */ +/* defines and enums */ + +#define V4L2_I2C_CLIENTS 1 + +#define FORMAT_FLAGS_PACKED 0x01 +#define FORMAT_FLAGS_PLANAR 0x02 + +#define VBI_LINE_COUNT 17 +#define VBI_LINE_LENGTH 2048 + +/* need "shadow" registers for some write-only ones ... */ +#define SHADOW_AUD_VOL_CTL 1 +#define SHADOW_AUD_BAL_CTL 2 +#define SHADOW_MAX 2 + +/* ----------------------------------------------------------- */ +/* tv norms */ + +struct cx88_tvnorm { + char *name; + v4l2_std_id id; + u32 cxiformat; + u32 cxoformat; +}; + +static unsigned int inline norm_maxw(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 768 : 640; +// return (norm->id & V4L2_STD_625_50) ? 720 : 640; +} + +static unsigned int inline norm_maxh(struct cx88_tvnorm *norm) +{ + return (norm->id & V4L2_STD_625_50) ? 576 : 480; +} + +/* ----------------------------------------------------------- */ +/* static data */ + +struct cx8800_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; + int flags; + u32 cxformat; +}; + +struct cx88_ctrl { + struct v4l2_queryctrl v; + u32 off; + u32 reg; + u32 sreg; + u32 mask; + u32 shift; +}; + +/* ----------------------------------------------------------- */ +/* SRAM memory management data (see cx88-core.c) */ + +#define SRAM_CH21 0 /* video */ +#define SRAM_CH22 1 +#define SRAM_CH23 2 +#define SRAM_CH24 3 /* vbi */ +#define SRAM_CH25 4 /* audio */ +#define SRAM_CH26 5 +#define SRAM_CH28 6 /* mpeg */ +/* more */ + +struct sram_channel { + char *name; + u32 cmds_start; + u32 ctrl_start; + u32 cdt; + u32 fifo_start; + u32 fifo_size; + u32 ptr1_reg; + u32 ptr2_reg; + u32 cnt1_reg; + u32 cnt2_reg; +}; +extern struct sram_channel cx88_sram_channels[]; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define CX88_BOARD_NOAUTO UNSET +#define CX88_BOARD_UNKNOWN 0 +#define CX88_BOARD_HAUPPAUGE 1 +#define CX88_BOARD_GDI 2 +#define CX88_BOARD_PIXELVIEW 3 +#define CX88_BOARD_ATI_WONDER_PRO 4 +#define CX88_BOARD_WINFAST2000XP_EXPERT 5 +#define CX88_BOARD_AVERTV_303 6 +#define CX88_BOARD_MSI_TVANYWHERE_MASTER 7 +#define CX88_BOARD_WINFAST_DV2000 8 +#define CX88_BOARD_LEADTEK_PVR2000 9 +#define CX88_BOARD_IODATA_GVVCP3PCI 10 +#define CX88_BOARD_PROLINK_PLAYTVPVR 11 +#define CX88_BOARD_ASUS_PVR_416 12 +#define CX88_BOARD_MSI_TVANYWHERE 13 +#define CX88_BOARD_KWORLD_DVB_T 14 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T1 15 +#define CX88_BOARD_KWORLD_LTV883 16 +#define CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD 17 +#define CX88_BOARD_HAUPPAUGE_DVB_T1 18 +#define CX88_BOARD_CONEXANT_DVB_T1 19 +#define CX88_BOARD_PROVIDEO_PV259 20 +#define CX88_BOARD_DVICO_FUSIONHDTV_DVB_T_PLUS 21 +#define CX88_BOARD_PCHDTV_HD3000 22 +#define CX88_BOARD_DNTV_LIVE_DVB_T 23 +#define CX88_BOARD_HAUPPAUGE_ROSLYN 24 +#define CX88_BOARD_DIGITALLOGIC_MEC 25 +#define CX88_BOARD_IODATA_GVBCTV7E 26 + +enum cx88_itype { + CX88_VMUX_COMPOSITE1 = 1, + CX88_VMUX_COMPOSITE2, + CX88_VMUX_COMPOSITE3, + CX88_VMUX_COMPOSITE4, + CX88_VMUX_SVIDEO, + CX88_VMUX_TELEVISION, + CX88_VMUX_CABLE, + CX88_VMUX_DVB, + CX88_VMUX_DEBUG, + CX88_RADIO, +}; + +struct cx88_input { + enum cx88_itype type; + unsigned int vmux; + u32 gpio0, gpio1, gpio2, gpio3; +}; + +struct cx88_board { + char *name; + unsigned int tuner_type; + int tda9887_conf; + struct cx88_input input[8]; + struct cx88_input radio; + int blackbird:1; + int dvb:1; +}; + +struct cx88_subid { + u16 subvendor; + u16 subdevice; + u32 card; +}; + +#define INPUT(nr) (&cx88_boards[core->board].input[nr]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define BUFFER_TIMEOUT (HZ/2) /* 0.5 seconds */ +//#define BUFFER_TIMEOUT (HZ*2) + +/* buffer for one video frame */ +struct cx88_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* cx88 specific */ + unsigned int bpl; + struct btcx_riscmem risc; + struct cx8800_fmt *fmt; + u32 count; +}; + +struct cx88_dmaqueue { + struct list_head active; + struct list_head queued; + struct timer_list timeout; + struct btcx_riscmem stopper; + u32 count; +}; + +struct cx88_core { + struct list_head devlist; + atomic_t refcount; + + /* board name */ + int nr; + char name[32]; + + /* pci stuff */ + int pci_bus; + int pci_slot; + u32 __iomem *lmmio; + u8 __iomem *bmmio; + u32 shadow[SHADOW_MAX]; + int pci_irqmask; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + u32 i2c_state, i2c_rc; + + /* config info -- analog */ + unsigned int board; + unsigned int tuner_type; + unsigned int tda9887_conf; + unsigned int has_radio; + + /* config info -- dvb */ + struct dvb_pll_desc *pll_desc; + unsigned int pll_addr; + + /* state info */ + struct task_struct *kthread; + struct cx88_tvnorm *tvnorm; + u32 tvaudio; + u32 audiomode_manual; + u32 audiomode_current; + u32 input; + u32 astat; + + /* IR remote control state */ + struct cx88_IR *ir; +}; + +struct cx8800_dev; +struct cx8802_dev; + +/* ----------------------------------------------------------- */ +/* function 0: video stuff */ + +struct cx8800_fh { + struct cx8800_dev *dev; + enum v4l2_buf_type type; + int radio; + unsigned int resources; + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip *clips; + unsigned int nclips; + + /* video capture */ + struct cx8800_fmt *fmt; + unsigned int width,height; + struct videobuf_queue vidq; + + /* vbi capture */ + struct videobuf_queue vbiq; +}; + +struct cx8800_suspend_state { + int disabled; +}; + +struct cx8800_dev { + struct cx88_core *core; + struct list_head devlist; + struct semaphore lock; + spinlock_t slock; + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *vbi_dev; + struct video_device *radio_dev; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + +#if 0 + /* video overlay */ + struct v4l2_framebuffer fbuf; + struct cx88_buffer *screen; +#endif + + /* capture queues */ + struct cx88_dmaqueue vidq; + struct cx88_dmaqueue vbiq; + + /* various v4l controls */ + u32 freq; + + /* other global state info */ + struct cx8800_suspend_state state; +}; + +/* ----------------------------------------------------------- */ +/* function 1: audio/alsa stuff */ + +struct cx8801_dev { + struct cx88_core *core; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; +}; + +/* ----------------------------------------------------------- */ +/* function 2: mpeg stuff */ + +struct cx8802_fh { + struct cx8802_dev *dev; + struct videobuf_queue mpegq; +}; + +struct cx8802_suspend_state { + int disabled; +}; + +struct cx8802_dev { + struct cx88_core *core; + struct semaphore lock; + spinlock_t slock; + + /* pci i/o */ + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + + /* dma queues */ + struct cx88_dmaqueue mpegq; + u32 ts_packet_size; + u32 ts_packet_count; + + /* other global state info */ + struct cx8802_suspend_state state; + + /* for blackbird only */ + struct list_head devlist; + struct video_device *mpeg_dev; + u32 mailbox; + int width; + int height; + + /* for dvb only */ + struct videobuf_dvb dvb; + void* fe_handle; + int (*fe_release)(void *handle); + + /* for switching modulation types */ + unsigned char ts_gen_cntrl; +}; + +/* ----------------------------------------------------------- */ + +#define cx_read(reg) readl(core->lmmio + ((reg)>>2)) +#define cx_write(reg,value) writel((value), core->lmmio + ((reg)>>2)) +#define cx_writeb(reg,value) writeb((value), core->bmmio + (reg)) + +#define cx_andor(reg,mask,value) \ + writel((readl(core->lmmio+((reg)>>2)) & ~(mask)) |\ + ((value) & (mask)), core->lmmio+((reg)>>2)) +#define cx_set(reg,bit) cx_andor((reg),(bit),(bit)) +#define cx_clear(reg,bit) cx_andor((reg),(bit),0) + +#define cx_wait(d) { if (need_resched()) schedule(); else udelay(d); } + +/* shadow registers */ +#define cx_sread(sreg) (core->shadow[sreg]) +#define cx_swrite(sreg,reg,value) \ + (core->shadow[sreg] = value, \ + writel(core->shadow[sreg], core->lmmio + ((reg)>>2))) +#define cx_sandor(sreg,reg,mask,value) \ + (core->shadow[sreg] = (core->shadow[sreg] & ~(mask)) | ((value) & (mask)), \ + writel(core->shadow[sreg], core->lmmio + ((reg)>>2))) + +/* ----------------------------------------------------------- */ +/* cx88-core.c */ + +extern char *cx88_pci_irqs[32]; +extern char *cx88_vid_irqs[32]; +extern char *cx88_mpeg_irqs[32]; +extern void cx88_print_irqbits(char *name, char *tag, char **strings, + u32 bits, u32 mask); +extern void cx88_print_ioctl(char *name, unsigned int cmd); + +extern int cx88_core_irq(struct cx88_core *core, u32 status); +extern void cx88_wakeup(struct cx88_core *core, + struct cx88_dmaqueue *q, u32 count); +extern void cx88_shutdown(struct cx88_core *core); +extern int cx88_reset(struct cx88_core *core); + +extern int +cx88_risc_buffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, + unsigned int top_offset, unsigned int bottom_offset, + unsigned int bpl, unsigned int padding, unsigned int lines); +extern int +cx88_risc_databuffer(struct pci_dev *pci, struct btcx_riscmem *risc, + struct scatterlist *sglist, unsigned int bpl, + unsigned int lines); +extern int +cx88_risc_stopper(struct pci_dev *pci, struct btcx_riscmem *risc, + u32 reg, u32 mask, u32 value); +extern void +cx88_free_buffer(struct pci_dev *pci, struct cx88_buffer *buf); + +extern void cx88_risc_disasm(struct cx88_core *core, + struct btcx_riscmem *risc); +extern int cx88_sram_channel_setup(struct cx88_core *core, + struct sram_channel *ch, + unsigned int bpl, u32 risc); +extern void cx88_sram_channel_dump(struct cx88_core *core, + struct sram_channel *ch); + +extern int cx88_set_scale(struct cx88_core *core, unsigned int width, + unsigned int height, enum v4l2_field field); +extern int cx88_set_tvnorm(struct cx88_core *core, struct cx88_tvnorm *norm); + +extern struct video_device *cx88_vdev_init(struct cx88_core *core, + struct pci_dev *pci, + struct video_device *template, + char *type); +extern struct cx88_core* cx88_core_get(struct pci_dev *pci); +extern void cx88_core_put(struct cx88_core *core, + struct pci_dev *pci); + +/* ----------------------------------------------------------- */ +/* cx88-vbi.c */ + +void cx8800_vbi_fmt(struct cx8800_dev *dev, struct v4l2_format *f); +int cx8800_start_vbi_dma(struct cx8800_dev *dev, + struct cx88_dmaqueue *q, + struct cx88_buffer *buf); +int cx8800_stop_vbi_dma(struct cx8800_dev *dev); +int cx8800_restart_vbi_queue(struct cx8800_dev *dev, + struct cx88_dmaqueue *q); +void cx8800_vbi_timeout(unsigned long data); + +extern struct videobuf_queue_ops cx8800_vbi_qops; + +/* ----------------------------------------------------------- */ +/* cx88-i2c.c */ + +extern int cx88_i2c_init(struct cx88_core *core, struct pci_dev *pci); +extern void cx88_call_i2c_clients(struct cx88_core *core, + unsigned int cmd, void *arg); + + +/* ----------------------------------------------------------- */ +/* cx88-cards.c */ + +extern struct cx88_board cx88_boards[]; +extern const unsigned int cx88_bcount; + +extern struct cx88_subid cx88_subids[]; +extern const unsigned int cx88_idcount; + +extern void cx88_card_list(struct cx88_core *core, struct pci_dev *pci); +extern void cx88_card_setup(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-tvaudio.c */ + +#define WW_NONE 1 +#define WW_BTSC 2 +#define WW_NICAM_I 3 +#define WW_NICAM_BGDKL 4 +#define WW_A1 5 +#define WW_A2_BG 6 +#define WW_A2_DK 7 +#define WW_A2_M 8 +#define WW_EIAJ 9 +#define WW_SYSTEM_L_AM 10 +#define WW_I2SPT 11 +#define WW_FM 12 + +void cx88_set_tvaudio(struct cx88_core *core); +void cx88_newstation(struct cx88_core *core); +void cx88_get_stereo(struct cx88_core *core, struct v4l2_tuner *t); +void cx88_set_stereo(struct cx88_core *core, u32 mode, int manual); +int cx88_audio_thread(void *data); + +/* ----------------------------------------------------------- */ +/* cx88-input.c */ + +int cx88_ir_init(struct cx88_core *core, struct pci_dev *pci); +int cx88_ir_fini(struct cx88_core *core); +void cx88_ir_irq(struct cx88_core *core); + +/* ----------------------------------------------------------- */ +/* cx88-mpeg.c */ + +int cx8802_buf_prepare(struct cx8802_dev *dev, struct cx88_buffer *buf); +void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf); +void cx8802_cancel_buffers(struct cx8802_dev *dev); + +int cx8802_init_common(struct cx8802_dev *dev); +void cx8802_fini_common(struct cx8802_dev *dev); + +int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state); +int cx8802_resume_common(struct pci_dev *pci_dev); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/dpc7146.c b/drivers/media/video/dpc7146.c new file mode 100644 index 000000000000..da9481198c53 --- /dev/null +++ b/drivers/media/video/dpc7146.c @@ -0,0 +1,401 @@ +/* + dpc7146.c - v4l2 driver for the dpc7146 demonstration board + + Copyright (C) 2000-2003 Michael Hunold <michael@mihu.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> +#include <linux/video_decoder.h> /* for saa7111a */ + +#define I2C_SAA7111A 0x24 + +/* All unused bytes are reserverd. */ +#define SAA711X_CHIP_VERSION 0x00 +#define SAA711X_ANALOG_INPUT_CONTROL_1 0x02 +#define SAA711X_ANALOG_INPUT_CONTROL_2 0x03 +#define SAA711X_ANALOG_INPUT_CONTROL_3 0x04 +#define SAA711X_ANALOG_INPUT_CONTROL_4 0x05 +#define SAA711X_HORIZONTAL_SYNC_START 0x06 +#define SAA711X_HORIZONTAL_SYNC_STOP 0x07 +#define SAA711X_SYNC_CONTROL 0x08 +#define SAA711X_LUMINANCE_CONTROL 0x09 +#define SAA711X_LUMINANCE_BRIGHTNESS 0x0A +#define SAA711X_LUMINANCE_CONTRAST 0x0B +#define SAA711X_CHROMA_SATURATION 0x0C +#define SAA711X_CHROMA_HUE_CONTROL 0x0D +#define SAA711X_CHROMA_CONTROL 0x0E +#define SAA711X_FORMAT_DELAY_CONTROL 0x10 +#define SAA711X_OUTPUT_CONTROL_1 0x11 +#define SAA711X_OUTPUT_CONTROL_2 0x12 +#define SAA711X_OUTPUT_CONTROL_3 0x13 +#define SAA711X_V_GATE_1_START 0x15 +#define SAA711X_V_GATE_1_STOP 0x16 +#define SAA711X_V_GATE_1_MSB 0x17 +#define SAA711X_TEXT_SLICER_STATUS 0x1A +#define SAA711X_DECODED_BYTES_OF_TS_1 0x1B +#define SAA711X_DECODED_BYTES_OF_TS_2 0x1C +#define SAA711X_STATUS_BYTE 0x1F + +#define DPC_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +static int dpc_num = 0; + +#define DPC_INPUTS 2 +static struct v4l2_input dpc_inputs[DPC_INPUTS] = { + { 0, "Port A", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 1, "Port B", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +#define DPC_AUDIOS 0 + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_STD, SAA7146_AFTER }, + { 0, 0 } +}; + +struct dpc +{ + struct video_device *video_dev; + struct video_device *vbi_dev; + + struct i2c_adapter i2c_adapter; + struct i2c_client *saa7111a; + + int cur_input; /* current input */ +}; + +/* fixme: add vbi stuff here */ +static int dpc_probe(struct saa7146_dev* dev) +{ + struct dpc* dpc = NULL; + struct i2c_client *client; + struct list_head *item; + + dpc = (struct dpc*)kmalloc(sizeof(struct dpc), GFP_KERNEL); + if( NULL == dpc ) { + printk("dpc_v4l2.o: dpc_probe: not enough kernel memory.\n"); + return -ENOMEM; + } + memset(dpc, 0x0, sizeof(struct dpc)); + + /* FIXME: enable i2c-port pins, video-port-pins + video port pins should be enabled here ?! */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + dpc->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "dpc7146", + }; + saa7146_i2c_adapter_prepare(dev, &dpc->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if(i2c_add_adapter(&dpc->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(dpc); + return -EFAULT; + } + + /* loop through all i2c-devices on the bus and look who is there */ + list_for_each(item,&dpc->i2c_adapter.clients) { + client = list_entry(item, struct i2c_client, list); + if( I2C_SAA7111A == client->addr ) + dpc->saa7111a = client; + } + + /* check if all devices are present */ + if( 0 == dpc->saa7111a ) { + DEB_D(("dpc_v4l2.o: dpc_attach failed for this device.\n")); + i2c_del_adapter(&dpc->i2c_adapter); + kfree(dpc); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + DEB_D(("dpc_v4l2.o: dpc_probe succeeded for this device.\n")); + + /* we store the pointer in our private data field */ + dev->ext_priv = dpc; + + return 0; +} + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int dpc_init_done(struct saa7146_dev* dev) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + DEB_D(("dpc_v4l2.o: dpc_init_done called.\n")); + + /* initialize the helper ics to useful values */ + i2c_smbus_write_byte_data(dpc->saa7111a, 0x00, 0x11); + + i2c_smbus_write_byte_data(dpc->saa7111a, 0x02, 0xc0); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x03, 0x30); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x04, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x05, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x06, 0xde); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x07, 0xad); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x08, 0xa8); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x09, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0a, 0x80); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0b, 0x47); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0c, 0x40); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0d, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0e, 0x03); + + i2c_smbus_write_byte_data(dpc->saa7111a, 0x10, 0xd0); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x11, 0x1c); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x12, 0xc1); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x13, 0x30); + + i2c_smbus_write_byte_data(dpc->saa7111a, 0x1f, 0x81); + + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int dpc_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + DEB_D(("dpc_v4l2.o: dpc_attach called.\n")); + + /* checking for i2c-devices can be omitted here, because we + already did this in "dpc_vl42_probe" */ + + saa7146_vv_init(dev,&vv_data); + if( 0 != saa7146_register_device(&dpc->video_dev, dev, "dpc", VFL_TYPE_GRABBER)) { + ERR(("cannot register capture v4l2 device. skipping.\n")); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if( 0 != DPC_BOARD_CAN_DO_VBI(dev)) { + if( 0 != saa7146_register_device(&dpc->vbi_dev, dev, "dpc", VFL_TYPE_VBI)) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + } + } + + i2c_use_client(dpc->saa7111a); + + printk("dpc: found 'dpc7146 demonstration board'-%d.\n",dpc_num); + dpc_num++; + + /* the rest */ + dpc->cur_input = 0; + dpc_init_done(dev); + + return 0; +} + +static int dpc_detach(struct saa7146_dev* dev) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + DEB_EE(("dev:%p\n",dev)); + + i2c_release_client(dpc->saa7111a); + + saa7146_unregister_device(&dpc->video_dev,dev); + if( 0 != DPC_BOARD_CAN_DO_VBI(dev)) { + saa7146_unregister_device(&dpc->vbi_dev,dev); + } + saa7146_vv_release(dev); + + dpc_num--; + + i2c_del_adapter(&dpc->i2c_adapter); + kfree(dpc); + return 0; +} + +#ifdef axa +int dpc_vbi_bypass(struct saa7146_dev* dev) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + int i = 1; + + /* switch bypass in saa7111a */ + if ( 0 != dpc->saa7111a->driver->command(dpc->saa7111a,SAA711X_VBI_BYPASS, &i)) { + printk("dpc_v4l2.o: VBI_BYPASS: could not address saa7111a.\n"); + return -1; + } + + return 0; +} +#endif + +static int dpc_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct dpc* dpc = (struct dpc*)dev->ext_priv; +/* + struct saa7146_vv *vv = dev->vv_data; +*/ + switch(cmd) + { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + DEB_EE(("VIDIOC_ENUMINPUT %d.\n",i->index)); + + if( i->index < 0 || i->index >= DPC_INPUTS) { + return -EINVAL; + } + + memcpy(i, &dpc_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D(("dpc_v4l2.o: v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n",i->index)); + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *)arg; + *input = dpc->cur_input; + + DEB_D(("dpc_v4l2.o: VIDIOC_G_INPUT: %d\n",*input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *)arg; + + if (input < 0 || input >= DPC_INPUTS) { + return -EINVAL; + } + + dpc->cur_input = input; + + /* fixme: switch input here, switch audio, too! */ +// saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, input_port_selection[input].hps_sync); + printk("dpc_v4l2.o: VIDIOC_S_INPUT: fixme switch input.\n"); + + return 0; + } + default: +/* + DEB_D(("dpc_v4l2.o: v4l2_ioctl does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) +{ + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_extension extension; + +static struct saa7146_pci_extension_data dpc = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long)&dpc, + }, { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = DPC_INPUTS, + .capabilities = V4L2_CAP_VBI_CAPTURE, + .stds = &standard[0], + .num_stds = sizeof(standard)/sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = dpc_ioctl, +}; + +static struct saa7146_extension extension = { + .name = "dpc7146 demonstration board", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = dpc_probe, + .attach = dpc_attach, + .detach = dpc_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init dpc_init_module(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit dpc_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(dpc_init_module); +module_exit(dpc_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the 'dpc7146 demonstration board'"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/hexium_gemini.c b/drivers/media/video/hexium_gemini.c new file mode 100644 index 000000000000..c9b00eafefde --- /dev/null +++ b/drivers/media/video/hexium_gemini.c @@ -0,0 +1,556 @@ +/* + hexium_gemini.c - v4l2 driver for Hexium Gemini frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold <michael@mihu.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num = 0; + +#define HEXIUM_GEMINI 4 +#define HEXIUM_GEMINI_DUAL 5 + +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_QUERYCTRL, SAA7146_BEFORE }, + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_STD, SAA7146_AFTER }, + { VIDIOC_G_CTRL, SAA7146_BEFORE }, + { VIDIOC_S_CTRL, SAA7146_BEFORE }, + { 0, 0 } +}; + +#define HEXIUM_CONTROLS 1 +static struct v4l2_queryctrl hexium_controls[] = { + { V4L2_CID_PRIVATE_BASE, V4L2_CTRL_TYPE_BOOLEAN, "B/W", 0, 1, 1, 0, 0 }, +}; + +#define HEXIUM_GEMINI_V_1_0 1 +#define HEXIUM_GEMINI_DUAL_V_1_0 2 + +struct hexium +{ + int type; + + struct video_device *video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ + v4l2_std_id cur_std; /* current standard */ + int cur_bw; /* current black/white status */ +}; + +/* Samsung KS0127B decoder default registers */ +static u8 hexium_ks0127b[0x100]={ +/*00*/ 0x00,0x52,0x30,0x40,0x01,0x0C,0x2A,0x10, +/*08*/ 0x00,0x00,0x00,0x60,0x00,0x00,0x0F,0x06, +/*10*/ 0x00,0x00,0xE4,0xC0,0x00,0x00,0x00,0x00, +/*18*/ 0x14,0x9B,0xFE,0xFF,0xFC,0xFF,0x03,0x22, +/*20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*28*/ 0x00,0x00,0x00,0x00,0x00,0x2C,0x9B,0x00, +/*30*/ 0x00,0x00,0x10,0x80,0x80,0x10,0x80,0x80, +/*38*/ 0x01,0x04,0x00,0x00,0x00,0x29,0xC0,0x00, +/*40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*48*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*50*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*58*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*68*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*70*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*78*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*88*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*90*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*98*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +static struct hexium_data hexium_pal[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_pal_bw[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_ntsc[] = { + { 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_ntsc_bw[] = { + { 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_secam[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_input_select[] = { + { 0x02, 0x60 }, + { 0x02, 0x64 }, + { 0x02, 0x61 }, + { 0x02, 0x65 }, + { 0x02, 0x62 }, + { 0x02, 0x66 }, + { 0x02, 0x68 }, + { 0x02, 0x69 }, + { 0x02, 0x6A }, +}; + +/* fixme: h_offset = 0 for Hexium Gemini *Dual*, which + are currently *not* supported*/ +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 28, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D(("hexium_init_done called.\n")); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_ks0127b); i++) { + data.byte = hexium_ks0127b[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + printk("hexium_gemini: hexium_init_done() failed for address 0x%02x\n", i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + + DEB_D((".\n")); + + data.byte = hexium_input_select[input].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, hexium_input_select[input].adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + + return 0; +} + +static int hexium_set_standard(struct hexium *hexium, struct hexium_data *vdec) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D((".\n")); + + while (vdec[i].adr != -1) { + data.byte = vdec[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, vdec[i].adr, I2C_SMBUS_BYTE_DATA, &data)) { + printk("hexium_init_done: hexium_set_standard() failed for address 0x%02x\n", i); + return -1; + } + i++; + } + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE((".\n")); + + hexium = (struct hexium *) kmalloc(sizeof(struct hexium), GFP_KERNEL); + if (NULL == hexium) { + printk("hexium_gemini: not enough kernel memory in hexium_attach().\n"); + return -ENOMEM; + } + memset(hexium, 0x0, sizeof(struct hexium)); + dev->ext_priv = hexium; + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + hexium->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "hexium gemini", + }; + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(hexium); + return -EFAULT; + } + + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + saa7146_write(dev, DD1_INIT, 0x07000700); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + + hexium_set_input(hexium, 0); + hexium->cur_input = 0; + + saa7146_vv_init(dev, &vv_data); + if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium gemini", VFL_TYPE_GRABBER)) { + printk("hexium_gemini: cannot register capture v4l2 device. skipping.\n"); + return -1; + } + + printk("hexium_gemini: found 'hexium gemini' frame grabber-%d.\n", hexium_num); + hexium_num++; + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE(("dev:%p\n", dev)); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int hexium_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; +/* + struct saa7146_vv *vv = dev->vv_data; +*/ + switch (cmd) { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + DEB_EE(("VIDIOC_ENUMINPUT %d.\n", i->index)); + + if (i->index < 0 || i->index >= HEXIUM_INPUTS) { + return -EINVAL; + } + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n", i->index)); + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *) arg; + *input = hexium->cur_input; + + DEB_D(("VIDIOC_G_INPUT: %d\n", *input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *) arg; + + DEB_EE(("VIDIOC_S_INPUT %d.\n", input)); + + if (input < 0 || input >= HEXIUM_INPUTS) { + return -EINVAL; + } + + hexium->cur_input = input; + hexium_set_input(hexium, input); + + return 0; + } + /* the saa7146 provides some controls (brightness, contrast, saturation) + which gets registered *after* this function. because of this we have + to return with a value != 0 even if the function succeded.. */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *qc = arg; + int i; + + for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { + if (hexium_controls[i].id == qc->id) { + *qc = hexium_controls[i]; + DEB_D(("VIDIOC_QUERYCTRL %d.\n", qc->id)); + return 0; + } + } + return -EAGAIN; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *vc = arg; + int i; + + for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { + if (hexium_controls[i].id == vc->id) { + break; + } + } + + if (i < 0) { + return -EAGAIN; + } + + switch (vc->id) { + case V4L2_CID_PRIVATE_BASE:{ + vc->value = hexium->cur_bw; + DEB_D(("VIDIOC_G_CTRL BW:%d.\n", vc->value)); + return 0; + } + } + return -EINVAL; + } + + case VIDIOC_S_CTRL: + { + struct v4l2_control *vc = arg; + int i = 0; + + for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { + if (hexium_controls[i].id == vc->id) { + break; + } + } + + if (i < 0) { + return -EAGAIN; + } + + switch (vc->id) { + case V4L2_CID_PRIVATE_BASE:{ + hexium->cur_bw = vc->value; + break; + } + } + + DEB_D(("VIDIOC_S_CTRL BW:%d.\n", hexium->cur_bw)); + + if (0 == hexium->cur_bw && V4L2_STD_PAL == hexium->cur_std) { + hexium_set_standard(hexium, hexium_pal); + return 0; + } + if (0 == hexium->cur_bw && V4L2_STD_NTSC == hexium->cur_std) { + hexium_set_standard(hexium, hexium_ntsc); + return 0; + } + if (0 == hexium->cur_bw && V4L2_STD_SECAM == hexium->cur_std) { + hexium_set_standard(hexium, hexium_secam); + return 0; + } + if (1 == hexium->cur_bw && V4L2_STD_PAL == hexium->cur_std) { + hexium_set_standard(hexium, hexium_pal_bw); + return 0; + } + if (1 == hexium->cur_bw && V4L2_STD_NTSC == hexium->cur_std) { + hexium_set_standard(hexium, hexium_ntsc_bw); + return 0; + } + if (1 == hexium->cur_bw && V4L2_STD_SECAM == hexium->cur_std) { + /* fixme: is there no bw secam mode? */ + return -EINVAL; + } + + return -EINVAL; + } + default: +/* + DEB_D(("hexium_ioctl() does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + if (V4L2_STD_PAL == std->id) { + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + return 0; + } else if (V4L2_STD_NTSC == std->id) { + hexium_set_standard(hexium, hexium_ntsc); + hexium->cur_std = V4L2_STD_NTSC; + return 0; + } else if (V4L2_STD_SECAM == std->id) { + hexium_set_standard(hexium, hexium_secam); + hexium->cur_std = V4L2_STD_SECAM; + return 0; + } + + return -1; +} + +static struct saa7146_extension hexium_extension; + +static struct saa7146_pci_extension_data hexium_gemini_4bnc = { + .ext_priv = "Hexium Gemini (4 BNC)", + .ext = &hexium_extension, +}; + +static struct saa7146_pci_extension_data hexium_gemini_dual_4bnc = { + .ext_priv = "Hexium Gemini Dual (4 BNC)", + .ext = &hexium_extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2401, + .driver_data = (unsigned long) &hexium_gemini_4bnc, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2402, + .driver_data = (unsigned long) &hexium_gemini_dual_4bnc, + }, + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = sizeof(hexium_standards) / sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = hexium_ioctl, +}; + +static struct saa7146_extension hexium_extension = { + .name = "hexium gemini", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&hexium_extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&hexium_extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Gemini frame grabber cards"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/hexium_orion.c b/drivers/media/video/hexium_orion.c new file mode 100644 index 000000000000..42a9414155c3 --- /dev/null +++ b/drivers/media/video/hexium_orion.c @@ -0,0 +1,522 @@ +/* + hexium_orion.c - v4l2 driver for the Hexium Orion frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold <michael@mihu.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num = 0; + +#define HEXIUM_HV_PCI6_ORION 1 +#define HEXIUM_ORION_1SVHS_3BNC 2 +#define HEXIUM_ORION_4BNC 3 + +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_STD, SAA7146_AFTER }, + { 0, 0 } +}; + +struct hexium +{ + int type; + struct video_device *video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ +}; + +/* Philips SAA7110 decoder default registers */ +static u8 hexium_saa7110[53]={ +/*00*/ 0x4C,0x3C,0x0D,0xEF,0xBD,0xF0,0x00,0x00, +/*08*/ 0xF8,0xF8,0x60,0x60,0x40,0x86,0x18,0x90, +/*10*/ 0x00,0x2C,0x40,0x46,0x42,0x1A,0xFF,0xDA, +/*18*/ 0xF0,0x8B,0x00,0x00,0x00,0x00,0x00,0x00, +/*20*/ 0xD9,0x17,0x40,0x41,0x80,0x41,0x80,0x4F, +/*28*/ 0xFE,0x01,0x0F,0x0F,0x03,0x01,0x81,0x03, +/*30*/ 0x44,0x75,0x01,0x8C,0x03 +}; + +static struct { + struct hexium_data data[8]; +} hexium_input_select[] = { +{ + { /* cvbs 1 */ + { 0x06, 0x00 }, + { 0x20, 0xD9 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 2 */ + { 0x06, 0x00 }, + { 0x20, 0x78 }, + { 0x21, 0x07 }, // 0x03, + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ? + { 0x21, 0x03 }, + } +}, { + { /* cvbs 3 */ + { 0x06, 0x00 }, + { 0x20, 0xBA }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 4 */ + { 0x06, 0x00 }, + { 0x20, 0xD8 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 5 */ + { 0x06, 0x00 }, + { 0x20, 0xB8 }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 6 */ + { 0x06, 0x00 }, + { 0x20, 0x7C }, + { 0x21, 0x07 }, // 0x03 + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x03 }, + } +}, { + { /* y/c 1 */ + { 0x06, 0x80 }, + { 0x20, 0x59 }, + { 0x21, 0x17 }, + { 0x22, 0x42 }, + { 0x2C, 0xA3 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x12 }, + } +}, { + { /* y/c 2 */ + { 0x06, 0x80 }, + { 0x20, 0x9A }, + { 0x21, 0x17 }, + { 0x22, 0xB1 }, + { 0x2C, 0x13 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, + { 0x21, 0x14 }, + } +}, { + { /* y/c 3 */ + { 0x06, 0x80 }, + { 0x20, 0x3C }, + { 0x21, 0x27 }, + { 0x22, 0xC1 }, + { 0x2C, 0x23 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x21 }, + } +} +}; + +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 16, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* this is only called for old HV-PCI6/Orion cards + without eeprom */ +static int hexium_probe(struct saa7146_dev *dev) +{ + struct hexium *hexium = NULL; + union i2c_smbus_data data; + int err = 0; + + DEB_EE((".\n")); + + /* there are no hexium orion cards with revision 0 saa7146s */ + if (0 == dev->revision) { + return -EFAULT; + } + + hexium = (struct hexium *) kmalloc(sizeof(struct hexium), GFP_KERNEL); + if (NULL == hexium) { + printk("hexium_orion: hexium_probe: not enough kernel memory.\n"); + return -ENOMEM; + } + memset(hexium, 0x0, sizeof(struct hexium)); + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + saa7146_write(dev, DD1_INIT, 0x01000100); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + hexium->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "hexium orion", + }; + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(hexium); + return -EFAULT; + } + + /* set SAA7110 control GPIO 0 */ + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTHI); + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + mdelay(10); + + /* detect newer Hexium Orion cards by subsystem ids */ + if (0x17c8 == dev->pci->subsystem_vendor && 0x0101 == dev->pci->subsystem_device) { + printk("hexium_orion: device is a Hexium Orion w/ 1 SVHS + 3 BNC inputs.\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_1SVHS_3BNC; + return 0; + } + + if (0x17c8 == dev->pci->subsystem_vendor && 0x2101 == dev->pci->subsystem_device) { + printk("hexium_orion: device is a Hexium Orion w/ 4 BNC inputs.\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_4BNC; + return 0; + } + + /* check if this is an old hexium Orion card by looking at + a saa7110 at address 0x4e */ + if (0 == (err = i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_READ, 0x00, I2C_SMBUS_BYTE_DATA, &data))) { + printk("hexium_orion: device is a Hexium HV-PCI6/Orion (old).\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_HV_PCI6_ORION; + return 0; + } + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return -EFAULT; +} + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D(("hexium_init_done called.\n")); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_saa7110); i++) { + data.byte = hexium_saa7110[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + printk("hexium_orion: failed for address 0x%02x\n", i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D((".\n")); + + for (i = 0; i < 8; i++) { + int adr = hexium_input_select[input].data[i].adr; + data.byte = hexium_input_select[input].data[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + printk("%d: 0x%02x => 0x%02x\n",input, adr,data.byte); + } + + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE((".\n")); + + saa7146_vv_init(dev, &vv_data); + if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium orion", VFL_TYPE_GRABBER)) { + printk("hexium_orion: cannot register capture v4l2 device. skipping.\n"); + return -1; + } + + printk("hexium_orion: found 'hexium orion' frame grabber-%d.\n", hexium_num); + hexium_num++; + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE(("dev:%p\n", dev)); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int hexium_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; +/* + struct saa7146_vv *vv = dev->vv_data; +*/ + switch (cmd) { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + DEB_EE(("VIDIOC_ENUMINPUT %d.\n", i->index)); + + if (i->index < 0 || i->index >= HEXIUM_INPUTS) { + return -EINVAL; + } + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n", i->index)); + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *) arg; + *input = hexium->cur_input; + + DEB_D(("VIDIOC_G_INPUT: %d\n", *input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *) arg; + + if (input < 0 || input >= HEXIUM_INPUTS) { + return -EINVAL; + } + + hexium->cur_input = input; + hexium_set_input(hexium, input); + + return 0; + } + default: +/* + DEB_D(("hexium_ioctl() does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + return 0; +} + +static struct saa7146_extension extension; + +static struct saa7146_pci_extension_data hexium_hv_pci6 = { + .ext_priv = "Hexium HV-PCI6 / Orion", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_1svhs_3bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (1 SVHS/3 BNC)", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_4bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (4 BNC)", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long) &hexium_hv_pci6, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x0101, + .driver_data = (unsigned long) &hexium_orion_1svhs_3bnc, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2101, + .driver_data = (unsigned long) &hexium_orion_4bnc, + }, + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = sizeof(hexium_standards) / sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = hexium_ioctl, +}; + +static struct saa7146_extension extension = { + .name = "hexium HV-PCI6/Orion", + .flags = 0, // SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = hexium_probe, + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Orion frame grabber cards"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/ibmmpeg2.h b/drivers/media/video/ibmmpeg2.h new file mode 100644 index 000000000000..68e10387c498 --- /dev/null +++ b/drivers/media/video/ibmmpeg2.h @@ -0,0 +1,94 @@ +/* ibmmpeg2.h - IBM MPEGCD21 definitions */ + +#ifndef __IBM_MPEG2__ +#define __IBM_MPEG2__ + +/* Define all MPEG Decoder registers */ +/* Chip Control and Status */ +#define IBM_MP2_CHIP_CONTROL 0x200*2 +#define IBM_MP2_CHIP_MODE 0x201*2 +/* Timer Control and Status */ +#define IBM_MP2_SYNC_STC2 0x202*2 +#define IBM_MP2_SYNC_STC1 0x203*2 +#define IBM_MP2_SYNC_STC0 0x204*2 +#define IBM_MP2_SYNC_PTS2 0x205*2 +#define IBM_MP2_SYNC_PTS1 0x206*2 +#define IBM_MP2_SYNC_PTS0 0x207*2 +/* Video FIFO Control */ +#define IBM_MP2_FIFO 0x208*2 +#define IBM_MP2_FIFOW 0x100*2 +#define IBM_MP2_FIFO_STAT 0x209*2 +#define IBM_MP2_RB_THRESHOLD 0x22b*2 +/* Command buffer */ +#define IBM_MP2_COMMAND 0x20a*2 +#define IBM_MP2_CMD_DATA 0x20b*2 +#define IBM_MP2_CMD_STAT 0x20c*2 +#define IBM_MP2_CMD_ADDR 0x20d*2 +/* Internal Processor Control and Status */ +#define IBM_MP2_PROC_IADDR 0x20e*2 +#define IBM_MP2_PROC_IDATA 0x20f*2 +#define IBM_MP2_WR_PROT 0x235*2 +/* DRAM Access */ +#define IBM_MP2_DRAM_ADDR 0x210*2 +#define IBM_MP2_DRAM_DATA 0x212*2 +#define IBM_MP2_DRAM_CMD_STAT 0x213*2 +#define IBM_MP2_BLOCK_SIZE 0x23b*2 +#define IBM_MP2_SRC_ADDR 0x23c*2 +/* Onscreen Display */ +#define IBM_MP2_OSD_ADDR 0x214*2 +#define IBM_MP2_OSD_DATA 0x215*2 +#define IBM_MP2_OSD_MODE 0x217*2 +#define IBM_MP2_OSD_LINK_ADDR 0x229*2 +#define IBM_MP2_OSD_SIZE 0x22a*2 +/* Interrupt Control */ +#define IBM_MP2_HOST_INT 0x218*2 +#define IBM_MP2_MASK0 0x219*2 +#define IBM_MP2_HOST_INT1 0x23e*2 +#define IBM_MP2_MASK1 0x23f*2 +/* Audio Control */ +#define IBM_MP2_AUD_IADDR 0x21a*2 +#define IBM_MP2_AUD_IDATA 0x21b*2 +#define IBM_MP2_AUD_FIFO 0x21c*2 +#define IBM_MP2_AUD_FIFOW 0x101*2 +#define IBM_MP2_AUD_CTL 0x21d*2 +#define IBM_MP2_BEEP_CTL 0x21e*2 +#define IBM_MP2_FRNT_ATTEN 0x22d*2 +/* Display Control */ +#define IBM_MP2_DISP_MODE 0x220*2 +#define IBM_MP2_DISP_DLY 0x221*2 +#define IBM_MP2_VBI_CTL 0x222*2 +#define IBM_MP2_DISP_LBOR 0x223*2 +#define IBM_MP2_DISP_TBOR 0x224*2 +/* Polarity Control */ +#define IBM_MP2_INFC_CTL 0x22c*2 + +/* control commands */ +#define IBM_MP2_PLAY 0 +#define IBM_MP2_PAUSE 1 +#define IBM_MP2_SINGLE_FRAME 2 +#define IBM_MP2_FAST_FORWARD 3 +#define IBM_MP2_SLOW_MOTION 4 +#define IBM_MP2_IMED_NORM_PLAY 5 +#define IBM_MP2_RESET_WINDOW 6 +#define IBM_MP2_FREEZE_FRAME 7 +#define IBM_MP2_RESET_VID_RATE 8 +#define IBM_MP2_CONFIG_DECODER 9 +#define IBM_MP2_CHANNEL_SWITCH 10 +#define IBM_MP2_RESET_AUD_RATE 11 +#define IBM_MP2_PRE_OP_CHN_SW 12 +#define IBM_MP2_SET_STILL_MODE 14 + +/* Define Xilinx FPGA Internal Registers */ + +/* general control register 0 */ +#define XILINX_CTL0 0x600 +/* genlock delay resister 1 */ +#define XILINX_GLDELAY 0x602 +/* send 16 bits to CS3310 port */ +#define XILINX_CS3310 0x604 +/* send 16 bits to CS3310 and complete */ +#define XILINX_CS3310_CMPLT 0x60c +/* pulse width modulator control */ +#define XILINX_PWM 0x606 + +#endif diff --git a/drivers/media/video/ir-kbd-gpio.c b/drivers/media/video/ir-kbd-gpio.c new file mode 100644 index 000000000000..ab6620de4b3b --- /dev/null +++ b/drivers/media/video/ir-kbd-gpio.c @@ -0,0 +1,444 @@ +/* + * $Id: ir-kbd-gpio.c,v 1.12 2005/02/22 12:28:40 kraxel Exp $ + * + * Copyright (c) 2003 Gerd Knorr + * Copyright (c) 2003 Pavel Machek + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/pci.h> + +#include <media/ir-common.h> + +#include "bttv.h" + +/* ---------------------------------------------------------------------- */ + +static IR_KEYTAB_TYPE ir_codes_avermedia[IR_KEYTAB_SIZE] = { + [ 34 ] = KEY_KP0, + [ 40 ] = KEY_KP1, + [ 24 ] = KEY_KP2, + [ 56 ] = KEY_KP3, + [ 36 ] = KEY_KP4, + [ 20 ] = KEY_KP5, + [ 52 ] = KEY_KP6, + [ 44 ] = KEY_KP7, + [ 28 ] = KEY_KP8, + [ 60 ] = KEY_KP9, + + [ 48 ] = KEY_EJECTCD, // Unmarked on my controller + [ 0 ] = KEY_POWER, + [ 18 ] = BTN_LEFT, // DISPLAY/L + [ 50 ] = BTN_RIGHT, // LOOP/R + [ 10 ] = KEY_MUTE, + [ 38 ] = KEY_RECORD, + [ 22 ] = KEY_PAUSE, + [ 54 ] = KEY_STOP, + [ 30 ] = KEY_VOLUMEDOWN, + [ 62 ] = KEY_VOLUMEUP, + + [ 32 ] = KEY_TUNER, // TV/FM + [ 16 ] = KEY_CD, + [ 8 ] = KEY_VIDEO, + [ 4 ] = KEY_AUDIO, + [ 12 ] = KEY_ZOOM, // full screen + [ 2 ] = KEY_INFO, // preview + [ 42 ] = KEY_SEARCH, // autoscan + [ 26 ] = KEY_STOP, // freeze + [ 58 ] = KEY_RECORD, // capture + [ 6 ] = KEY_PLAY, // unmarked + [ 46 ] = KEY_RED, // unmarked + [ 14 ] = KEY_GREEN, // unmarked + + [ 33 ] = KEY_YELLOW, // unmarked + [ 17 ] = KEY_CHANNELDOWN, + [ 49 ] = KEY_CHANNELUP, + [ 1 ] = KEY_BLUE, // unmarked +}; + +/* Matt Jesson <dvb@jesson.eclipse.co.uk */ +static IR_KEYTAB_TYPE ir_codes_avermedia_dvbt[IR_KEYTAB_SIZE] = { + [ 0x28 ] = KEY_KP0, //'0' / 'enter' + [ 0x22 ] = KEY_KP1, //'1' + [ 0x12 ] = KEY_KP2, //'2' / 'up arrow' + [ 0x32 ] = KEY_KP3, //'3' + [ 0x24 ] = KEY_KP4, //'4' / 'left arrow' + [ 0x14 ] = KEY_KP5, //'5' + [ 0x34 ] = KEY_KP6, //'6' / 'right arrow' + [ 0x26 ] = KEY_KP7, //'7' + [ 0x16 ] = KEY_KP8, //'8' / 'down arrow' + [ 0x36 ] = KEY_KP9, //'9' + + [ 0x20 ] = KEY_LIST, // 'source' + [ 0x10 ] = KEY_TEXT, // 'teletext' + [ 0x00 ] = KEY_POWER, // 'power' + [ 0x04 ] = KEY_AUDIO, // 'audio' + [ 0x06 ] = KEY_ZOOM, // 'full screen' + [ 0x18 ] = KEY_VIDEO, // 'display' + [ 0x38 ] = KEY_SEARCH, // 'loop' + [ 0x08 ] = KEY_INFO, // 'preview' + [ 0x2a ] = KEY_REWIND, // 'backward <<' + [ 0x1a ] = KEY_FASTFORWARD, // 'forward >>' + [ 0x3a ] = KEY_RECORD, // 'capture' + [ 0x0a ] = KEY_MUTE, // 'mute' + [ 0x2c ] = KEY_RECORD, // 'record' + [ 0x1c ] = KEY_PAUSE, // 'pause' + [ 0x3c ] = KEY_STOP, // 'stop' + [ 0x0c ] = KEY_PLAY, // 'play' + [ 0x2e ] = KEY_RED, // 'red' + [ 0x01 ] = KEY_BLUE, // 'blue' / 'cancel' + [ 0x0e ] = KEY_YELLOW, // 'yellow' / 'ok' + [ 0x21 ] = KEY_GREEN, // 'green' + [ 0x11 ] = KEY_CHANNELDOWN, // 'channel -' + [ 0x31 ] = KEY_CHANNELUP, // 'channel +' + [ 0x1e ] = KEY_VOLUMEDOWN, // 'volume -' + [ 0x3e ] = KEY_VOLUMEUP, // 'volume +' +}; + +static IR_KEYTAB_TYPE ir_codes_pixelview[IR_KEYTAB_SIZE] = { + [ 2 ] = KEY_KP0, + [ 1 ] = KEY_KP1, + [ 11 ] = KEY_KP2, + [ 27 ] = KEY_KP3, + [ 5 ] = KEY_KP4, + [ 9 ] = KEY_KP5, + [ 21 ] = KEY_KP6, + [ 6 ] = KEY_KP7, + [ 10 ] = KEY_KP8, + [ 18 ] = KEY_KP9, + + [ 3 ] = KEY_TUNER, // TV/FM + [ 7 ] = KEY_SEARCH, // scan + [ 28 ] = KEY_ZOOM, // full screen + [ 30 ] = KEY_POWER, + [ 23 ] = KEY_VOLUMEDOWN, + [ 31 ] = KEY_VOLUMEUP, + [ 20 ] = KEY_CHANNELDOWN, + [ 22 ] = KEY_CHANNELUP, + [ 24 ] = KEY_MUTE, + + [ 0 ] = KEY_LIST, // source + [ 19 ] = KEY_INFO, // loop + [ 16 ] = KEY_LAST, // +100 + [ 13 ] = KEY_CLEAR, // reset + [ 12 ] = BTN_RIGHT, // fun++ + [ 4 ] = BTN_LEFT, // fun-- + [ 14 ] = KEY_GOTO, // function + [ 15 ] = KEY_STOP, // freeze +}; + +/* Attila Kondoros <attila.kondoros@chello.hu> */ +static IR_KEYTAB_TYPE ir_codes_apac_viewcomp[IR_KEYTAB_SIZE] = { + + [ 1 ] = KEY_KP1, + [ 2 ] = KEY_KP2, + [ 3 ] = KEY_KP3, + [ 4 ] = KEY_KP4, + [ 5 ] = KEY_KP5, + [ 6 ] = KEY_KP6, + [ 7 ] = KEY_KP7, + [ 8 ] = KEY_KP8, + [ 9 ] = KEY_KP9, + [ 0 ] = KEY_KP0, + [ 23 ] = KEY_LAST, // +100 + [ 10 ] = KEY_LIST, // recall + + + [ 28 ] = KEY_TUNER, // TV/FM + [ 21 ] = KEY_SEARCH, // scan + [ 18 ] = KEY_POWER, // power + [ 31 ] = KEY_VOLUMEDOWN, // vol up + [ 27 ] = KEY_VOLUMEUP, // vol down + [ 30 ] = KEY_CHANNELDOWN, // chn up + [ 26 ] = KEY_CHANNELUP, // chn down + + [ 17 ] = KEY_VIDEO, // video + [ 15 ] = KEY_ZOOM, // full screen + [ 19 ] = KEY_MUTE, // mute/unmute + [ 16 ] = KEY_TEXT, // min + + [ 13 ] = KEY_STOP, // freeze + [ 14 ] = KEY_RECORD, // record + [ 29 ] = KEY_PLAYPAUSE, // stop + [ 25 ] = KEY_PLAY, // play + + [ 22 ] = KEY_GOTO, // osd + [ 20 ] = KEY_REFRESH, // default + [ 12 ] = KEY_KPPLUS, // fine tune >>>> + [ 24 ] = KEY_KPMINUS // fine tune <<<< +}; + +/* ---------------------------------------------------------------------- */ + +struct IR { + struct bttv_sub_device *sub; + struct input_dev input; + struct ir_input_state ir; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; + + int polling; + u32 last_gpio; + struct work_struct work; + struct timer_list timer; +}; + +static int debug; +module_param(debug, int, 0644); /* debug level (0,1,2) */ + +#define DEVNAME "ir-kbd-gpio" +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG DEVNAME ": " fmt , ## arg) + +static void ir_irq(struct bttv_sub_device *sub); +static int ir_probe(struct device *dev); +static int ir_remove(struct device *dev); + +static struct bttv_sub_driver driver = { + .drv = { + .name = DEVNAME, + .probe = ir_probe, + .remove = ir_remove, + }, + .gpio_irq = ir_irq, +}; + +/* ---------------------------------------------------------------------- */ + +static void ir_handle_key(struct IR *ir) +{ + u32 gpio,data; + + /* read gpio value */ + gpio = bttv_gpio_read(ir->sub->core); + if (ir->polling) { + if (ir->last_gpio == gpio) + return; + ir->last_gpio = gpio; + } + + /* extract data */ + data = ir_extract_bits(gpio, ir->mask_keycode); + dprintk(DEVNAME ": irq gpio=0x%x code=%d | %s%s%s\n", + gpio, data, + ir->polling ? "poll" : "irq", + (gpio & ir->mask_keydown) ? " down" : "", + (gpio & ir->mask_keyup) ? " up" : ""); + + if (ir->mask_keydown) { + /* bit set on keydown */ + if (gpio & ir->mask_keydown) { + ir_input_keydown(&ir->input,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->input,&ir->ir); + } + + } else if (ir->mask_keyup) { + /* bit cleared on keydown */ + if (0 == (gpio & ir->mask_keyup)) { + ir_input_keydown(&ir->input,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->input,&ir->ir); + } + + } else { + /* can't disturgissh keydown/up :-/ */ + ir_input_keydown(&ir->input,&ir->ir,data,data); + ir_input_nokey(&ir->input,&ir->ir); + } +} + +static void ir_irq(struct bttv_sub_device *sub) +{ + struct IR *ir = dev_get_drvdata(&sub->dev); + + if (!ir->polling) + ir_handle_key(ir); +} + +static void ir_timer(unsigned long data) +{ + struct IR *ir = (struct IR*)data; + + schedule_work(&ir->work); +} + +static void ir_work(void *data) +{ + struct IR *ir = data; + unsigned long timeout; + + ir_handle_key(ir); + timeout = jiffies + (ir->polling * HZ / 1000); + mod_timer(&ir->timer, timeout); +} + +/* ---------------------------------------------------------------------- */ + +static int ir_probe(struct device *dev) +{ + struct bttv_sub_device *sub = to_bttv_sub_dev(dev); + struct IR *ir; + IR_KEYTAB_TYPE *ir_codes = NULL; + int ir_type = IR_TYPE_OTHER; + + ir = kmalloc(sizeof(*ir),GFP_KERNEL); + if (NULL == ir) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + + /* detect & configure */ + switch (sub->core->type) { + case BTTV_AVERMEDIA: + case BTTV_AVPHONE98: + case BTTV_AVERMEDIA98: + ir_codes = ir_codes_avermedia; + ir->mask_keycode = 0xf88000; + ir->mask_keydown = 0x010000; + ir->polling = 50; // ms + break; + + case BTTV_AVDVBT_761: + case BTTV_AVDVBT_771: + ir_codes = ir_codes_avermedia_dvbt; + ir->mask_keycode = 0x0f00c0; + ir->mask_keydown = 0x000020; + ir->polling = 50; // ms + break; + + case BTTV_PXELVWPLTVPAK: + ir_codes = ir_codes_pixelview; + ir->mask_keycode = 0x003e00; + ir->mask_keyup = 0x010000; + ir->polling = 50; // ms + break; + case BTTV_PV_BT878P_9B: + case BTTV_PV_BT878P_PLUS: + ir_codes = ir_codes_pixelview; + ir->mask_keycode = 0x001f00; + ir->mask_keyup = 0x008000; + ir->polling = 50; // ms + break; + + case BTTV_WINFAST2000: + ir_codes = ir_codes_winfast; + ir->mask_keycode = 0x1f8; + break; + case BTTV_MAGICTVIEW061: + case BTTV_MAGICTVIEW063: + ir_codes = ir_codes_winfast; + ir->mask_keycode = 0x0008e000; + ir->mask_keydown = 0x00200000; + break; + case BTTV_APAC_VIEWCOMP: + ir_codes = ir_codes_apac_viewcomp; + ir->mask_keycode = 0x001f00; + ir->mask_keyup = 0x008000; + ir->polling = 50; // ms + break; + } + if (NULL == ir_codes) { + kfree(ir); + return -ENODEV; + } + + /* init hardware-specific stuff */ + bttv_gpio_inout(sub->core, ir->mask_keycode | ir->mask_keydown, 0); + ir->sub = sub; + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "bttv IR (card=%d)", + sub->core->type); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(sub->core->pci)); + + ir_input_init(&ir->input, &ir->ir, ir_type, ir_codes); + ir->input.name = ir->name; + ir->input.phys = ir->phys; + ir->input.id.bustype = BUS_PCI; + ir->input.id.version = 1; + if (sub->core->pci->subsystem_vendor) { + ir->input.id.vendor = sub->core->pci->subsystem_vendor; + ir->input.id.product = sub->core->pci->subsystem_device; + } else { + ir->input.id.vendor = sub->core->pci->vendor; + ir->input.id.product = sub->core->pci->device; + } + + if (ir->polling) { + INIT_WORK(&ir->work, ir_work, ir); + init_timer(&ir->timer); + ir->timer.function = ir_timer; + ir->timer.data = (unsigned long)ir; + schedule_work(&ir->work); + } + + /* all done */ + dev_set_drvdata(dev,ir); + input_register_device(&ir->input); + printk(DEVNAME ": %s detected at %s\n",ir->input.name,ir->input.phys); + + return 0; +} + +static int ir_remove(struct device *dev) +{ + struct IR *ir = dev_get_drvdata(dev); + + if (ir->polling) { + del_timer(&ir->timer); + flush_scheduled_work(); + } + + input_unregister_device(&ir->input); + kfree(ir); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Pavel Machek"); +MODULE_DESCRIPTION("input driver for bt8x8 gpio IR remote controls"); +MODULE_LICENSE("GPL"); + +static int ir_init(void) +{ + return bttv_sub_register(&driver, "remote"); +} + +static void ir_fini(void) +{ + bttv_sub_unregister(&driver); +} + +module_init(ir_init); +module_exit(ir_fini); + + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/ir-kbd-i2c.c b/drivers/media/video/ir-kbd-i2c.c new file mode 100644 index 000000000000..92664f75d327 --- /dev/null +++ b/drivers/media/video/ir-kbd-i2c.c @@ -0,0 +1,492 @@ +/* + * $Id: ir-kbd-i2c.c,v 1.10 2004/12/09 12:51:35 kraxel Exp $ + * + * keyboard input driver for i2c IR remote controls + * + * Copyright (c) 2000-2003 Gerd Knorr <kraxel@bytesex.org> + * modified for PixelView (BT878P+W/FM) by + * Michal Kochanowicz <mkochano@pld.org.pl> + * Christoph Bartelmus <lirc@bartelmus.de> + * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by + * Ulrich Mueller <ulrich.mueller42@web.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> + +#include <asm/semaphore.h> + +#include <media/ir-common.h> + +/* Mark Phalan <phalanm@o2.ie> */ +static IR_KEYTAB_TYPE ir_codes_pv951[IR_KEYTAB_SIZE] = { + [ 0 ] = KEY_KP0, + [ 1 ] = KEY_KP1, + [ 2 ] = KEY_KP2, + [ 3 ] = KEY_KP3, + [ 4 ] = KEY_KP4, + [ 5 ] = KEY_KP5, + [ 6 ] = KEY_KP6, + [ 7 ] = KEY_KP7, + [ 8 ] = KEY_KP8, + [ 9 ] = KEY_KP9, + + [ 18 ] = KEY_POWER, + [ 16 ] = KEY_MUTE, + [ 31 ] = KEY_VOLUMEDOWN, + [ 27 ] = KEY_VOLUMEUP, + [ 26 ] = KEY_CHANNELUP, + [ 30 ] = KEY_CHANNELDOWN, + [ 14 ] = KEY_PAGEUP, + [ 29 ] = KEY_PAGEDOWN, + [ 19 ] = KEY_SOUND, + + [ 24 ] = KEY_KPPLUSMINUS, // CH +/- + [ 22 ] = KEY_SUBTITLE, // CC + [ 13 ] = KEY_TEXT, // TTX + [ 11 ] = KEY_TV, // AIR/CBL + [ 17 ] = KEY_PC, // PC/TV + [ 23 ] = KEY_OK, // CH RTN + [ 25 ] = KEY_MODE, // FUNC + [ 12 ] = KEY_SEARCH, // AUTOSCAN + + /* Not sure what to do with these ones! */ + [ 15 ] = KEY_SELECT, // SOURCE + [ 10 ] = KEY_KPPLUS, // +100 + [ 20 ] = KEY_KPEQUAL, // SYNC + [ 28 ] = KEY_MEDIA, // PC/TV +}; + +static IR_KEYTAB_TYPE ir_codes_purpletv[IR_KEYTAB_SIZE] = { + [ 0x3 ] = KEY_POWER, + [ 0x6f ] = KEY_MUTE, + [ 0x10 ] = KEY_BACKSPACE, // Recall + + [ 0x11 ] = KEY_KP0, + [ 0x4 ] = KEY_KP1, + [ 0x5 ] = KEY_KP2, + [ 0x6 ] = KEY_KP3, + [ 0x8 ] = KEY_KP4, + [ 0x9 ] = KEY_KP5, + [ 0xa ] = KEY_KP6, + [ 0xc ] = KEY_KP7, + [ 0xd ] = KEY_KP8, + [ 0xe ] = KEY_KP9, + [ 0x12 ] = KEY_KPDOT, // 100+ + + [ 0x7 ] = KEY_VOLUMEUP, + [ 0xb ] = KEY_VOLUMEDOWN, + [ 0x1a ] = KEY_KPPLUS, + [ 0x18 ] = KEY_KPMINUS, + [ 0x15 ] = KEY_UP, + [ 0x1d ] = KEY_DOWN, + [ 0xf ] = KEY_CHANNELUP, + [ 0x13 ] = KEY_CHANNELDOWN, + [ 0x48 ] = KEY_ZOOM, + + [ 0x1b ] = KEY_VIDEO, // Video source +#if 0 + [ 0x1f ] = KEY_S, // Snapshot +#endif + [ 0x49 ] = KEY_LANGUAGE, // MTS Select + [ 0x19 ] = KEY_SEARCH, // Auto Scan + + [ 0x4b ] = KEY_RECORD, + [ 0x46 ] = KEY_PLAY, + [ 0x45 ] = KEY_PAUSE, // Pause + [ 0x44 ] = KEY_STOP, +#if 0 + [ 0x43 ] = KEY_T, // Time Shift + [ 0x47 ] = KEY_Y, // Time Shift OFF + [ 0x4a ] = KEY_O, // TOP + [ 0x17 ] = KEY_F, // SURF CH +#endif + [ 0x40 ] = KEY_FORWARD, // Forward ? + [ 0x42 ] = KEY_REWIND, // Backward ? + +}; + +struct IR; +struct IR { + struct i2c_client c; + struct input_dev input; + struct ir_input_state ir; + + struct work_struct work; + struct timer_list timer; + char phys[32]; + int (*get_key)(struct IR*, u32*, u32*); +}; + +/* ----------------------------------------------------------------------- */ +/* insmod parameters */ + +static int debug; +module_param(debug, int, 0644); /* debug level (0,1,2) */ + +#define DEVNAME "ir-kbd-i2c" +#define dprintk(level, fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG DEVNAME ": " fmt , ## arg) + +/* ----------------------------------------------------------------------- */ + +static int get_key_haup(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char buf[3]; + int start, toggle, dev, code; + + /* poll IR chip */ + if (3 != i2c_master_recv(&ir->c,buf,3)) + return -EIO; + + /* split rc5 data block ... */ + start = (buf[0] >> 6) & 3; + toggle = (buf[0] >> 5) & 1; + dev = buf[0] & 0x1f; + code = (buf[1] >> 2) & 0x3f; + + if (3 != start) + /* no key pressed */ + return 0; + dprintk(1,"ir hauppauge (rc5): s%d t%d dev=%d code=%d\n", + start, toggle, dev, code); + + /* return key */ + *ir_key = code; + *ir_raw = (start << 12) | (toggle << 11) | (dev << 6) | code; + return 1; +} + +static int get_key_pixelview(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_pv951(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + + /* ignore 0xaa */ + if (b==0xaa) + return 0; + dprintk(2,"key %02x\n", b); + + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_knc1(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + + /* it seems that 0xFE indicates that a button is still hold + down, while 0xFF indicates that no button is hold + down. 0xFE sequences are sometimes interrupted by 0xFF */ + + dprintk(2,"key %02x\n", b); + + if (b == 0xFF) + return 0; + + if (b == 0xFE) + /* keep old data */ + return 1; + + *ir_key = b; + *ir_raw = b; + return 1; +} + +static int get_key_purpletv(struct IR *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b; + + /* poll IR chip */ + if (1 != i2c_master_recv(&ir->c,&b,1)) { + dprintk(1,"read error\n"); + return -EIO; + } + + /* no button press */ + if (b==0) + return 0; + + /* repeating */ + if (b & 0x80) + return 1; + + *ir_key = b; + *ir_raw = b; + return 1; +} +/* ----------------------------------------------------------------------- */ + +static void ir_key_poll(struct IR *ir) +{ + static u32 ir_key, ir_raw; + int rc; + + dprintk(2,"ir_poll_key\n"); + rc = ir->get_key(ir, &ir_key, &ir_raw); + if (rc < 0) { + dprintk(2,"error\n"); + return; + } + + if (0 == rc) { + ir_input_nokey(&ir->input,&ir->ir); + } else { + ir_input_keydown(&ir->input,&ir->ir, ir_key, ir_raw); + } +} + +static void ir_timer(unsigned long data) +{ + struct IR *ir = (struct IR*)data; + schedule_work(&ir->work); +} + +static void ir_work(void *data) +{ + struct IR *ir = data; + ir_key_poll(ir); + mod_timer(&ir->timer, jiffies+HZ/10); +} + +/* ----------------------------------------------------------------------- */ + +static int ir_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind); +static int ir_detach(struct i2c_client *client); +static int ir_probe(struct i2c_adapter *adap); + +static struct i2c_driver driver = { + .name = "ir remote kbd driver", + .id = I2C_DRIVERID_EXP3, /* FIXME */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = ir_probe, + .detach_client = ir_detach, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("unset"), + .driver = &driver +}; + +static int ir_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + IR_KEYTAB_TYPE *ir_codes = NULL; + char *name; + int ir_type; + struct IR *ir; + + if (NULL == (ir = kmalloc(sizeof(struct IR),GFP_KERNEL))) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + ir->c = client_template; + + i2c_set_clientdata(&ir->c, ir); + ir->c.adapter = adap; + ir->c.addr = addr; + + switch(addr) { + case 0x64: + name = "Pixelview"; + ir->get_key = get_key_pixelview; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_empty; + break; + case 0x4b: + name = "PV951"; + ir->get_key = get_key_pv951; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_pv951; + break; + case 0x18: + case 0x1a: + name = "Hauppauge"; + ir->get_key = get_key_haup; + ir_type = IR_TYPE_RC5; + ir_codes = ir_codes_rc5_tv; + break; + case 0x30: + name = "KNC One"; + ir->get_key = get_key_knc1; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_empty; + break; + case 0x7a: + name = "Purple TV"; + ir->get_key = get_key_purpletv; + ir_type = IR_TYPE_OTHER; + ir_codes = ir_codes_purpletv; + break; + default: + /* shouldn't happen */ + printk(DEVNAME ": Huh? unknown i2c address (0x%02x)?\n",addr); + kfree(ir); + return -1; + } + + /* register i2c device */ + i2c_attach_client(&ir->c); + snprintf(ir->c.name, sizeof(ir->c.name), "i2c IR (%s)", name); + snprintf(ir->phys, sizeof(ir->phys), "%s/%s/ir0", + ir->c.adapter->dev.bus_id, + ir->c.dev.bus_id); + + /* init + register input device */ + ir_input_init(&ir->input,&ir->ir,ir_type,ir_codes); + ir->input.id.bustype = BUS_I2C; + ir->input.name = ir->c.name; + ir->input.phys = ir->phys; + input_register_device(&ir->input); + printk(DEVNAME ": %s detected at %s [%s]\n", + ir->input.name,ir->input.phys,adap->name); + + /* start polling via eventd */ + INIT_WORK(&ir->work, ir_work, ir); + init_timer(&ir->timer); + ir->timer.function = ir_timer; + ir->timer.data = (unsigned long)ir; + schedule_work(&ir->work); + + return 0; +} + +static int ir_detach(struct i2c_client *client) +{ + struct IR *ir = i2c_get_clientdata(client); + + /* kill outstanding polls */ + del_timer(&ir->timer); + flush_scheduled_work(); + + /* unregister devices */ + input_unregister_device(&ir->input); + i2c_detach_client(&ir->c); + + /* free memory */ + kfree(ir); + return 0; +} + +static int ir_probe(struct i2c_adapter *adap) +{ + + /* The external IR receiver is at i2c address 0x34 (0x35 for + reads). Future Hauppauge cards will have an internal + receiver at 0x30 (0x31 for reads). In theory, both can be + fitted, and Hauppauge suggest an external overrides an + internal. + + That's why we probe 0x1a (~0x34) first. CB + */ + + static const int probe_bttv[] = { 0x1a, 0x18, 0x4b, 0x64, 0x30, -1}; + static const int probe_saa7134[] = { 0x7a, -1 }; + const int *probe = NULL; + struct i2c_client c; char buf; int i,rc; + + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_B_BT848: + probe = probe_bttv; + break; + case I2C_ALGO_SAA7134: + probe = probe_saa7134; + break; + } + if (NULL == probe) + return 0; + + memset(&c,0,sizeof(c)); + c.adapter = adap; + for (i = 0; -1 != probe[i]; i++) { + c.addr = probe[i]; + rc = i2c_master_recv(&c,&buf,1); + dprintk(1,"probe 0x%02x @ %s: %s\n", + probe[i], adap->name, + (1 == rc) ? "yes" : "no"); + if (1 == rc) { + ir_attach(adap,probe[i],0,0); + break; + } + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller"); +MODULE_DESCRIPTION("input driver for i2c IR remote controls"); +MODULE_LICENSE("GPL"); + +static int __init ir_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit ir_fini(void) +{ + i2c_del_driver(&driver); +} + +module_init(ir_init); +module_exit(ir_fini); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/meye.c b/drivers/media/video/meye.c new file mode 100644 index 000000000000..be72c2ce242e --- /dev/null +++ b/drivers/media/video/meye.c @@ -0,0 +1,2041 @@ +/* + * Motion Eye video4linux driver for Sony Vaio PictureBook + * + * Copyright (C) 2001-2004 Stelian Pop <stelian@popies.net> + * + * Copyright (C) 2001-2002 Alcôve <www.alcove.com> + * + * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com> + * + * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras. + * + * Some parts borrowed from various video4linux drivers, especially + * bttv-driver.c and zoran.c, see original files for credits. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/videodev.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> + +#include "meye.h" +#include <linux/meye.h> + +MODULE_AUTHOR("Stelian Pop <stelian@popies.net>"); +MODULE_DESCRIPTION("v4l/v4l2 driver for the MotionEye camera"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(MEYE_DRIVER_VERSION); + +/* force usage of V4L1 API */ +static int forcev4l1; /* = 0 */ +module_param(forcev4l1, int, 0644); +MODULE_PARM_DESC(forcev4l1, "force use of V4L1 instead of V4L2"); + +/* number of grab buffers */ +static unsigned int gbuffers = 2; +module_param(gbuffers, int, 0444); +MODULE_PARM_DESC(gbuffers, "number of capture buffers, default is 2 (32 max)"); + +/* size of a grab buffer */ +static unsigned int gbufsize = MEYE_MAX_BUFSIZE; +module_param(gbufsize, int, 0444); +MODULE_PARM_DESC(gbufsize, "size of the capture buffers, default is 614400" + " (will be rounded up to a page multiple)"); + +/* /dev/videoX registration number */ +static int video_nr = -1; +module_param(video_nr, int, 0444); +MODULE_PARM_DESC(video_nr, "video device to register (0=/dev/video0, etc)"); + +/* driver structure - only one possible */ +static struct meye meye; + +/****************************************************************************/ +/* Memory allocation routines (stolen from bttv-driver.c) */ +/****************************************************************************/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (mem) { + memset(mem, 0, size); + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + } + return mem; +} + +static void rvfree(void * mem, unsigned long size) +{ + unsigned long adr; + + if (mem) { + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); + } +} + +/* + * return a page table pointing to N pages of locked memory + * + * NOTE: The meye device expects DMA addresses on 32 bits, we build + * a table of 1024 entries = 4 bytes * 1024 = 4096 bytes. + */ +static int ptable_alloc(void) +{ + u32 *pt; + int i; + + memset(meye.mchip_ptable, 0, sizeof(meye.mchip_ptable)); + + /* give only 32 bit DMA addresses */ + if (dma_set_mask(&meye.mchip_dev->dev, 0xffffffff)) + return -1; + + meye.mchip_ptable_toc = dma_alloc_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + &meye.mchip_dmahandle, + GFP_KERNEL); + if (!meye.mchip_ptable_toc) { + meye.mchip_dmahandle = 0; + return -1; + } + + pt = meye.mchip_ptable_toc; + for (i = 0; i < MCHIP_NB_PAGES; i++) { + dma_addr_t dma; + meye.mchip_ptable[i] = dma_alloc_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + &dma, + GFP_KERNEL); + if (!meye.mchip_ptable[i]) { + int j; + pt = meye.mchip_ptable_toc; + for (j = 0; j < i; ++j) { + dma = (dma_addr_t) *pt; + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable[j], dma); + pt++; + } + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable_toc, + meye.mchip_dmahandle); + meye.mchip_ptable_toc = NULL; + meye.mchip_dmahandle = 0; + return -1; + } + *pt = (u32) dma; + pt++; + } + return 0; +} + +static void ptable_free(void) +{ + u32 *pt; + int i; + + pt = meye.mchip_ptable_toc; + for (i = 0; i < MCHIP_NB_PAGES; i++) { + dma_addr_t dma = (dma_addr_t) *pt; + if (meye.mchip_ptable[i]) + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable[i], dma); + pt++; + } + + if (meye.mchip_ptable_toc) + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable_toc, + meye.mchip_dmahandle); + + memset(meye.mchip_ptable, 0, sizeof(meye.mchip_ptable)); + meye.mchip_ptable_toc = NULL; + meye.mchip_dmahandle = 0; +} + +/* copy data from ptable into buf */ +static void ptable_copy(u8 *buf, int start, int size, int pt_pages) +{ + int i; + + for (i = 0; i < (size / PAGE_SIZE) * PAGE_SIZE; i += PAGE_SIZE) { + memcpy(buf + i, meye.mchip_ptable[start++], PAGE_SIZE); + if (start >= pt_pages) + start = 0; + } + memcpy(buf + i, meye.mchip_ptable[start], size % PAGE_SIZE); +} + +/****************************************************************************/ +/* JPEG tables at different qualities to load into the VRJ chip */ +/****************************************************************************/ + +/* return a set of quantisation tables based on a quality from 1 to 10 */ +static u16 *jpeg_quantisation_tables(int *length, int quality) +{ + static u16 jpeg_tables[][70] = { { + 0xdbff, 0x4300, 0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + 0xdbff, 0x4300, 0xff01, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + }, + { + 0xdbff, 0x4300, 0x5000, 0x3c37, 0x3c46, 0x5032, 0x4146, 0x5a46, + 0x5055, 0x785f, 0x82c8, 0x6e78, 0x786e, 0xaff5, 0x91b9, 0xffc8, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + 0xdbff, 0x4300, 0x5501, 0x5a5a, 0x6978, 0xeb78, 0x8282, 0xffeb, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + }, + { + 0xdbff, 0x4300, 0x2800, 0x1e1c, 0x1e23, 0x2819, 0x2123, 0x2d23, + 0x282b, 0x3c30, 0x4164, 0x373c, 0x3c37, 0x587b, 0x495d, 0x9164, + 0x9980, 0x8f96, 0x8c80, 0xa08a, 0xe6b4, 0xa0c3, 0xdaaa, 0x8aad, + 0xc88c, 0xcbff, 0xeeda, 0xfff5, 0xffff, 0xc19b, 0xffff, 0xfaff, + 0xe6ff, 0xfffd, 0xfff8, + 0xdbff, 0x4300, 0x2b01, 0x2d2d, 0x353c, 0x763c, 0x4141, 0xf876, + 0x8ca5, 0xf8a5, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, + 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, + 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, + 0xf8f8, 0xf8f8, 0xfff8, + }, + { + 0xdbff, 0x4300, 0x1b00, 0x1412, 0x1417, 0x1b11, 0x1617, 0x1e17, + 0x1b1c, 0x2820, 0x2b42, 0x2528, 0x2825, 0x3a51, 0x303d, 0x6042, + 0x6555, 0x5f64, 0x5d55, 0x6a5b, 0x9978, 0x6a81, 0x9071, 0x5b73, + 0x855d, 0x86b5, 0x9e90, 0xaba3, 0xabad, 0x8067, 0xc9bc, 0xa6ba, + 0x99c7, 0xaba8, 0xffa4, + 0xdbff, 0x4300, 0x1c01, 0x1e1e, 0x2328, 0x4e28, 0x2b2b, 0xa44e, + 0x5d6e, 0xa46e, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xffa4, + }, + { + 0xdbff, 0x4300, 0x1400, 0x0f0e, 0x0f12, 0x140d, 0x1012, 0x1712, + 0x1415, 0x1e18, 0x2132, 0x1c1e, 0x1e1c, 0x2c3d, 0x242e, 0x4932, + 0x4c40, 0x474b, 0x4640, 0x5045, 0x735a, 0x5062, 0x6d55, 0x4556, + 0x6446, 0x6588, 0x776d, 0x817b, 0x8182, 0x604e, 0x978d, 0x7d8c, + 0x7396, 0x817e, 0xff7c, + 0xdbff, 0x4300, 0x1501, 0x1717, 0x1a1e, 0x3b1e, 0x2121, 0x7c3b, + 0x4653, 0x7c53, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, + 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, + 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, + 0x7c7c, 0x7c7c, 0xff7c, + }, + { + 0xdbff, 0x4300, 0x1000, 0x0c0b, 0x0c0e, 0x100a, 0x0d0e, 0x120e, + 0x1011, 0x1813, 0x1a28, 0x1618, 0x1816, 0x2331, 0x1d25, 0x3a28, + 0x3d33, 0x393c, 0x3833, 0x4037, 0x5c48, 0x404e, 0x5744, 0x3745, + 0x5038, 0x516d, 0x5f57, 0x6762, 0x6768, 0x4d3e, 0x7971, 0x6470, + 0x5c78, 0x6765, 0xff63, + 0xdbff, 0x4300, 0x1101, 0x1212, 0x1518, 0x2f18, 0x1a1a, 0x632f, + 0x3842, 0x6342, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, + 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, + 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, + 0x6363, 0x6363, 0xff63, + }, + { + 0xdbff, 0x4300, 0x0d00, 0x0a09, 0x0a0b, 0x0d08, 0x0a0b, 0x0e0b, + 0x0d0e, 0x130f, 0x1520, 0x1213, 0x1312, 0x1c27, 0x171e, 0x2e20, + 0x3129, 0x2e30, 0x2d29, 0x332c, 0x4a3a, 0x333e, 0x4636, 0x2c37, + 0x402d, 0x4157, 0x4c46, 0x524e, 0x5253, 0x3e32, 0x615a, 0x505a, + 0x4a60, 0x5251, 0xff4f, + 0xdbff, 0x4300, 0x0e01, 0x0e0e, 0x1113, 0x2613, 0x1515, 0x4f26, + 0x2d35, 0x4f35, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, + 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, + 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, + 0x4f4f, 0x4f4f, 0xff4f, + }, + { + 0xdbff, 0x4300, 0x0a00, 0x0707, 0x0708, 0x0a06, 0x0808, 0x0b08, + 0x0a0a, 0x0e0b, 0x1018, 0x0d0e, 0x0e0d, 0x151d, 0x1116, 0x2318, + 0x251f, 0x2224, 0x221f, 0x2621, 0x372b, 0x262f, 0x3429, 0x2129, + 0x3022, 0x3141, 0x3934, 0x3e3b, 0x3e3e, 0x2e25, 0x4944, 0x3c43, + 0x3748, 0x3e3d, 0xff3b, + 0xdbff, 0x4300, 0x0a01, 0x0b0b, 0x0d0e, 0x1c0e, 0x1010, 0x3b1c, + 0x2228, 0x3b28, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, + 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, + 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, + 0x3b3b, 0x3b3b, 0xff3b, + }, + { + 0xdbff, 0x4300, 0x0600, 0x0504, 0x0506, 0x0604, 0x0506, 0x0706, + 0x0607, 0x0a08, 0x0a10, 0x090a, 0x0a09, 0x0e14, 0x0c0f, 0x1710, + 0x1814, 0x1718, 0x1614, 0x1a16, 0x251d, 0x1a1f, 0x231b, 0x161c, + 0x2016, 0x202c, 0x2623, 0x2927, 0x292a, 0x1f19, 0x302d, 0x282d, + 0x2530, 0x2928, 0xff28, + 0xdbff, 0x4300, 0x0701, 0x0707, 0x080a, 0x130a, 0x0a0a, 0x2813, + 0x161a, 0x281a, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, + 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, + 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, + 0x2828, 0x2828, 0xff28, + }, + { + 0xdbff, 0x4300, 0x0300, 0x0202, 0x0203, 0x0302, 0x0303, 0x0403, + 0x0303, 0x0504, 0x0508, 0x0405, 0x0504, 0x070a, 0x0607, 0x0c08, + 0x0c0a, 0x0b0c, 0x0b0a, 0x0d0b, 0x120e, 0x0d10, 0x110e, 0x0b0e, + 0x100b, 0x1016, 0x1311, 0x1514, 0x1515, 0x0f0c, 0x1817, 0x1416, + 0x1218, 0x1514, 0xff14, + 0xdbff, 0x4300, 0x0301, 0x0404, 0x0405, 0x0905, 0x0505, 0x1409, + 0x0b0d, 0x140d, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, + 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, + 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, + 0x1414, 0x1414, 0xff14, + }, + { + 0xdbff, 0x4300, 0x0100, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0xff01, + 0xdbff, 0x4300, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0xff01, + } }; + + if (quality < 0 || quality > 10) { + printk(KERN_WARNING + "meye: invalid quality level %d - using 8\n", quality); + quality = 8; + } + + *length = ARRAY_SIZE(jpeg_tables[quality]); + return jpeg_tables[quality]; +} + +/* return a generic set of huffman tables */ +static u16 *jpeg_huffman_tables(int *length) +{ + static u16 tables[] = { + 0xC4FF, 0xB500, 0x0010, 0x0102, 0x0303, 0x0402, 0x0503, 0x0405, + 0x0004, 0x0100, 0x017D, 0x0302, 0x0400, 0x0511, 0x2112, 0x4131, + 0x1306, 0x6151, 0x2207, 0x1471, 0x8132, 0xA191, 0x2308, 0xB142, + 0x15C1, 0xD152, 0x24F0, 0x6233, 0x8272, 0x0A09, 0x1716, 0x1918, + 0x251A, 0x2726, 0x2928, 0x342A, 0x3635, 0x3837, 0x3A39, 0x4443, + 0x4645, 0x4847, 0x4A49, 0x5453, 0x5655, 0x5857, 0x5A59, 0x6463, + 0x6665, 0x6867, 0x6A69, 0x7473, 0x7675, 0x7877, 0x7A79, 0x8483, + 0x8685, 0x8887, 0x8A89, 0x9392, 0x9594, 0x9796, 0x9998, 0xA29A, + 0xA4A3, 0xA6A5, 0xA8A7, 0xAAA9, 0xB3B2, 0xB5B4, 0xB7B6, 0xB9B8, + 0xC2BA, 0xC4C3, 0xC6C5, 0xC8C7, 0xCAC9, 0xD3D2, 0xD5D4, 0xD7D6, + 0xD9D8, 0xE1DA, 0xE3E2, 0xE5E4, 0xE7E6, 0xE9E8, 0xF1EA, 0xF3F2, + 0xF5F4, 0xF7F6, 0xF9F8, 0xFFFA, + 0xC4FF, 0xB500, 0x0011, 0x0102, 0x0402, 0x0304, 0x0704, 0x0405, + 0x0004, 0x0201, 0x0077, 0x0201, 0x1103, 0x0504, 0x3121, 0x1206, + 0x5141, 0x6107, 0x1371, 0x3222, 0x0881, 0x4214, 0xA191, 0xC1B1, + 0x2309, 0x5233, 0x15F0, 0x7262, 0x0AD1, 0x2416, 0xE134, 0xF125, + 0x1817, 0x1A19, 0x2726, 0x2928, 0x352A, 0x3736, 0x3938, 0x433A, + 0x4544, 0x4746, 0x4948, 0x534A, 0x5554, 0x5756, 0x5958, 0x635A, + 0x6564, 0x6766, 0x6968, 0x736A, 0x7574, 0x7776, 0x7978, 0x827A, + 0x8483, 0x8685, 0x8887, 0x8A89, 0x9392, 0x9594, 0x9796, 0x9998, + 0xA29A, 0xA4A3, 0xA6A5, 0xA8A7, 0xAAA9, 0xB3B2, 0xB5B4, 0xB7B6, + 0xB9B8, 0xC2BA, 0xC4C3, 0xC6C5, 0xC8C7, 0xCAC9, 0xD3D2, 0xD5D4, + 0xD7D6, 0xD9D8, 0xE2DA, 0xE4E3, 0xE6E5, 0xE8E7, 0xEAE9, 0xF3F2, + 0xF5F4, 0xF7F6, 0xF9F8, 0xFFFA, + 0xC4FF, 0x1F00, 0x0000, 0x0501, 0x0101, 0x0101, 0x0101, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0201, 0x0403, 0x0605, 0x0807, 0x0A09, + 0xFF0B, + 0xC4FF, 0x1F00, 0x0001, 0x0103, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0000, 0x0000, 0x0000, 0x0201, 0x0403, 0x0605, 0x0807, 0x0A09, + 0xFF0B + }; + + *length = ARRAY_SIZE(tables); + return tables; +} + +/****************************************************************************/ +/* MCHIP low-level functions */ +/****************************************************************************/ + +/* returns the horizontal capture size */ +static inline int mchip_hsize(void) +{ + return meye.params.subsample ? 320 : 640; +} + +/* returns the vertical capture size */ +static inline int mchip_vsize(void) +{ + return meye.params.subsample ? 240 : 480; +} + +/* waits for a register to be available */ +static void mchip_sync(int reg) +{ + u32 status; + int i; + + if (reg == MCHIP_MM_FIFO_DATA) { + for (i = 0; i < MCHIP_REG_TIMEOUT; i++) { + status = readl(meye.mchip_mmregs + + MCHIP_MM_FIFO_STATUS); + if (!(status & MCHIP_MM_FIFO_WAIT)) { + printk(KERN_WARNING "meye: fifo not ready\n"); + return; + } + if (status & MCHIP_MM_FIFO_READY) + return; + udelay(1); + } + } else if (reg > 0x80) { + u32 mask = (reg < 0x100) ? MCHIP_HIC_STATUS_MCC_RDY + : MCHIP_HIC_STATUS_VRJ_RDY; + for (i = 0; i < MCHIP_REG_TIMEOUT; i++) { + status = readl(meye.mchip_mmregs + MCHIP_HIC_STATUS); + if (status & mask) + return; + udelay(1); + } + } else + return; + printk(KERN_WARNING + "meye: mchip_sync() timeout on reg 0x%x status=0x%x\n", + reg, status); +} + +/* sets a value into the register */ +static inline void mchip_set(int reg, u32 v) +{ + mchip_sync(reg); + writel(v, meye.mchip_mmregs + reg); +} + +/* get the register value */ +static inline u32 mchip_read(int reg) +{ + mchip_sync(reg); + return readl(meye.mchip_mmregs + reg); +} + +/* wait for a register to become a particular value */ +static inline int mchip_delay(u32 reg, u32 v) +{ + int n = 10; + while (--n && mchip_read(reg) != v) + udelay(1); + return n; +} + +/* setup subsampling */ +static void mchip_subsample(void) +{ + mchip_set(MCHIP_MCC_R_SAMPLING, meye.params.subsample); + mchip_set(MCHIP_MCC_R_XRANGE, mchip_hsize()); + mchip_set(MCHIP_MCC_R_YRANGE, mchip_vsize()); + mchip_set(MCHIP_MCC_B_XRANGE, mchip_hsize()); + mchip_set(MCHIP_MCC_B_YRANGE, mchip_vsize()); + mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE); +} + +/* set the framerate into the mchip */ +static void mchip_set_framerate(void) +{ + mchip_set(MCHIP_HIC_S_RATE, meye.params.framerate); +} + +/* load some huffman and quantisation tables into the VRJ chip ready + for JPEG compression */ +static void mchip_load_tables(void) +{ + int i; + int length; + u16 *tables; + + tables = jpeg_huffman_tables(&length); + for (i = 0; i < length; i++) + writel(tables[i], meye.mchip_mmregs + MCHIP_VRJ_TABLE_DATA); + + tables = jpeg_quantisation_tables(&length, meye.params.quality); + for (i = 0; i < length; i++) + writel(tables[i], meye.mchip_mmregs + MCHIP_VRJ_TABLE_DATA); +} + +/* setup the VRJ parameters in the chip */ +static void mchip_vrj_setup(u8 mode) +{ + mchip_set(MCHIP_VRJ_BUS_MODE, 5); + mchip_set(MCHIP_VRJ_SIGNAL_ACTIVE_LEVEL, 0x1f); + mchip_set(MCHIP_VRJ_PDAT_USE, 1); + mchip_set(MCHIP_VRJ_IRQ_FLAG, 0xa0); + mchip_set(MCHIP_VRJ_MODE_SPECIFY, mode); + mchip_set(MCHIP_VRJ_NUM_LINES, mchip_vsize()); + mchip_set(MCHIP_VRJ_NUM_PIXELS, mchip_hsize()); + mchip_set(MCHIP_VRJ_NUM_COMPONENTS, 0x1b); + mchip_set(MCHIP_VRJ_LIMIT_COMPRESSED_LO, 0xFFFF); + mchip_set(MCHIP_VRJ_LIMIT_COMPRESSED_HI, 0xFFFF); + mchip_set(MCHIP_VRJ_COMP_DATA_FORMAT, 0xC); + mchip_set(MCHIP_VRJ_RESTART_INTERVAL, 0); + mchip_set(MCHIP_VRJ_SOF1, 0x601); + mchip_set(MCHIP_VRJ_SOF2, 0x1502); + mchip_set(MCHIP_VRJ_SOF3, 0x1503); + mchip_set(MCHIP_VRJ_SOF4, 0x1596); + mchip_set(MCHIP_VRJ_SOS, 0x0ed0); + + mchip_load_tables(); +} + +/* sets the DMA parameters into the chip */ +static void mchip_dma_setup(dma_addr_t dma_addr) +{ + int i; + + mchip_set(MCHIP_MM_PT_ADDR, (u32)dma_addr); + for (i = 0; i < 4; i++) + mchip_set(MCHIP_MM_FIR(i), 0); + meye.mchip_fnum = 0; +} + +/* setup for DMA transfers - also zeros the framebuffer */ +static int mchip_dma_alloc(void) +{ + if (!meye.mchip_dmahandle) + if (ptable_alloc()) + return -1; + return 0; +} + +/* frees the DMA buffer */ +static void mchip_dma_free(void) +{ + if (meye.mchip_dmahandle) { + mchip_dma_setup(0); + ptable_free(); + } +} + +/* stop any existing HIC action and wait for any dma to complete then + reset the dma engine */ +static void mchip_hic_stop(void) +{ + int i, j; + + meye.mchip_mode = MCHIP_HIC_MODE_NOOP; + if (!(mchip_read(MCHIP_HIC_STATUS) & MCHIP_HIC_STATUS_BUSY)) + return; + for (i = 0; i < 20; ++i) { + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_STOP); + mchip_delay(MCHIP_HIC_CMD, 0); + for (j = 0; j < 100; ++j) { + if (mchip_delay(MCHIP_HIC_STATUS, + MCHIP_HIC_STATUS_IDLE)) + return; + msleep(1); + } + printk(KERN_ERR "meye: need to reset HIC!\n"); + + mchip_set(MCHIP_HIC_CTL, MCHIP_HIC_CTL_SOFT_RESET); + msleep(250); + } + printk(KERN_ERR "meye: resetting HIC hanged!\n"); +} + +/****************************************************************************/ +/* MCHIP frame processing functions */ +/****************************************************************************/ + +/* get the next ready frame from the dma engine */ +static u32 mchip_get_frame(void) +{ + u32 v; + + v = mchip_read(MCHIP_MM_FIR(meye.mchip_fnum)); + return v; +} + +/* frees the current frame from the dma engine */ +static void mchip_free_frame(void) +{ + mchip_set(MCHIP_MM_FIR(meye.mchip_fnum), 0); + meye.mchip_fnum++; + meye.mchip_fnum %= 4; +} + +/* read one frame from the framebuffer assuming it was captured using + a uncompressed transfer */ +static void mchip_cont_read_frame(u32 v, u8 *buf, int size) +{ + int pt_id; + + pt_id = (v >> 17) & 0x3FF; + + ptable_copy(buf, pt_id, size, MCHIP_NB_PAGES); +} + +/* read a compressed frame from the framebuffer */ +static int mchip_comp_read_frame(u32 v, u8 *buf, int size) +{ + int pt_start, pt_end, trailer; + int fsize; + int i; + + pt_start = (v >> 19) & 0xFF; + pt_end = (v >> 11) & 0xFF; + trailer = (v >> 1) & 0x3FF; + + if (pt_end < pt_start) + fsize = (MCHIP_NB_PAGES_MJPEG - pt_start) * PAGE_SIZE + + pt_end * PAGE_SIZE + trailer * 4; + else + fsize = (pt_end - pt_start) * PAGE_SIZE + trailer * 4; + + if (fsize > size) { + printk(KERN_WARNING "meye: oversized compressed frame %d\n", + fsize); + return -1; + } + + ptable_copy(buf, pt_start, fsize, MCHIP_NB_PAGES_MJPEG); + +#ifdef MEYE_JPEG_CORRECTION + + /* Some mchip generated jpeg frames are incorrect. In most + * (all ?) of those cases, the final EOI (0xff 0xd9) marker + * is not present at the end of the frame. + * + * Since adding the final marker is not enough to restore + * the jpeg integrity, we drop the frame. + */ + + for (i = fsize - 1; i > 0 && buf[i] == 0xff; i--) ; + + if (i < 2 || buf[i - 1] != 0xff || buf[i] != 0xd9) + return -1; + +#endif + + return fsize; +} + +/* take a picture into SDRAM */ +static void mchip_take_picture(void) +{ + int i; + + mchip_hic_stop(); + mchip_subsample(); + mchip_dma_setup(meye.mchip_dmahandle); + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_CAP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + + for (i = 0; i < 100; ++i) { + if (mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) + break; + msleep(1); + } +} + +/* dma a previously taken picture into a buffer */ +static void mchip_get_picture(u8 *buf, int bufsize) +{ + u32 v; + int i; + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_OUT); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + for (i = 0; i < 100; ++i) { + if (mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) + break; + msleep(1); + } + for (i = 0; i < 4; ++i) { + v = mchip_get_frame(); + if (v & MCHIP_MM_FIR_RDY) { + mchip_cont_read_frame(v, buf, bufsize); + break; + } + mchip_free_frame(); + } +} + +/* start continuous dma capture */ +static void mchip_continuous_start(void) +{ + mchip_hic_stop(); + mchip_subsample(); + mchip_set_framerate(); + mchip_dma_setup(meye.mchip_dmahandle); + + meye.mchip_mode = MCHIP_HIC_MODE_CONT_OUT; + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_CONT_OUT); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); +} + +/* compress one frame into a buffer */ +static int mchip_compress_frame(u8 *buf, int bufsize) +{ + u32 v; + int len = -1, i; + + mchip_vrj_setup(0x3f); + udelay(50); + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_COMP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + for (i = 0; i < 100; ++i) { + if (mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) + break; + msleep(1); + } + + for (i = 0; i < 4; ++i) { + v = mchip_get_frame(); + if (v & MCHIP_MM_FIR_RDY) { + len = mchip_comp_read_frame(v, buf, bufsize); + break; + } + mchip_free_frame(); + } + return len; +} + +#if 0 +/* uncompress one image into a buffer */ +static int mchip_uncompress_frame(u8 *img, int imgsize, u8 *buf, int bufsize) +{ + mchip_vrj_setup(0x3f); + udelay(50); + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_DECOMP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + + return mchip_comp_read_frame(buf, bufsize); +} +#endif + +/* start continuous compressed capture */ +static void mchip_cont_compression_start(void) +{ + mchip_hic_stop(); + mchip_vrj_setup(0x3f); + mchip_subsample(); + mchip_set_framerate(); + mchip_dma_setup(meye.mchip_dmahandle); + + meye.mchip_mode = MCHIP_HIC_MODE_CONT_COMP; + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_CONT_COMP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); +} + +/****************************************************************************/ +/* Interrupt handling */ +/****************************************************************************/ + +static irqreturn_t meye_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 v; + int reqnr; + static int sequence = 0; + + v = mchip_read(MCHIP_MM_INTA); + + if (meye.mchip_mode != MCHIP_HIC_MODE_CONT_OUT && + meye.mchip_mode != MCHIP_HIC_MODE_CONT_COMP) + return IRQ_NONE; + +again: + v = mchip_get_frame(); + if (!(v & MCHIP_MM_FIR_RDY)) + return IRQ_HANDLED; + + if (meye.mchip_mode == MCHIP_HIC_MODE_CONT_OUT) { + if (kfifo_get(meye.grabq, (unsigned char *)&reqnr, + sizeof(int)) != sizeof(int)) { + mchip_free_frame(); + return IRQ_HANDLED; + } + mchip_cont_read_frame(v, meye.grab_fbuffer + gbufsize * reqnr, + mchip_hsize() * mchip_vsize() * 2); + meye.grab_buffer[reqnr].size = mchip_hsize() * mchip_vsize() * 2; + meye.grab_buffer[reqnr].state = MEYE_BUF_DONE; + do_gettimeofday(&meye.grab_buffer[reqnr].timestamp); + meye.grab_buffer[reqnr].sequence = sequence++; + kfifo_put(meye.doneq, (unsigned char *)&reqnr, sizeof(int)); + wake_up_interruptible(&meye.proc_list); + } else { + int size; + size = mchip_comp_read_frame(v, meye.grab_temp, gbufsize); + if (size == -1) { + mchip_free_frame(); + goto again; + } + if (kfifo_get(meye.grabq, (unsigned char *)&reqnr, + sizeof(int)) != sizeof(int)) { + mchip_free_frame(); + goto again; + } + memcpy(meye.grab_fbuffer + gbufsize * reqnr, meye.grab_temp, + size); + meye.grab_buffer[reqnr].size = size; + meye.grab_buffer[reqnr].state = MEYE_BUF_DONE; + do_gettimeofday(&meye.grab_buffer[reqnr].timestamp); + meye.grab_buffer[reqnr].sequence = sequence++; + kfifo_put(meye.doneq, (unsigned char *)&reqnr, sizeof(int)); + wake_up_interruptible(&meye.proc_list); + } + mchip_free_frame(); + goto again; +} + +/****************************************************************************/ +/* video4linux integration */ +/****************************************************************************/ + +static int meye_open(struct inode *inode, struct file *file) +{ + int i, err; + + err = video_exclusive_open(inode, file); + if (err < 0) + return err; + + mchip_hic_stop(); + + if (mchip_dma_alloc()) { + printk(KERN_ERR "meye: mchip framebuffer allocation failed\n"); + video_exclusive_release(inode, file); + return -ENOBUFS; + } + + for (i = 0; i < MEYE_MAX_BUFNBRS; i++) + meye.grab_buffer[i].state = MEYE_BUF_UNUSED; + kfifo_reset(meye.grabq); + kfifo_reset(meye.doneq); + return 0; +} + +static int meye_release(struct inode *inode, struct file *file) +{ + mchip_hic_stop(); + mchip_dma_free(); + video_exclusive_release(inode, file); + return 0; +} + +static int meye_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + switch (cmd) { + + case VIDIOCGCAP: { + struct video_capability *b = arg; + strcpy(b->name,meye.video_dev->name); + b->type = VID_TYPE_CAPTURE; + b->channels = 1; + b->audios = 0; + b->maxwidth = 640; + b->maxheight = 480; + b->minwidth = 320; + b->minheight = 240; + break; + } + + case VIDIOCGCHAN: { + struct video_channel *v = arg; + v->flags = 0; + v->tuners = 0; + v->type = VIDEO_TYPE_CAMERA; + if (v->channel != 0) + return -EINVAL; + strcpy(v->name,"Camera"); + break; + } + + case VIDIOCSCHAN: { + struct video_channel *v = arg; + if (v->channel != 0) + return -EINVAL; + break; + } + + case VIDIOCGPICT: { + struct video_picture *p = arg; + *p = meye.picture; + break; + } + + case VIDIOCSPICT: { + struct video_picture *p = arg; + if (p->depth != 16) + return -EINVAL; + if (p->palette != VIDEO_PALETTE_YUV422) + return -EINVAL; + down(&meye.lock); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, + p->brightness >> 10); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, + p->hue >> 10); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, + p->colour >> 10); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, + p->contrast >> 10); + meye.picture = *p; + up(&meye.lock); + break; + } + + case VIDIOCSYNC: { + int *i = arg; + int unused; + + if (*i < 0 || *i >= gbuffers) + return -EINVAL; + + down(&meye.lock); + + switch (meye.grab_buffer[*i].state) { + + case MEYE_BUF_UNUSED: + up(&meye.lock); + return -EINVAL; + case MEYE_BUF_USING: + if (file->f_flags & O_NONBLOCK) { + up(&meye.lock); + return -EAGAIN; + } + if (wait_event_interruptible(meye.proc_list, + (meye.grab_buffer[*i].state != MEYE_BUF_USING))) { + up(&meye.lock); + return -EINTR; + } + /* fall through */ + case MEYE_BUF_DONE: + meye.grab_buffer[*i].state = MEYE_BUF_UNUSED; + kfifo_get(meye.doneq, (unsigned char *)&unused, sizeof(int)); + } + up(&meye.lock); + break; + } + + case VIDIOCMCAPTURE: { + struct video_mmap *vm = arg; + int restart = 0; + + if (vm->frame >= gbuffers || vm->frame < 0) + return -EINVAL; + if (vm->format != VIDEO_PALETTE_YUV422) + return -EINVAL; + if (vm->height * vm->width * 2 > gbufsize) + return -EINVAL; + if (!meye.grab_fbuffer) + return -EINVAL; + if (meye.grab_buffer[vm->frame].state != MEYE_BUF_UNUSED) + return -EBUSY; + + down(&meye.lock); + if (vm->width == 640 && vm->height == 480) { + if (meye.params.subsample) { + meye.params.subsample = 0; + restart = 1; + } + } else if (vm->width == 320 && vm->height == 240) { + if (!meye.params.subsample) { + meye.params.subsample = 1; + restart = 1; + } + } else { + up(&meye.lock); + return -EINVAL; + } + + if (restart || meye.mchip_mode != MCHIP_HIC_MODE_CONT_OUT) + mchip_continuous_start(); + meye.grab_buffer[vm->frame].state = MEYE_BUF_USING; + kfifo_put(meye.grabq, (unsigned char *)&vm->frame, sizeof(int)); + up(&meye.lock); + break; + } + + case VIDIOCGMBUF: { + struct video_mbuf *vm = arg; + int i; + + memset(vm, 0 , sizeof(*vm)); + vm->size = gbufsize * gbuffers; + vm->frames = gbuffers; + for (i = 0; i < gbuffers; i++) + vm->offsets[i] = i * gbufsize; + break; + } + + case MEYEIOC_G_PARAMS: { + struct meye_params *p = arg; + *p = meye.params; + break; + } + + case MEYEIOC_S_PARAMS: { + struct meye_params *jp = arg; + if (jp->subsample > 1) + return -EINVAL; + if (jp->quality > 10) + return -EINVAL; + if (jp->sharpness > 63 || jp->agc > 63 || jp->picture > 63) + return -EINVAL; + if (jp->framerate > 31) + return -EINVAL; + down(&meye.lock); + if (meye.params.subsample != jp->subsample || + meye.params.quality != jp->quality) + mchip_hic_stop(); /* need restart */ + meye.params = *jp; + sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, + meye.params.sharpness); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, + meye.params.agc); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, + meye.params.picture); + up(&meye.lock); + break; + } + + case MEYEIOC_QBUF_CAPT: { + int *nb = arg; + + if (!meye.grab_fbuffer) + return -EINVAL; + if (*nb >= gbuffers) + return -EINVAL; + if (*nb < 0) { + /* stop capture */ + mchip_hic_stop(); + return 0; + } + if (meye.grab_buffer[*nb].state != MEYE_BUF_UNUSED) + return -EBUSY; + down(&meye.lock); + if (meye.mchip_mode != MCHIP_HIC_MODE_CONT_COMP) + mchip_cont_compression_start(); + meye.grab_buffer[*nb].state = MEYE_BUF_USING; + kfifo_put(meye.grabq, (unsigned char *)nb, sizeof(int)); + up(&meye.lock); + break; + } + + case MEYEIOC_SYNC: { + int *i = arg; + int unused; + + if (*i < 0 || *i >= gbuffers) + return -EINVAL; + + down(&meye.lock); + switch (meye.grab_buffer[*i].state) { + + case MEYE_BUF_UNUSED: + up(&meye.lock); + return -EINVAL; + case MEYE_BUF_USING: + if (file->f_flags & O_NONBLOCK) { + up(&meye.lock); + return -EAGAIN; + } + if (wait_event_interruptible(meye.proc_list, + (meye.grab_buffer[*i].state != MEYE_BUF_USING))) { + up(&meye.lock); + return -EINTR; + } + /* fall through */ + case MEYE_BUF_DONE: + meye.grab_buffer[*i].state = MEYE_BUF_UNUSED; + kfifo_get(meye.doneq, (unsigned char *)&unused, sizeof(int)); + } + *i = meye.grab_buffer[*i].size; + up(&meye.lock); + break; + } + + case MEYEIOC_STILLCAPT: { + + if (!meye.grab_fbuffer) + return -EINVAL; + if (meye.grab_buffer[0].state != MEYE_BUF_UNUSED) + return -EBUSY; + down(&meye.lock); + meye.grab_buffer[0].state = MEYE_BUF_USING; + mchip_take_picture(); + mchip_get_picture( + meye.grab_fbuffer, + mchip_hsize() * mchip_vsize() * 2); + meye.grab_buffer[0].state = MEYE_BUF_DONE; + up(&meye.lock); + break; + } + + case MEYEIOC_STILLJCAPT: { + int *len = arg; + + if (!meye.grab_fbuffer) + return -EINVAL; + if (meye.grab_buffer[0].state != MEYE_BUF_UNUSED) + return -EBUSY; + down(&meye.lock); + meye.grab_buffer[0].state = MEYE_BUF_USING; + *len = -1; + while (*len == -1) { + mchip_take_picture(); + *len = mchip_compress_frame(meye.grab_fbuffer, gbufsize); + } + meye.grab_buffer[0].state = MEYE_BUF_DONE; + up(&meye.lock); + break; + } + + case VIDIOC_QUERYCAP: { + struct v4l2_capability *cap = arg; + + if (forcev4l1) + return -EINVAL; + + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, "meye"); + strcpy(cap->card, "meye"); + sprintf(cap->bus_info, "PCI:%s", pci_name(meye.mchip_dev)); + cap->version = (MEYE_DRIVER_MAJORVERSION << 8) + + MEYE_DRIVER_MINORVERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING; + break; + } + + case VIDIOC_ENUMINPUT: { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + memset(i, 0, sizeof(*i)); + i->index = 0; + strcpy(i->name, "Camera"); + i->type = V4L2_INPUT_TYPE_CAMERA; + break; + } + + case VIDIOC_G_INPUT: { + int *i = arg; + + *i = 0; + break; + } + + case VIDIOC_S_INPUT: { + int *i = arg; + + if (*i != 0) + return -EINVAL; + break; + } + + case VIDIOC_QUERYCTRL: { + struct v4l2_queryctrl *c = arg; + + switch (c->id) { + + case V4L2_CID_BRIGHTNESS: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Brightness"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_HUE: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Hue"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_CONTRAST: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Contrast"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_SATURATION: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Saturation"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_AGC: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Agc"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 48; + c->flags = 0; + break; + case V4L2_CID_SHARPNESS: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Sharpness"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_PICTURE: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Picture"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 0; + c->flags = 0; + break; + case V4L2_CID_JPEGQUAL: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "JPEG quality"); + c->minimum = 0; + c->maximum = 10; + c->step = 1; + c->default_value = 8; + c->flags = 0; + break; + case V4L2_CID_FRAMERATE: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Framerate"); + c->minimum = 0; + c->maximum = 31; + c->step = 1; + c->default_value = 0; + c->flags = 0; + break; + default: + return -EINVAL; + } + break; + } + + case VIDIOC_S_CTRL: { + struct v4l2_control *c = arg; + + down(&meye.lock); + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERABRIGHTNESS, c->value); + meye.picture.brightness = c->value << 10; + break; + case V4L2_CID_HUE: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERAHUE, c->value); + meye.picture.hue = c->value << 10; + break; + case V4L2_CID_CONTRAST: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERACONTRAST, c->value); + meye.picture.contrast = c->value << 10; + break; + case V4L2_CID_SATURATION: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERACOLOR, c->value); + meye.picture.colour = c->value << 10; + break; + case V4L2_CID_AGC: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERAAGC, c->value); + meye.params.agc = c->value; + break; + case V4L2_CID_SHARPNESS: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERASHARPNESS, c->value); + meye.params.sharpness = c->value; + break; + case V4L2_CID_PICTURE: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERAPICTURE, c->value); + meye.params.picture = c->value; + break; + case V4L2_CID_JPEGQUAL: + meye.params.quality = c->value; + break; + case V4L2_CID_FRAMERATE: + meye.params.framerate = c->value; + break; + default: + up(&meye.lock); + return -EINVAL; + } + up(&meye.lock); + break; + } + + case VIDIOC_G_CTRL: { + struct v4l2_control *c = arg; + + down(&meye.lock); + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = meye.picture.brightness >> 10; + break; + case V4L2_CID_HUE: + c->value = meye.picture.hue >> 10; + break; + case V4L2_CID_CONTRAST: + c->value = meye.picture.contrast >> 10; + break; + case V4L2_CID_SATURATION: + c->value = meye.picture.colour >> 10; + break; + case V4L2_CID_AGC: + c->value = meye.params.agc; + break; + case V4L2_CID_SHARPNESS: + c->value = meye.params.sharpness; + break; + case V4L2_CID_PICTURE: + c->value = meye.params.picture; + break; + case V4L2_CID_JPEGQUAL: + c->value = meye.params.quality; + break; + case V4L2_CID_FRAMERATE: + c->value = meye.params.framerate; + break; + default: + up(&meye.lock); + return -EINVAL; + } + up(&meye.lock); + break; + } + + case VIDIOC_ENUM_FMT: { + struct v4l2_fmtdesc *f = arg; + + if (f->index > 1) + return -EINVAL; + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->index == 0) { + /* standard YUV 422 capture */ + memset(f, 0, sizeof(*f)); + f->index = 0; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = 0; + strcpy(f->description, "YUV422"); + f->pixelformat = V4L2_PIX_FMT_YUYV; + } else { + /* compressed MJPEG capture */ + memset(f, 0, sizeof(*f)); + f->index = 1; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + strcpy(f->description, "MJPEG"); + f->pixelformat = V4L2_PIX_FMT_MJPEG; + } + break; + } + + case VIDIOC_TRY_FMT: { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV && + f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + 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; + if (f->fmt.pix.width <= 320) { + f->fmt.pix.width = 320; + f->fmt.pix.height = 240; + } else { + f->fmt.pix.width = 640; + f->fmt.pix.height = 480; + } + 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; + break; + } + + case VIDIOC_G_FMT: { + struct v4l2_format *f = arg; + + 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; + switch (meye.mchip_mode) { + case MCHIP_HIC_MODE_CONT_OUT: + default: + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + break; + case MCHIP_HIC_MODE_CONT_COMP: + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + break; + } + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = mchip_hsize(); + f->fmt.pix.height = mchip_vsize(); + 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; + break; + } + + case VIDIOC_S_FMT: { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV && + f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + 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; + down(&meye.lock); + if (f->fmt.pix.width <= 320) { + f->fmt.pix.width = 320; + f->fmt.pix.height = 240; + meye.params.subsample = 1; + } else { + f->fmt.pix.width = 640; + f->fmt.pix.height = 480; + meye.params.subsample = 0; + } + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + meye.mchip_mode = MCHIP_HIC_MODE_CONT_OUT; + break; + case V4L2_PIX_FMT_MJPEG: + meye.mchip_mode = MCHIP_HIC_MODE_CONT_COMP; + break; + } + up(&meye.lock); + 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; + + break; + } + + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + int i; + + if (req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (req->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + if (meye.grab_fbuffer && req->count == gbuffers) { + /* already allocated, no modifications */ + break; + } + down(&meye.lock); + if (meye.grab_fbuffer) { + for (i = 0; i < gbuffers; i++) + if (meye.vma_use_count[i]) { + up(&meye.lock); + return -EINVAL; + } + rvfree(meye.grab_fbuffer, gbuffers * gbufsize); + meye.grab_fbuffer = NULL; + } + gbuffers = max(2, min((int)req->count, MEYE_MAX_BUFNBRS)); + req->count = gbuffers; + meye.grab_fbuffer = rvmalloc(gbuffers * gbufsize); + if (!meye.grab_fbuffer) { + printk(KERN_ERR "meye: v4l framebuffer allocation" + " failed\n"); + up(&meye.lock); + return -ENOMEM; + } + for (i = 0; i < gbuffers; i++) + meye.vma_use_count[i] = 0; + up(&meye.lock); + break; + } + + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + + if (index < 0 || index >= gbuffers) + return -EINVAL; + memset(buf, 0, sizeof(*buf)); + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->index = index; + buf->bytesused = meye.grab_buffer[index].size; + buf->flags = V4L2_BUF_FLAG_MAPPED; + if (meye.grab_buffer[index].state == MEYE_BUF_USING) + buf->flags |= V4L2_BUF_FLAG_QUEUED; + if (meye.grab_buffer[index].state == MEYE_BUF_DONE) + buf->flags |= V4L2_BUF_FLAG_DONE; + buf->field = V4L2_FIELD_NONE; + buf->timestamp = meye.grab_buffer[index].timestamp; + buf->sequence = meye.grab_buffer[index].sequence; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = index * gbufsize; + buf->length = gbufsize; + break; + } + + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (buf->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + if (buf->index < 0 || buf->index >= gbuffers) + return -EINVAL; + if (meye.grab_buffer[buf->index].state != MEYE_BUF_UNUSED) + return -EINVAL; + down(&meye.lock); + buf->flags |= V4L2_BUF_FLAG_QUEUED; + buf->flags &= ~V4L2_BUF_FLAG_DONE; + meye.grab_buffer[buf->index].state = MEYE_BUF_USING; + kfifo_put(meye.grabq, (unsigned char *)&buf->index, sizeof(int)); + up(&meye.lock); + break; + } + + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + int reqnr; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (buf->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + down(&meye.lock); + if (kfifo_len(meye.doneq) == 0 && file->f_flags & O_NONBLOCK) { + up(&meye.lock); + return -EAGAIN; + } + if (wait_event_interruptible(meye.proc_list, + kfifo_len(meye.doneq) != 0) < 0) { + up(&meye.lock); + return -EINTR; + } + if (!kfifo_get(meye.doneq, (unsigned char *)&reqnr, + sizeof(int))) { + up(&meye.lock); + return -EBUSY; + } + if (meye.grab_buffer[reqnr].state != MEYE_BUF_DONE) { + up(&meye.lock); + return -EINVAL; + } + buf->index = reqnr; + buf->bytesused = meye.grab_buffer[reqnr].size; + buf->flags = V4L2_BUF_FLAG_MAPPED; + buf->field = V4L2_FIELD_NONE; + buf->timestamp = meye.grab_buffer[reqnr].timestamp; + buf->sequence = meye.grab_buffer[reqnr].sequence; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = reqnr * gbufsize; + buf->length = gbufsize; + meye.grab_buffer[reqnr].state = MEYE_BUF_UNUSED; + up(&meye.lock); + break; + } + + case VIDIOC_STREAMON: { + down(&meye.lock); + switch (meye.mchip_mode) { + case MCHIP_HIC_MODE_CONT_OUT: + mchip_continuous_start(); + break; + case MCHIP_HIC_MODE_CONT_COMP: + mchip_cont_compression_start(); + break; + default: + up(&meye.lock); + return -EINVAL; + } + up(&meye.lock); + break; + } + + case VIDIOC_STREAMOFF: { + int i; + + down(&meye.lock); + mchip_hic_stop(); + kfifo_reset(meye.grabq); + kfifo_reset(meye.doneq); + for (i = 0; i < MEYE_MAX_BUFNBRS; i++) + meye.grab_buffer[i].state = MEYE_BUF_UNUSED; + up(&meye.lock); + break; + } + + /* + * XXX what about private snapshot ioctls ? + * Do they need to be converted to V4L2 ? + */ + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static int meye_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, meye_do_ioctl); +} + +static unsigned int meye_poll(struct file *file, poll_table *wait) +{ + unsigned int res = 0; + + down(&meye.lock); + poll_wait(file, &meye.proc_list, wait); + if (kfifo_len(meye.doneq)) + res = POLLIN | POLLRDNORM; + up(&meye.lock); + return res; +} + +static void meye_vm_open(struct vm_area_struct *vma) +{ + int idx = (int)vma->vm_private_data; + meye.vma_use_count[idx]++; +} + +static void meye_vm_close(struct vm_area_struct *vma) +{ + int idx = (int)vma->vm_private_data; + meye.vma_use_count[idx]--; +} + +static struct vm_operations_struct meye_vm_ops = { + .open = meye_vm_open, + .close = meye_vm_close, +}; + +static int meye_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + down(&meye.lock); + if (size > gbuffers * gbufsize) { + up(&meye.lock); + return -EINVAL; + } + if (!meye.grab_fbuffer) { + int i; + + /* lazy allocation */ + meye.grab_fbuffer = rvmalloc(gbuffers*gbufsize); + if (!meye.grab_fbuffer) { + printk(KERN_ERR "meye: v4l framebuffer allocation failed\n"); + up(&meye.lock); + return -ENOMEM; + } + for (i = 0; i < gbuffers; i++) + meye.vma_use_count[i] = 0; + } + pos = (unsigned long)meye.grab_fbuffer + offset; + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + up(&meye.lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + vma->vm_ops = &meye_vm_ops; + vma->vm_flags &= ~VM_IO; /* not I/O memory */ + vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */ + vma->vm_private_data = (void *) (offset / gbufsize); + meye_vm_open(vma); + + up(&meye.lock); + return 0; +} + +static struct file_operations meye_fops = { + .owner = THIS_MODULE, + .open = meye_open, + .release = meye_release, + .mmap = meye_mmap, + .ioctl = meye_ioctl, + .poll = meye_poll, + .llseek = no_llseek, +}; + +static struct video_device meye_template = { + .owner = THIS_MODULE, + .name = "meye", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_MEYE, + .fops = &meye_fops, + .release = video_device_release, + .minor = -1, +}; + +#ifdef CONFIG_PM +static int meye_suspend(struct pci_dev *pdev, u32 state) +{ + pci_save_state(pdev); + meye.pm_mchip_mode = meye.mchip_mode; + mchip_hic_stop(); + mchip_set(MCHIP_MM_INTA, 0x0); + return 0; +} + +static int meye_resume(struct pci_dev *pdev) +{ + pci_restore_state(pdev); + pci_write_config_word(meye.mchip_dev, MCHIP_PCI_SOFTRESET_SET, 1); + + mchip_delay(MCHIP_HIC_CMD, 0); + mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE); + msleep(1); + mchip_set(MCHIP_VRJ_SOFT_RESET, 1); + msleep(1); + mchip_set(MCHIP_MM_PCI_MODE, 5); + msleep(1); + mchip_set(MCHIP_MM_INTA, MCHIP_MM_INTA_HIC_1_MASK); + + switch (meye.pm_mchip_mode) { + case MCHIP_HIC_MODE_CONT_OUT: + mchip_continuous_start(); + break; + case MCHIP_HIC_MODE_CONT_COMP: + mchip_cont_compression_start(); + break; + } + return 0; +} +#endif + +static int __devinit meye_probe(struct pci_dev *pcidev, + const struct pci_device_id *ent) +{ + int ret = -EBUSY; + unsigned long mchip_adr; + u8 revision; + + if (meye.mchip_dev != NULL) { + printk(KERN_ERR "meye: only one device allowed!\n"); + goto outnotdev; + } + + meye.mchip_dev = pcidev; + meye.video_dev = video_device_alloc(); + if (!meye.video_dev) { + printk(KERN_ERR "meye: video_device_alloc() failed!\n"); + goto outnotdev; + } + + ret = -ENOMEM; + meye.grab_temp = vmalloc(MCHIP_NB_PAGES_MJPEG * PAGE_SIZE); + if (!meye.grab_temp) { + printk(KERN_ERR "meye: grab buffer allocation failed\n"); + goto outvmalloc; + } + + spin_lock_init(&meye.grabq_lock); + meye.grabq = kfifo_alloc(sizeof(int) * MEYE_MAX_BUFNBRS, GFP_KERNEL, + &meye.grabq_lock); + if (IS_ERR(meye.grabq)) { + printk(KERN_ERR "meye: fifo allocation failed\n"); + goto outkfifoalloc1; + } + spin_lock_init(&meye.doneq_lock); + meye.doneq = kfifo_alloc(sizeof(int) * MEYE_MAX_BUFNBRS, GFP_KERNEL, + &meye.doneq_lock); + if (IS_ERR(meye.doneq)) { + printk(KERN_ERR "meye: fifo allocation failed\n"); + goto outkfifoalloc2; + } + + 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))) { + 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"); + goto outsonypienable; + } + + ret = -EIO; + if ((ret = pci_enable_device(meye.mchip_dev))) { + printk(KERN_ERR "meye: pci_enable_device failed\n"); + goto outenabledev; + } + + mchip_adr = pci_resource_start(meye.mchip_dev,0); + if (!mchip_adr) { + printk(KERN_ERR "meye: mchip has no device base address\n"); + goto outregions; + } + if (!request_mem_region(pci_resource_start(meye.mchip_dev, 0), + pci_resource_len(meye.mchip_dev, 0), + "meye")) { + printk(KERN_ERR "meye: request_mem_region failed\n"); + goto outregions; + } + meye.mchip_mmregs = ioremap(mchip_adr, MCHIP_MM_REGS); + if (!meye.mchip_mmregs) { + printk(KERN_ERR "meye: ioremap failed\n"); + goto outremap; + } + + meye.mchip_irq = pcidev->irq; + if (request_irq(meye.mchip_irq, meye_irq, + SA_INTERRUPT | SA_SHIRQ, "meye", meye_irq)) { + printk(KERN_ERR "meye: request_irq failed\n"); + goto outreqirq; + } + + pci_read_config_byte(meye.mchip_dev, PCI_REVISION_ID, &revision); + pci_write_config_byte(meye.mchip_dev, PCI_CACHE_LINE_SIZE, 8); + pci_write_config_byte(meye.mchip_dev, PCI_LATENCY_TIMER, 64); + + pci_set_master(meye.mchip_dev); + + /* Ask the camera to perform a soft reset. */ + pci_write_config_word(meye.mchip_dev, MCHIP_PCI_SOFTRESET_SET, 1); + + mchip_delay(MCHIP_HIC_CMD, 0); + mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE); + + msleep(1); + mchip_set(MCHIP_VRJ_SOFT_RESET, 1); + + msleep(1); + mchip_set(MCHIP_MM_PCI_MODE, 5); + + msleep(1); + mchip_set(MCHIP_MM_INTA, MCHIP_MM_INTA_HIC_1_MASK); + + if (video_register_device(meye.video_dev, VFL_TYPE_GRABBER, + video_nr) < 0) { + printk(KERN_ERR "meye: video_register_device failed\n"); + goto outvideoreg; + } + + init_MUTEX(&meye.lock); + init_waitqueue_head(&meye.proc_list); + meye.picture.depth = 16; + meye.picture.palette = VIDEO_PALETTE_YUV422; + meye.picture.brightness = 32 << 10; + meye.picture.hue = 32 << 10; + meye.picture.colour = 32 << 10; + meye.picture.contrast = 32 << 10; + meye.picture.whiteness = 0; + meye.params.subsample = 0; + meye.params.quality = 8; + meye.params.sharpness = 32; + meye.params.agc = 48; + 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); + + printk(KERN_INFO "meye: Motion Eye Camera Driver v%s.\n", + MEYE_DRIVER_VERSION); + printk(KERN_INFO "meye: mchip KL5A72002 rev. %d, base %lx, irq %d\n", + revision, mchip_adr, meye.mchip_irq); + + return 0; + +outvideoreg: + free_irq(meye.mchip_irq, meye_irq); +outreqirq: + iounmap(meye.mchip_mmregs); +outremap: + release_mem_region(pci_resource_start(meye.mchip_dev, 0), + pci_resource_len(meye.mchip_dev, 0)); +outregions: + pci_disable_device(meye.mchip_dev); +outenabledev: + sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); +outsonypienable: + kfifo_free(meye.doneq); +outkfifoalloc2: + kfifo_free(meye.grabq); +outkfifoalloc1: + vfree(meye.grab_temp); +outvmalloc: + video_device_release(meye.video_dev); +outnotdev: + return ret; +} + +static void __devexit meye_remove(struct pci_dev *pcidev) +{ + video_unregister_device(meye.video_dev); + + mchip_hic_stop(); + + mchip_dma_free(); + + /* disable interrupts */ + mchip_set(MCHIP_MM_INTA, 0x0); + + free_irq(meye.mchip_irq, meye_irq); + + iounmap(meye.mchip_mmregs); + + release_mem_region(pci_resource_start(meye.mchip_dev, 0), + pci_resource_len(meye.mchip_dev, 0)); + + pci_disable_device(meye.mchip_dev); + + sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); + + kfifo_free(meye.doneq); + kfifo_free(meye.grabq); + + vfree(meye.grab_temp); + + if (meye.grab_fbuffer) { + rvfree(meye.grab_fbuffer, gbuffers*gbufsize); + meye.grab_fbuffer = NULL; + } + + printk(KERN_INFO "meye: removed\n"); +} + +static struct pci_device_id meye_pci_tbl[] = { + { PCI_VENDOR_ID_KAWASAKI, PCI_DEVICE_ID_MCHIP_KL5A72002, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(pci, meye_pci_tbl); + +static struct pci_driver meye_driver = { + .name = "meye", + .id_table = meye_pci_tbl, + .probe = meye_probe, + .remove = __devexit_p(meye_remove), +#ifdef CONFIG_PM + .suspend = meye_suspend, + .resume = meye_resume, +#endif +}; + +static int __init meye_init(void) +{ + gbuffers = max(2, min((int)gbuffers, MEYE_MAX_BUFNBRS)); + if (gbufsize < 0 || gbufsize > MEYE_MAX_BUFSIZE) + gbufsize = MEYE_MAX_BUFSIZE; + gbufsize = PAGE_ALIGN(gbufsize); + printk(KERN_INFO "meye: using %d buffers with %dk (%dk total)" + "for capture\n", + gbuffers, + gbufsize / 1024, gbuffers * gbufsize / 1024); + return pci_register_driver(&meye_driver); +} + +static void __exit meye_exit(void) +{ + pci_unregister_driver(&meye_driver); +} + +module_init(meye_init); +module_exit(meye_exit); diff --git a/drivers/media/video/meye.h b/drivers/media/video/meye.h new file mode 100644 index 000000000000..e8cd897b0d20 --- /dev/null +++ b/drivers/media/video/meye.h @@ -0,0 +1,318 @@ +/* + * Motion Eye video4linux driver for Sony Vaio PictureBook + * + * Copyright (C) 2001-2004 Stelian Pop <stelian@popies.net> + * + * Copyright (C) 2001-2002 Alcôve <www.alcove.com> + * + * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com> + * + * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras. + * + * Some parts borrowed from various video4linux drivers, especially + * bttv-driver.c and zoran.c, see original files for credits. + * + * 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. + */ + +#ifndef _MEYE_PRIV_H_ +#define _MEYE_PRIV_H_ + +#define MEYE_DRIVER_MAJORVERSION 1 +#define MEYE_DRIVER_MINORVERSION 13 + +#define MEYE_DRIVER_VERSION __stringify(MEYE_DRIVER_MAJORVERSION) "." \ + __stringify(MEYE_DRIVER_MINORVERSION) + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/kfifo.h> + +/****************************************************************************/ +/* Motion JPEG chip registers */ +/****************************************************************************/ + +/* Motion JPEG chip PCI configuration registers */ +#define MCHIP_PCI_POWER_CSR 0x54 +#define MCHIP_PCI_MCORE_STATUS 0x60 /* see HIC_STATUS */ +#define MCHIP_PCI_HOSTUSEREQ_SET 0x64 +#define MCHIP_PCI_HOSTUSEREQ_CLR 0x68 +#define MCHIP_PCI_LOWPOWER_SET 0x6c +#define MCHIP_PCI_LOWPOWER_CLR 0x70 +#define MCHIP_PCI_SOFTRESET_SET 0x74 + +/* Motion JPEG chip memory mapped registers */ +#define MCHIP_MM_REGS 0x200 /* 512 bytes */ +#define MCHIP_REG_TIMEOUT 1000 /* reg access, ~us */ +#define MCHIP_MCC_VRJ_TIMEOUT 1000 /* MCC & VRJ access */ + +#define MCHIP_MM_PCI_MODE 0x00 /* PCI access mode */ +#define MCHIP_MM_PCI_MODE_RETRY 0x00000001 /* retry mode */ +#define MCHIP_MM_PCI_MODE_MASTER 0x00000002 /* master access */ +#define MCHIP_MM_PCI_MODE_READ_LINE 0x00000004 /* read line */ + +#define MCHIP_MM_INTA 0x04 /* Int status/mask */ +#define MCHIP_MM_INTA_MCC 0x00000001 /* MCC interrupt */ +#define MCHIP_MM_INTA_VRJ 0x00000002 /* VRJ interrupt */ +#define MCHIP_MM_INTA_HIC_1 0x00000004 /* one frame done */ +#define MCHIP_MM_INTA_HIC_1_MASK 0x00000400 /* 1: enable */ +#define MCHIP_MM_INTA_HIC_END 0x00000008 /* all frames done */ +#define MCHIP_MM_INTA_HIC_END_MASK 0x00000800 +#define MCHIP_MM_INTA_JPEG 0x00000010 /* decompress. error */ +#define MCHIP_MM_INTA_JPEG_MASK 0x00001000 +#define MCHIP_MM_INTA_CAPTURE 0x00000020 /* capture end */ +#define MCHIP_MM_INTA_PCI_ERR 0x00000040 /* PCI error */ +#define MCHIP_MM_INTA_PCI_ERR_MASK 0x00004000 + +#define MCHIP_MM_PT_ADDR 0x08 /* page table address*/ + /* n*4kB */ +#define MCHIP_NB_PAGES 1024 /* pages for display */ +#define MCHIP_NB_PAGES_MJPEG 256 /* pages for mjpeg */ + +#define MCHIP_MM_FIR(n) (0x0c+(n)*4) /* Frame info 0-3 */ +#define MCHIP_MM_FIR_RDY 0x00000001 /* frame ready */ +#define MCHIP_MM_FIR_FAILFR_MASK 0xf8000000 /* # of failed frames */ +#define MCHIP_MM_FIR_FAILFR_SHIFT 27 + + /* continuous comp/decomp mode */ +#define MCHIP_MM_FIR_C_ENDL_MASK 0x000007fe /* end DW [10] */ +#define MCHIP_MM_FIR_C_ENDL_SHIFT 1 +#define MCHIP_MM_FIR_C_ENDP_MASK 0x0007f800 /* end page [8] */ +#define MCHIP_MM_FIR_C_ENDP_SHIFT 11 +#define MCHIP_MM_FIR_C_STARTP_MASK 0x07f80000 /* start page [8] */ +#define MCHIP_MM_FIR_C_STARTP_SHIFT 19 + + /* continuous picture output mode */ +#define MCHIP_MM_FIR_O_STARTP_MASK 0x7ffe0000 /* start page [10] */ +#define MCHIP_MM_FIR_O_STARTP_SHIFT 17 + +#define MCHIP_MM_FIFO_DATA 0x1c /* PCI TGT FIFO data */ +#define MCHIP_MM_FIFO_STATUS 0x20 /* PCI TGT FIFO stat */ +#define MCHIP_MM_FIFO_MASK 0x00000003 +#define MCHIP_MM_FIFO_WAIT_OR_READY 0x00000002 /* Bits common to WAIT & READY*/ +#define MCHIP_MM_FIFO_IDLE 0x0 /* HIC idle */ +#define MCHIP_MM_FIFO_IDLE1 0x1 /* idem ??? */ +#define MCHIP_MM_FIFO_WAIT 0x2 /* wait request */ +#define MCHIP_MM_FIFO_READY 0x3 /* data ready */ + +#define MCHIP_HIC_HOST_USEREQ 0x40 /* host uses MCORE */ + +#define MCHIP_HIC_TP_BUSY 0x44 /* taking picture */ + +#define MCHIP_HIC_PIC_SAVED 0x48 /* pic in SDRAM */ + +#define MCHIP_HIC_LOWPOWER 0x4c /* clock stopped */ + +#define MCHIP_HIC_CTL 0x50 /* HIC control */ +#define MCHIP_HIC_CTL_SOFT_RESET 0x00000001 /* MCORE reset */ +#define MCHIP_HIC_CTL_MCORE_RDY 0x00000002 /* MCORE ready */ + +#define MCHIP_HIC_CMD 0x54 /* HIC command */ +#define MCHIP_HIC_CMD_BITS 0x00000003 /* cmd width=[1:0]*/ +#define MCHIP_HIC_CMD_NOOP 0x0 +#define MCHIP_HIC_CMD_START 0x1 +#define MCHIP_HIC_CMD_STOP 0x2 + +#define MCHIP_HIC_MODE 0x58 +#define MCHIP_HIC_MODE_NOOP 0x0 +#define MCHIP_HIC_MODE_STILL_CAP 0x1 /* still pic capt */ +#define MCHIP_HIC_MODE_DISPLAY 0x2 /* display */ +#define MCHIP_HIC_MODE_STILL_COMP 0x3 /* still pic comp. */ +#define MCHIP_HIC_MODE_STILL_DECOMP 0x4 /* still pic decomp. */ +#define MCHIP_HIC_MODE_CONT_COMP 0x5 /* cont capt+comp */ +#define MCHIP_HIC_MODE_CONT_DECOMP 0x6 /* cont decomp+disp */ +#define MCHIP_HIC_MODE_STILL_OUT 0x7 /* still pic output */ +#define MCHIP_HIC_MODE_CONT_OUT 0x8 /* cont output */ + +#define MCHIP_HIC_STATUS 0x5c +#define MCHIP_HIC_STATUS_MCC_RDY 0x00000001 /* MCC reg acc ok */ +#define MCHIP_HIC_STATUS_VRJ_RDY 0x00000002 /* VRJ reg acc ok */ +#define MCHIP_HIC_STATUS_IDLE 0x00000003 +#define MCHIP_HIC_STATUS_CAPDIS 0x00000004 /* cap/disp in prog */ +#define MCHIP_HIC_STATUS_COMPDEC 0x00000008 /* (de)comp in prog */ +#define MCHIP_HIC_STATUS_BUSY 0x00000010 /* HIC busy */ + +#define MCHIP_HIC_S_RATE 0x60 /* MJPEG # frames */ + +#define MCHIP_HIC_PCI_VFMT 0x64 /* video format */ +#define MCHIP_HIC_PCI_VFMT_YVYU 0x00000001 /* 0: V Y' U Y */ + /* 1: Y' V Y U */ + +#define MCHIP_MCC_CMD 0x80 /* MCC commands */ +#define MCHIP_MCC_CMD_INITIAL 0x0 /* idle ? */ +#define MCHIP_MCC_CMD_IIC_START_SET 0x1 +#define MCHIP_MCC_CMD_IIC_END_SET 0x2 +#define MCHIP_MCC_CMD_FM_WRITE 0x3 /* frame memory */ +#define MCHIP_MCC_CMD_FM_READ 0x4 +#define MCHIP_MCC_CMD_FM_STOP 0x5 +#define MCHIP_MCC_CMD_CAPTURE 0x6 +#define MCHIP_MCC_CMD_DISPLAY 0x7 +#define MCHIP_MCC_CMD_END_DISP 0x8 +#define MCHIP_MCC_CMD_STILL_COMP 0x9 +#define MCHIP_MCC_CMD_STILL_DECOMP 0xa +#define MCHIP_MCC_CMD_STILL_OUTPUT 0xb +#define MCHIP_MCC_CMD_CONT_OUTPUT 0xc +#define MCHIP_MCC_CMD_CONT_COMP 0xd +#define MCHIP_MCC_CMD_CONT_DECOMP 0xe +#define MCHIP_MCC_CMD_RESET 0xf /* MCC reset */ + +#define MCHIP_MCC_IIC_WR 0x84 + +#define MCHIP_MCC_MCC_WR 0x88 + +#define MCHIP_MCC_MCC_RD 0x8c + +#define MCHIP_MCC_STATUS 0x90 +#define MCHIP_MCC_STATUS_CAPT 0x00000001 /* capturing */ +#define MCHIP_MCC_STATUS_DISP 0x00000002 /* displaying */ +#define MCHIP_MCC_STATUS_COMP 0x00000004 /* compressing */ +#define MCHIP_MCC_STATUS_DECOMP 0x00000008 /* decompressing */ +#define MCHIP_MCC_STATUS_MCC_WR 0x00000010 /* register ready */ +#define MCHIP_MCC_STATUS_MCC_RD 0x00000020 /* register ready */ +#define MCHIP_MCC_STATUS_IIC_WR 0x00000040 /* register ready */ +#define MCHIP_MCC_STATUS_OUTPUT 0x00000080 /* output in prog */ + +#define MCHIP_MCC_SIG_POLARITY 0x94 +#define MCHIP_MCC_SIG_POL_VS_H 0x00000001 /* VS active-high */ +#define MCHIP_MCC_SIG_POL_HS_H 0x00000002 /* HS active-high */ +#define MCHIP_MCC_SIG_POL_DOE_H 0x00000004 /* DOE active-high */ + +#define MCHIP_MCC_IRQ 0x98 +#define MCHIP_MCC_IRQ_CAPDIS_STRT 0x00000001 /* cap/disp started */ +#define MCHIP_MCC_IRQ_CAPDIS_STRT_MASK 0x00000010 +#define MCHIP_MCC_IRQ_CAPDIS_END 0x00000002 /* cap/disp ended */ +#define MCHIP_MCC_IRQ_CAPDIS_END_MASK 0x00000020 +#define MCHIP_MCC_IRQ_COMPDEC_STRT 0x00000004 /* (de)comp started */ +#define MCHIP_MCC_IRQ_COMPDEC_STRT_MASK 0x00000040 +#define MCHIP_MCC_IRQ_COMPDEC_END 0x00000008 /* (de)comp ended */ +#define MCHIP_MCC_IRQ_COMPDEC_END_MASK 0x00000080 + +#define MCHIP_MCC_HSTART 0x9c /* video in */ +#define MCHIP_MCC_VSTART 0xa0 +#define MCHIP_MCC_HCOUNT 0xa4 +#define MCHIP_MCC_VCOUNT 0xa8 +#define MCHIP_MCC_R_XBASE 0xac /* capt/disp */ +#define MCHIP_MCC_R_YBASE 0xb0 +#define MCHIP_MCC_R_XRANGE 0xb4 +#define MCHIP_MCC_R_YRANGE 0xb8 +#define MCHIP_MCC_B_XBASE 0xbc /* comp/decomp */ +#define MCHIP_MCC_B_YBASE 0xc0 +#define MCHIP_MCC_B_XRANGE 0xc4 +#define MCHIP_MCC_B_YRANGE 0xc8 + +#define MCHIP_MCC_R_SAMPLING 0xcc /* 1: 1:4 */ + +#define MCHIP_VRJ_CMD 0x100 /* VRJ commands */ + +/* VRJ registers (see table 12.2.4) */ +#define MCHIP_VRJ_COMPRESSED_DATA 0x1b0 +#define MCHIP_VRJ_PIXEL_DATA 0x1b8 + +#define MCHIP_VRJ_BUS_MODE 0x100 +#define MCHIP_VRJ_SIGNAL_ACTIVE_LEVEL 0x108 +#define MCHIP_VRJ_PDAT_USE 0x110 +#define MCHIP_VRJ_MODE_SPECIFY 0x118 +#define MCHIP_VRJ_LIMIT_COMPRESSED_LO 0x120 +#define MCHIP_VRJ_LIMIT_COMPRESSED_HI 0x124 +#define MCHIP_VRJ_COMP_DATA_FORMAT 0x128 +#define MCHIP_VRJ_TABLE_DATA 0x140 +#define MCHIP_VRJ_RESTART_INTERVAL 0x148 +#define MCHIP_VRJ_NUM_LINES 0x150 +#define MCHIP_VRJ_NUM_PIXELS 0x158 +#define MCHIP_VRJ_NUM_COMPONENTS 0x160 +#define MCHIP_VRJ_SOF1 0x168 +#define MCHIP_VRJ_SOF2 0x170 +#define MCHIP_VRJ_SOF3 0x178 +#define MCHIP_VRJ_SOF4 0x180 +#define MCHIP_VRJ_SOS 0x188 +#define MCHIP_VRJ_SOFT_RESET 0x190 + +#define MCHIP_VRJ_STATUS 0x1c0 +#define MCHIP_VRJ_STATUS_BUSY 0x00001 +#define MCHIP_VRJ_STATUS_COMP_ACCESS 0x00002 +#define MCHIP_VRJ_STATUS_PIXEL_ACCESS 0x00004 +#define MCHIP_VRJ_STATUS_ERROR 0x00008 + +#define MCHIP_VRJ_IRQ_FLAG 0x1c8 +#define MCHIP_VRJ_ERROR_REPORT 0x1d8 + +#define MCHIP_VRJ_START_COMMAND 0x1a0 + +/****************************************************************************/ +/* Driver definitions. */ +/****************************************************************************/ + +/* Sony Programmable I/O Controller for accessing the camera commands */ +#include <linux/sonypi.h> + +/* private API definitions */ +#include <linux/meye.h> + +/* Enable jpg software correction */ +#define MEYE_JPEG_CORRECTION 1 + +/* Maximum size of a buffer */ +#define MEYE_MAX_BUFSIZE 614400 /* 640 * 480 * 2 */ + +/* Maximum number of buffers */ +#define MEYE_MAX_BUFNBRS 32 + +/* State of a buffer */ +#define MEYE_BUF_UNUSED 0 /* not used */ +#define MEYE_BUF_USING 1 /* currently grabbing / playing */ +#define MEYE_BUF_DONE 2 /* done */ + +/* grab buffer */ +struct meye_grab_buffer { + int state; /* state of buffer */ + unsigned long size; /* size of jpg frame */ + struct timeval timestamp; /* timestamp */ + unsigned long sequence; /* sequence number */ +}; + +/* size of kfifos containings buffer indices */ +#define MEYE_QUEUE_SIZE MEYE_MAX_BUFNBRS + +/* Motion Eye device structure */ +struct meye { + struct pci_dev *mchip_dev; /* pci device */ + u8 mchip_irq; /* irq */ + u8 mchip_mode; /* actual mchip mode: HIC_MODE... */ + u8 mchip_fnum; /* current mchip frame number */ + unsigned char __iomem *mchip_mmregs;/* mchip: memory mapped registers */ + u8 *mchip_ptable[MCHIP_NB_PAGES];/* mchip: ptable */ + void *mchip_ptable_toc; /* mchip: ptable toc */ + dma_addr_t mchip_dmahandle; /* mchip: dma handle to ptable toc */ + unsigned char *grab_fbuffer; /* capture framebuffer */ + unsigned char *grab_temp; /* temporary buffer */ + /* list of buffers */ + struct meye_grab_buffer grab_buffer[MEYE_MAX_BUFNBRS]; + int vma_use_count[MEYE_MAX_BUFNBRS]; /* mmap count */ + struct semaphore lock; /* semaphore for open/mmap... */ + struct kfifo *grabq; /* queue for buffers to be grabbed */ + spinlock_t grabq_lock; /* lock protecting the queue */ + struct kfifo *doneq; /* queue for grabbed buffers */ + spinlock_t doneq_lock; /* lock protecting the queue */ + wait_queue_head_t proc_list; /* wait queue */ + struct video_device *video_dev; /* video device parameters */ + struct video_picture picture; /* video picture parameters */ + struct meye_params params; /* additional parameters */ +#ifdef CONFIG_PM + u8 pm_mchip_mode; /* old mchip mode */ +#endif +}; + +#endif diff --git a/drivers/media/video/msp3400.c b/drivers/media/video/msp3400.c new file mode 100644 index 000000000000..d996ec99caff --- /dev/null +++ b/drivers/media/video/msp3400.c @@ -0,0 +1,1876 @@ +/* + * programming the msp34* sound processor family + * + * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org> + * + * what works and what doesn't: + * + * AM-Mono + * Support for Hauppauge cards added (decoding handled by tuner) added by + * Frederic Crozat <fcrozat@mail.dotcom.fr> + * + * FM-Mono + * should work. The stereo modes are backward compatible to FM-mono, + * therefore FM-Mono should be allways available. + * + * FM-Stereo (B/G, used in germany) + * should work, with autodetect + * + * FM-Stereo (satellite) + * should work, no autodetect (i.e. default is mono, but you can + * switch to stereo -- untested) + * + * NICAM (B/G, L , used in UK, Scandinavia, Spain and France) + * should work, with autodetect. Support for NICAM was added by + * Pekka Pietikainen <pp@netppl.fi> + * + * + * TODO: + * - better SAT support + * + * + * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch) + * using soundcore instead of OSS + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/kthread.h> +#include <linux/suspend.h> +#include <asm/semaphore.h> +#include <asm/pgtable.h> + +#include <media/audiochip.h> +#include <media/id.h> +#include "msp3400.h" + +#define OPMODE_AUTO -1 +#define OPMODE_MANUAL 0 +#define OPMODE_SIMPLE 1 /* use short programming (>= msp3410 only) */ +#define OPMODE_SIMPLER 2 /* use shorter programming (>= msp34xxG) */ + +/* insmod parameters */ +static int opmode = OPMODE_AUTO; +static int debug = 0; /* debug output */ +static int once = 0; /* no continous stereo monitoring */ +static int amsound = 0; /* hard-wire AM sound at 6.5 Hz (france), + the autoscan seems work well only with FM... */ +static int standard = 1; /* Override auto detect of audio standard, if needed. */ +static int dolby = 0; + +static int stereo_threshold = 0x190; /* a2 threshold for stereo/bilingual + (msp34xxg only) 0x00a0-0x03c0 */ + +struct msp3400c { + int rev1,rev2; + + int opmode; + int mode; + int norm; + int nicam_on; + int acb; + int main, second; /* sound carrier */ + int input; + int source; /* see msp34xxg_set_source */ + + /* v4l2 */ + int audmode; + int rxsubchans; + + int muted; + int volume, balance; + int bass, treble; + + /* thread */ + struct task_struct *kthread; + wait_queue_head_t wq; + int restart:1; + int watch_stereo:1; +}; + +#define HAVE_NICAM(msp) (((msp->rev2>>8) & 0xff) != 00) +#define HAVE_SIMPLE(msp) ((msp->rev1 & 0xff) >= 'D'-'@') +#define HAVE_SIMPLER(msp) ((msp->rev1 & 0xff) >= 'G'-'@') +#define HAVE_RADIO(msp) ((msp->rev1 & 0xff) >= 'G'-'@') + +#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */ + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug >= 1) printk +#define d2printk if (debug >= 2) printk + +/* read-only */ +module_param(opmode, int, 0444); + +/* read-write */ +module_param(once, int, 0644); +module_param(debug, int, 0644); +module_param(stereo_threshold, int, 0644); +module_param(standard, int, 0644); +module_param(amsound, int, 0644); +module_param(dolby, int, 0644); + +MODULE_PARM_DESC(once, "No continuous stereo monitoring"); +MODULE_PARM_DESC(debug, "Enable debug messages"); +MODULE_PARM_DESC(standard, "Specify audio standard: 32 = NTSC, 64 = radio, Default: Autodetect"); +MODULE_PARM_DESC(amsound, "Hardwire AM sound at 6.5Hz (France), FM can autoscan"); + +MODULE_DESCRIPTION("device driver for msp34xx TV sound processor"); +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("Dual BSD/GPL"); /* FreeBSD uses this too */ + +/* ---------------------------------------------------------------------- */ + +#define I2C_MSP3400C 0x80 +#define I2C_MSP3400C_ALT 0x88 + +#define I2C_MSP3400C_DEM 0x10 +#define I2C_MSP3400C_DFP 0x12 + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_MSP3400C >> 1, + I2C_MSP3400C_ALT >> 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the MSP3400C Sound processor */ + +static int msp3400c_reset(struct i2c_client *client) +{ + /* reset and read revision code */ + static char reset_off[3] = { 0x00, 0x80, 0x00 }; + static char reset_on[3] = { 0x00, 0x00, 0x00 }; + static char write[3] = { I2C_MSP3400C_DFP + 1, 0x00, 0x1e }; + char read[2]; + struct i2c_msg reset[2] = { + { client->addr, I2C_M_IGNORE_NAK, 3, reset_off }, + { client->addr, I2C_M_IGNORE_NAK, 3, reset_on }, + }; + struct i2c_msg test[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read }, + }; + + if ( (1 != i2c_transfer(client->adapter,&reset[0],1)) || + (1 != i2c_transfer(client->adapter,&reset[1],1)) || + (2 != i2c_transfer(client->adapter,test,2)) ) { + printk(KERN_ERR "msp3400: chip reset failed\n"); + return -1; + } + return 0; +} + +static int +msp3400c_read(struct i2c_client *client, int dev, int addr) +{ + int err; + + unsigned char write[3]; + unsigned char read[2]; + struct i2c_msg msgs[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read } + }; + write[0] = dev+1; + write[1] = addr >> 8; + write[2] = addr & 0xff; + + for (err = 0; err < 3;) { + if (2 == i2c_transfer(client->adapter,msgs,2)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (read 0x%02x/0x%02x)\n", + err, dev, addr); + msleep(10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return read[0] << 8 | read[1]; +} + +static int +msp3400c_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + unsigned char buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + for (err = 0; err < 3;) { + if (5 == i2c_master_send(client, buffer, 5)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (write 0x%02x/0x%02x)\n", + err, dev, addr); + msleep(10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ + +/* This macro is allowed for *constants* only, gcc must calculate it + at compile time. Remember -- no floats in kernel mode */ +#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24))) + +#define MSP_MODE_AM_DETECT 0 +#define MSP_MODE_FM_RADIO 2 +#define MSP_MODE_FM_TERRA 3 +#define MSP_MODE_FM_SAT 4 +#define MSP_MODE_FM_NICAM1 5 +#define MSP_MODE_FM_NICAM2 6 +#define MSP_MODE_AM_NICAM 7 +#define MSP_MODE_BTSC 8 +#define MSP_MODE_EXTERN 9 + +static struct MSP_INIT_DATA_DEM { + int fir1[6]; + int fir2[6]; + int cdo1; + int cdo2; + int ad_cv; + int mode_reg; + int dfp_src; + int dfp_matrix; +} msp_init_data[] = { + /* AM (for carrier detect / msp3400) */ + { { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0500, 0x0020, 0x3000}, + + /* AM (for carrier detect / msp3410) */ + { { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0100, 0x0020, 0x3000}, + + /* FM Radio */ + { { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 }, + MSP_CARRIER(10.7), MSP_CARRIER(10.7), + 0x00d0, 0x0480, 0x0020, 0x3000 }, + + /* Terrestial FM-mono + FM-stereo */ + { { 3, 18, 27, 48, 66, 72 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0480, 0x0030, 0x3000}, + + /* Sat FM-mono */ + { { 1, 9, 14, 24, 33, 37 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0480, 0x0000, 0x3000}, + + /* NICAM/FM -- B/G (5.5/5.85), D/K (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/FM -- I (6.0/6.552) */ + { { 2, 4, -6, -4, 40, 94 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.0), MSP_CARRIER(6.0), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/AM -- L (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { -4, -12, -9, 23, 79, 126 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0140, 0x0120, 0x7c03}, +}; + +struct CARRIER_DETECT { + int cdo; + char *name; +}; + +static struct CARRIER_DETECT carrier_detect_main[] = { + /* main carrier */ + { MSP_CARRIER(4.5), "4.5 NTSC" }, + { MSP_CARRIER(5.5), "5.5 PAL B/G" }, + { MSP_CARRIER(6.0), "6.0 PAL I" }, + { MSP_CARRIER(6.5), "6.5 PAL D/K + SAT + SECAM" } +}; + +static struct CARRIER_DETECT carrier_detect_55[] = { + /* PAL B/G */ + { MSP_CARRIER(5.7421875), "5.742 PAL B/G FM-stereo" }, + { MSP_CARRIER(5.85), "5.85 PAL B/G NICAM" } +}; + +static struct CARRIER_DETECT carrier_detect_65[] = { + /* PAL SAT / SECAM */ + { MSP_CARRIER(5.85), "5.85 PAL D/K + SECAM NICAM" }, + { MSP_CARRIER(6.2578125), "6.25 PAL D/K1 FM-stereo" }, + { MSP_CARRIER(6.7421875), "6.74 PAL D/K2 FM-stereo" }, + { MSP_CARRIER(7.02), "7.02 PAL SAT FM-stereo s/b" }, + { MSP_CARRIER(7.20), "7.20 PAL SAT FM-stereo s" }, + { MSP_CARRIER(7.38), "7.38 PAL SAT FM-stereo b" }, +}; + +#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT)) + +/* ----------------------------------------------------------------------- */ + +static int scarts[3][9] = { + /* MASK IN1 IN2 IN1_DA IN2_DA IN3 IN4 MONO MUTE */ + { 0x0320, 0x0000, 0x0200, -1, -1, 0x0300, 0x0020, 0x0100, 0x0320 }, + { 0x0c40, 0x0440, 0x0400, 0x0c00, 0x0040, 0x0000, 0x0840, 0x0800, 0x0c40 }, + { 0x3080, 0x1000, 0x1080, 0x0000, 0x0080, 0x2080, 0x3080, 0x2000, 0x3000 }, +}; + +static char *scart_names[] = { + "mask", "in1", "in2", "in1 da", "in2 da", "in3", "in4", "mono", "mute" +}; + +static void +msp3400c_set_scart(struct i2c_client *client, int in, int out) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (-1 == scarts[out][in]) + return; + + dprintk(KERN_DEBUG + "msp34xx: scart switch: %s => %d\n",scart_names[in],out); + msp->acb &= ~scarts[out][SCART_MASK]; + msp->acb |= scarts[out][in]; + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0013, msp->acb); +} + +/* ------------------------------------------------------------------------ */ + +static void msp3400c_setcarrier(struct i2c_client *client, int cdo1, int cdo2) +{ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0093, cdo1 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x009b, cdo1 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00a3, cdo2 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00ab, cdo2 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ +} + +static void msp3400c_setvolume(struct i2c_client *client, + int muted, int volume, int balance) +{ + int val = 0, bal = 0; + + if (!muted) { + val = (volume * 0x7F / 65535) << 8; + } + if (val) { + bal = (balance / 256) - 128; + } + dprintk(KERN_DEBUG + "msp34xx: setvolume: mute=%s %d:%d v=0x%02x b=0x%02x\n", + muted ? "on" : "off", volume, balance, val>>8, bal); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0000, val); /* loudspeaker */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0006, val); /* headphones */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0007, + muted ? 0x01 : (val | 0x01)); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0001, bal << 8); +} + +static void msp3400c_setbass(struct i2c_client *client, int bass) +{ + int val = ((bass-32768) * 0x60 / 65535) << 8; + + dprintk(KERN_DEBUG "msp34xx: setbass: %d 0x%02x\n",bass, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0002, val); /* loudspeaker */ +} + +static void msp3400c_settreble(struct i2c_client *client, int treble) +{ + int val = ((treble-32768) * 0x60 / 65535) << 8; + + dprintk(KERN_DEBUG "msp34xx: settreble: %d 0x%02x\n",treble, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0003, val); /* loudspeaker */ +} + +static void msp3400c_setmode(struct i2c_client *client, int type) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int i; + + dprintk(KERN_DEBUG "msp3400: setmode: %d\n",type); + msp->mode = type; + msp->audmode = V4L2_TUNER_MODE_MONO; + msp->rxsubchans = V4L2_TUNER_SUB_MONO; + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb, /* ad_cv */ + msp_init_data[type].ad_cv); + + for (i = 5; i >= 0; i--) /* fir 1 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0001, + msp_init_data[type].fir1[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0004); /* fir 2 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0040); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0000); + for (i = 5; i >= 0; i--) + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, + msp_init_data[type].fir2[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0083, /* MODE_REG */ + msp_init_data[type].mode_reg); + + msp3400c_setcarrier(client, msp_init_data[type].cdo1, + msp_init_data[type].cdo2); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + 0x0520); /* I2S1 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + 0x0620); /* I2S2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, + msp_init_data[type].dfp_matrix); + + if (HAVE_NICAM(msp)) { + /* nicam prescale */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0010, 0x5a00); /* was: 0x3000 */ + } +} + +static int best_audio_mode(int rxsubchans) +{ + if (rxsubchans & V4L2_TUNER_SUB_STEREO) + return V4L2_TUNER_MODE_STEREO; + if (rxsubchans & V4L2_TUNER_SUB_LANG1) + return V4L2_TUNER_MODE_LANG1; + if (rxsubchans & V4L2_TUNER_SUB_LANG2) + return V4L2_TUNER_MODE_LANG2; + return V4L2_TUNER_MODE_MONO; +} + +/* turn on/off nicam + stereo */ +static void msp3400c_set_audmode(struct i2c_client *client, int audmode) +{ + static char *strmode[16] = { +#if __GNUC__ >= 3 + [ 0 ... 15 ] = "invalid", +#endif + [ V4L2_TUNER_MODE_MONO ] = "mono", + [ V4L2_TUNER_MODE_STEREO ] = "stereo", + [ V4L2_TUNER_MODE_LANG1 ] = "lang1", + [ V4L2_TUNER_MODE_LANG2 ] = "lang2", + }; + struct msp3400c *msp = i2c_get_clientdata(client); + int nicam=0; /* channel source: FM/AM or nicam */ + int src=0; + + BUG_ON(msp->opmode == OPMODE_SIMPLER); + msp->audmode = audmode; + + /* switch demodulator */ + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + dprintk(KERN_DEBUG "msp3400: FM setstereo: %s\n", + strmode[audmode]); + msp3400c_setcarrier(client,msp->second,msp->main); + switch (audmode) { + case V4L2_TUNER_MODE_STEREO: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3001); + break; + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + case V4L2_TUNER_MODE_LANG2: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3000); + break; + } + break; + case MSP_MODE_FM_SAT: + dprintk(KERN_DEBUG "msp3400: SAT setstereo: %s\n", + strmode[audmode]); + switch (audmode) { + case V4L2_TUNER_MODE_MONO: + msp3400c_setcarrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5)); + break; + case V4L2_TUNER_MODE_STEREO: + msp3400c_setcarrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02)); + break; + case V4L2_TUNER_MODE_LANG1: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + case V4L2_TUNER_MODE_LANG2: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + } + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + dprintk(KERN_DEBUG "msp3400: NICAM setstereo: %s\n", + strmode[audmode]); + msp3400c_setcarrier(client,msp->second,msp->main); + if (msp->nicam_on) + nicam=0x0100; + break; + case MSP_MODE_BTSC: + dprintk(KERN_DEBUG "msp3400: BTSC setstereo: %s\n", + strmode[audmode]); + nicam=0x0300; + break; + case MSP_MODE_EXTERN: + dprintk(KERN_DEBUG "msp3400: extern setstereo: %s\n", + strmode[audmode]); + nicam = 0x0200; + break; + case MSP_MODE_FM_RADIO: + dprintk(KERN_DEBUG "msp3400: FM-Radio setstereo: %s\n", + strmode[audmode]); + break; + default: + dprintk(KERN_DEBUG "msp3400: mono setstereo\n"); + return; + } + + /* switch audio */ + switch (audmode) { + case V4L2_TUNER_MODE_STEREO: + src = 0x0020 | nicam; +#if 0 + /* spatial effect */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0005,0x4000); +#endif + break; + case V4L2_TUNER_MODE_MONO: + if (msp->mode == MSP_MODE_AM_NICAM) { + dprintk("msp3400: switching to AM mono\n"); + /* AM mono decoding is handled by tuner, not MSP chip */ + /* SCART switching control register */ + msp3400c_set_scart(client,SCART_MONO,0); + src = 0x0200; + break; + } + case V4L2_TUNER_MODE_LANG1: + src = 0x0000 | nicam; + break; + case V4L2_TUNER_MODE_LANG2: + src = 0x0010 | nicam; + break; + } + dprintk(KERN_DEBUG + "msp3400: setstereo final source/matrix = 0x%x\n", src); + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,0x0520); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,0x0620); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } +} + +static void +msp3400c_print_mode(struct msp3400c *msp) +{ + if (msp->main == msp->second) { + printk(KERN_DEBUG "msp3400: mono sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } else { + printk(KERN_DEBUG "msp3400: main sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } + if (msp->mode == MSP_MODE_FM_NICAM1 || + msp->mode == MSP_MODE_FM_NICAM2) + printk(KERN_DEBUG "msp3400: NICAM/FM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_AM_NICAM) + printk(KERN_DEBUG "msp3400: NICAM/AM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_FM_TERRA && + msp->main != msp->second) { + printk(KERN_DEBUG "msp3400: FM-stereo carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + } +} + +/* ----------------------------------------------------------------------- */ + +struct REGISTER_DUMP { + int addr; + char *name; +}; + +static int +autodetect_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int val; + int rxsubchans = msp->rxsubchans; + int newnicam = msp->nicam_on; + int update = 0; + + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x18); + if (val > 32767) + val -= 65536; + dprintk(KERN_DEBUG + "msp34xx: stereo detect register: %d\n",val); + if (val > 4096) { + rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + } else if (val < -4096) { + rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + } else { + rxsubchans = V4L2_TUNER_SUB_MONO; + } + newnicam = 0; + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x23); + dprintk(KERN_DEBUG + "msp34xx: nicam sync=%d, mode=%d\n", + val & 1, (val & 0x1e) >> 1); + + if (val & 1) { + /* nicam synced */ + switch ((val & 0x1e) >> 1) { + case 0: + case 8: + rxsubchans = V4L2_TUNER_SUB_STEREO; + break; + case 1: + case 9: + rxsubchans = V4L2_TUNER_SUB_MONO + | V4L2_TUNER_SUB_LANG1; + break; + case 2: + case 10: + rxsubchans = V4L2_TUNER_SUB_MONO + | V4L2_TUNER_SUB_LANG1 + | V4L2_TUNER_SUB_LANG2; + break; + default: + rxsubchans = V4L2_TUNER_SUB_MONO; + break; + } + newnicam=1; + } else { + newnicam = 0; + rxsubchans = V4L2_TUNER_SUB_MONO; + } + break; + case MSP_MODE_BTSC: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x200); + dprintk(KERN_DEBUG + "msp3410: status=0x%x (pri=%s, sec=%s, %s%s%s)\n", + val, + (val & 0x0002) ? "no" : "yes", + (val & 0x0004) ? "no" : "yes", + (val & 0x0040) ? "stereo" : "mono", + (val & 0x0080) ? ", nicam 2nd mono" : "", + (val & 0x0100) ? ", bilingual/SAP" : ""); + rxsubchans = V4L2_TUNER_SUB_MONO; + if (val & 0x0040) rxsubchans |= V4L2_TUNER_SUB_STEREO; + if (val & 0x0100) rxsubchans |= V4L2_TUNER_SUB_LANG1; + break; + } + if (rxsubchans != msp->rxsubchans) { + update = 1; + dprintk(KERN_DEBUG "msp34xx: watch: rxsubchans %d => %d\n", + msp->rxsubchans,rxsubchans); + msp->rxsubchans = rxsubchans; + } + if (newnicam != msp->nicam_on) { + update = 1; + dprintk(KERN_DEBUG "msp34xx: watch: nicam %d => %d\n", + msp->nicam_on,newnicam); + msp->nicam_on = newnicam; + } + return update; +} + +/* + * A kernel thread for msp3400 control -- we don't want to block the + * in the ioctl while doing the sound carrier & stereo detect + */ + +static int msp34xx_sleep(struct msp3400c *msp, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + +again: + add_wait_queue(&msp->wq, &wait); + if (!kthread_should_stop()) { + if (timeout < 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { +#if 0 + /* hmm, that one doesn't return on wakeup ... */ + msleep_interruptible(timeout); +#else + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(timeout)); +#endif + } + } + + remove_wait_queue(&msp->wq, &wait); + + if (try_to_freeze(PF_FREEZE)) + goto again; + + return msp->restart; +} + +/* stereo/multilang monitoring */ +static void watch_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (autodetect_stereo(client)) + msp3400c_set_audmode(client,best_audio_mode(msp->rxsubchans)); + if (once) + msp->watch_stereo = 0; +} + +static int msp3400c_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + struct CARRIER_DETECT *cd; + int count, max1,max2,val1,val2, val,this; + + printk("msp3400: kthread started\n"); + for (;;) { + d2printk("msp3400: thread: sleep\n"); + msp34xx_sleep(msp,-1); + d2printk("msp3400: thread: wakeup\n"); + + restart: + dprintk("msp3410: thread: restart scan\n"); + msp->restart = 0; + if (kthread_should_stop()) + break; + + if (VIDEO_MODE_RADIO == msp->norm || + MSP_MODE_EXTERN == msp->mode) { + /* no carrier scan, just unmute */ + printk("msp3400: thread: no carrier scan\n"); + msp3400c_setvolume(client, msp->muted, + msp->volume, msp->balance); + continue; + } + + /* mute */ + msp3400c_setvolume(client, msp->muted, 0, 0); + msp3400c_setmode(client, MSP_MODE_AM_DETECT /* +1 */ ); + val1 = val2 = 0; + max1 = max2 = -1; + msp->watch_stereo = 0; + + /* some time for the tuner to sync */ + if (msp34xx_sleep(msp,200)) + goto restart; + + /* carrier detect pass #1 -- main carrier */ + cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main); + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + max1 = 3; + count = 0; + dprintk("msp3400: AM sound override\n"); + } + + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + if (msp34xx_sleep(msp,100)) + goto restart; + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32767) + val -= 65536; + if (val1 < val) + val1 = val, max1 = this; + dprintk("msp3400: carrier1 val: %5d / %s\n", val,cd[this].name); + } + + /* carrier detect pass #2 -- second (stereo) carrier */ + switch (max1) { + case 1: /* 5.5 */ + cd = carrier_detect_55; + count = CARRIER_COUNT(carrier_detect_55); + break; + case 3: /* 6.5 */ + cd = carrier_detect_65; + count = CARRIER_COUNT(carrier_detect_65); + break; + case 0: /* 4.5 */ + case 2: /* 6.0 */ + default: + cd = NULL; count = 0; + break; + } + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + cd = NULL; count = 0; max2 = 0; + } + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + if (msp34xx_sleep(msp,100)) + goto restart; + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32767) + val -= 65536; + if (val2 < val) + val2 = val, max2 = this; + dprintk("msp3400: carrier2 val: %5d / %s\n", val,cd[this].name); + } + + /* programm the msp3400 according to the results */ + msp->main = carrier_detect_main[max1].cdo; + switch (max1) { + case 1: /* 5.5 */ + if (max2 == 0) { + /* B/G FM-stereo */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_set_audmode(client, V4L2_TUNER_MODE_MONO); + msp->watch_stereo = 1; + } else if (max2 == 1 && HAVE_NICAM(msp)) { + /* B/G NICAM */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 2: /* 6.0 */ + /* PAL I NICAM */ + msp->second = MSP_CARRIER(6.552); + msp3400c_setmode(client, MSP_MODE_FM_NICAM2); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + break; + case 3: /* 6.5 */ + if (max2 == 1 || max2 == 2) { + /* D/K FM-stereo */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_set_audmode(client, V4L2_TUNER_MODE_MONO); + msp->watch_stereo = 1; + } else if (max2 == 0 && + msp->norm == VIDEO_MODE_SECAM) { + /* L NICAM or AM-mono */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_AM_NICAM); + msp->nicam_on = 0; + msp3400c_set_audmode(client, V4L2_TUNER_MODE_MONO); + msp3400c_setcarrier(client, msp->second, msp->main); + /* volume prescale for SCART (AM mono input) */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000d, 0x1900); + msp->watch_stereo = 1; + } else if (max2 == 0 && HAVE_NICAM(msp)) { + /* D/K NICAM */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 0: /* 4.5 */ + default: + no_second: + msp->second = carrier_detect_main[max1].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->rxsubchans = V4L2_TUNER_SUB_MONO; + msp3400c_set_audmode(client, V4L2_TUNER_MODE_MONO); + break; + } + + /* unmute */ + msp3400c_setvolume(client, msp->muted, + msp->volume, msp->balance); + if (debug) + msp3400c_print_mode(msp); + + /* monitor tv audio mode */ + while (msp->watch_stereo) { + if (msp34xx_sleep(msp,5000)) + goto restart; + watch_stereo(client); + } + } + dprintk(KERN_DEBUG "msp3400: thread: exit\n"); + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* this one uses the automatic sound standard detection of newer */ +/* msp34xx chip versions */ + +static struct MODES { + int retval; + int main, second; + char *name; +} modelist[] = { + { 0x0000, 0, 0, "ERROR" }, + { 0x0001, 0, 0, "autodetect start" }, + { 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), "4.5/4.72 M Dual FM-Stereo" }, + { 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), "5.5/5.74 B/G Dual FM-Stereo" }, + { 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), "6.5/6.25 D/K1 Dual FM-Stereo" }, + { 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), "6.5/6.74 D/K2 Dual FM-Stereo" }, + { 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 D/K FM-Mono (HDEV3)" }, + { 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), "5.5/5.85 B/G NICAM FM" }, + { 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 L NICAM AM" }, + { 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), "6.0/6.55 I NICAM FM" }, + { 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM" }, + { 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM (HDEV2)" }, + { 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Stereo" }, + { 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Mono + SAP" }, + { 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M EIA-J Japan Stereo" }, + { 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), "10.7 FM-Stereo Radio" }, + { 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 SAT-Mono" }, + { 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), "7.02/7.20 SAT-Stereo" }, + { 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2 SAT ADR" }, + { -1, 0, 0, NULL }, /* EOF */ +}; + +static inline const char *msp34xx_standard_mode_name(int mode) +{ + int i; + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == mode) + return modelist[i].name; + return "unknown"; +} + +static int msp34xx_modus(int norm) +{ + switch (norm) { + case VIDEO_MODE_PAL: + return 0x1003; + case VIDEO_MODE_NTSC: /* BTSC */ + return 0x2003; + case VIDEO_MODE_SECAM: + return 0x0003; + case VIDEO_MODE_RADIO: + return 0x0003; + case VIDEO_MODE_AUTO: + return 0x2003; + default: + return 0x0003; + } +} + +static int msp34xx_standard(int norm) +{ + switch (norm) { + case VIDEO_MODE_PAL: + return 1; + case VIDEO_MODE_NTSC: /* BTSC */ + return 0x0020; + case VIDEO_MODE_SECAM: + return 1; + case VIDEO_MODE_RADIO: + return 0x0040; + default: + return 1; + } +} + +static int msp3410d_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + int mode,val,i,std; + + printk("msp3410: daemon started\n"); + for (;;) { + d2printk(KERN_DEBUG "msp3410: thread: sleep\n"); + msp34xx_sleep(msp,-1); + d2printk(KERN_DEBUG "msp3410: thread: wakeup\n"); + + restart: + dprintk("msp3410: thread: restart scan\n"); + msp->restart = 0; + if (kthread_should_stop()) + break; + + if (msp->mode == MSP_MODE_EXTERN) { + /* no carrier scan needed, just unmute */ + dprintk(KERN_DEBUG "msp3410: thread: no carrier scan\n"); + msp3400c_setvolume(client, msp->muted, + msp->volume, msp->balance); + continue; + } + + /* put into sane state (and mute) */ + msp3400c_reset(client); + + /* some time for the tuner to sync */ + if (msp34xx_sleep(msp,200)) + goto restart; + + /* start autodetect */ + mode = msp34xx_modus(msp->norm); + std = msp34xx_standard(msp->norm); + msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, mode); + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, std); + msp->watch_stereo = 0; + + if (debug) + printk(KERN_DEBUG "msp3410: setting mode: %s (0x%04x)\n", + msp34xx_standard_mode_name(std) ,std); + + if (std != 1) { + /* programmed some specific mode */ + val = std; + } else { + /* triggered autodetect */ + for (;;) { + if (msp34xx_sleep(msp,100)) + goto restart; + + /* check results */ + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e); + if (val < 0x07ff) + break; + dprintk(KERN_DEBUG "msp3410: detection still in progress\n"); + } + } + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == val) + break; + dprintk(KERN_DEBUG "msp3410: current mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown", + val); + msp->main = modelist[i].main; + msp->second = modelist[i].second; + + if (amsound && (msp->norm == VIDEO_MODE_SECAM) && (val != 0x0009)) { + /* autodetection has failed, let backup */ + dprintk(KERN_DEBUG "msp3410: autodetection failed," + " switching to backup mode: %s (0x%04x)\n", + modelist[8].name ? modelist[8].name : "unknown",val); + val = 0x0009; + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, val); + } + + /* set various prescales */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0d, 0x1900); /* scart */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); /* nicam */ + + /* set stereo */ + switch (val) { + case 0x0008: /* B/G NICAM */ + case 0x000a: /* I NICAM */ + if (val == 0x0008) + msp->mode = MSP_MODE_FM_NICAM1; + else + msp->mode = MSP_MODE_FM_NICAM2; + /* just turn on stereo */ + msp->rxsubchans = V4L2_TUNER_SUB_STEREO; + msp->nicam_on = 1; + msp->watch_stereo = 1; + msp3400c_set_audmode(client,V4L2_TUNER_MODE_STEREO); + break; + case 0x0009: + msp->mode = MSP_MODE_AM_NICAM; + msp->rxsubchans = V4L2_TUNER_SUB_MONO; + msp->nicam_on = 1; + msp3400c_set_audmode(client,V4L2_TUNER_MODE_MONO); + msp->watch_stereo = 1; + break; + case 0x0020: /* BTSC */ + /* just turn on stereo */ + msp->mode = MSP_MODE_BTSC; + msp->rxsubchans = V4L2_TUNER_SUB_STEREO; + msp->nicam_on = 0; + msp->watch_stereo = 1; + msp3400c_set_audmode(client,V4L2_TUNER_MODE_STEREO); + break; + case 0x0040: /* FM radio */ + msp->mode = MSP_MODE_FM_RADIO; + msp->rxsubchans = V4L2_TUNER_SUB_STEREO; + msp->audmode = V4L2_TUNER_MODE_STEREO; + msp->nicam_on = 0; + msp->watch_stereo = 0; + /* not needed in theory if HAVE_RADIO(), but + short programming enables carrier mute */ + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + /* scart routing */ + msp3400c_set_scart(client,SCART_IN2,0); +#if 0 + /* radio from SCART_IN2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0220); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0220); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0220); +#else + /* msp34xx does radio decoding */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0020); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0020); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0020); +#endif + break; + case 0x0003: + case 0x0004: + case 0x0005: + msp->mode = MSP_MODE_FM_TERRA; + msp->rxsubchans = V4L2_TUNER_SUB_MONO; + msp->audmode = V4L2_TUNER_MODE_MONO; + msp->nicam_on = 0; + msp->watch_stereo = 1; + break; + } + + /* unmute, restore misc registers */ + msp3400c_setbass(client, msp->bass); + msp3400c_settreble(client, msp->treble); + msp3400c_setvolume(client, msp->muted, + msp->volume, msp->balance); + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0013, msp->acb); + + /* monitor tv audio mode */ + while (msp->watch_stereo) { + if (msp34xx_sleep(msp,5000)) + goto restart; + watch_stereo(client); + } + } + dprintk(KERN_DEBUG "msp3410: thread: exit\n"); + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* msp34xxG + (simpler no-thread) */ +/* this one uses both automatic standard detection and automatic sound */ +/* select which are available in the newer G versions */ +/* struct msp: only norm, acb and source are really used in this mode */ + +static void msp34xxg_set_source(struct i2c_client *client, int source); + +/* (re-)initialize the msp34xxg, according to the current norm in msp->norm + * return 0 if it worked, -1 if it failed + */ +static int msp34xxg_init(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int modus,std; + + if (msp3400c_reset(client)) + return -1; + + /* make sure that input/output is muted (paranoid mode) */ + if (msp3400c_write(client, + I2C_MSP3400C_DFP, + 0x13, /* ACB */ + 0x0f20 /* mute DSP input, mute SCART 1 */)) + return -1; + + /* step-by-step initialisation, as described in the manual */ + modus = msp34xx_modus(msp->norm); + std = msp34xx_standard(msp->norm); + modus &= ~0x03; /* STATUS_CHANGE=0 */ + modus |= 0x01; /* AUTOMATIC_SOUND_DETECTION=1 */ + if (msp3400c_write(client, + I2C_MSP3400C_DEM, + 0x30/*MODUS*/, + modus)) + return -1; + if (msp3400c_write(client, + I2C_MSP3400C_DEM, + 0x20/*stanard*/, + std)) + return -1; + + /* write the dfps that may have an influence on + standard/audio autodetection right now */ + msp34xxg_set_source(client, msp->source); + + if (msp3400c_write(client, I2C_MSP3400C_DFP, + 0x0e, /* AM/FM Prescale */ + 0x3000 /* default: [15:8] 75khz deviation */)) + return -1; + + if (msp3400c_write(client, I2C_MSP3400C_DFP, + 0x10, /* NICAM Prescale */ + 0x5a00 /* default: 9db gain (as recommended) */)) + return -1; + + if (msp3400c_write(client, + I2C_MSP3400C_DEM, + 0x20, /* STANDARD SELECT */ + standard /* default: 0x01 for automatic standard select*/)) + return -1; + return 0; +} + +static int msp34xxg_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = i2c_get_clientdata(client); + int val, std, i; + + printk("msp34xxg: daemon started\n"); + for (;;) { + d2printk(KERN_DEBUG "msp34xxg: thread: sleep\n"); + msp34xx_sleep(msp,-1); + d2printk(KERN_DEBUG "msp34xxg: thread: wakeup\n"); + + restart: + dprintk("msp34xxg: thread: restart scan\n"); + msp->restart = 0; + if (kthread_should_stop()) + break; + + /* setup the chip*/ + msp34xxg_init(client); + std = standard; + if (std != 0x01) + goto unmute; + + /* watch autodetect */ + dprintk("msp34xxg: triggered autodetect, waiting for result\n"); + for (i = 0; i < 10; i++) { + if (msp34xx_sleep(msp,100)) + goto restart; + + /* check results */ + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e); + if (val < 0x07ff) { + std = val; + break; + } + dprintk("msp34xxg: detection still in progress\n"); + } + if (0x01 == std) { + dprintk("msp34xxg: detection still in progress after 10 tries. giving up.\n"); + continue; + } + + unmute: + dprintk("msp34xxg: current mode: %s (0x%04x)\n", + msp34xx_standard_mode_name(std), std); + + /* unmute: dispatch sound to scart output, set scart volume */ + dprintk("msp34xxg: unmute\n"); + + msp3400c_setbass(client, msp->bass); + msp3400c_settreble(client, msp->treble); + msp3400c_setvolume(client, msp->muted, msp->volume, msp->balance); + + /* restore ACB */ + if (msp3400c_write(client, + I2C_MSP3400C_DFP, + 0x13, /* ACB */ + msp->acb)) + return -1; + } + dprintk(KERN_DEBUG "msp34xxg: thread: exit\n"); + return 0; +} + +/* set the same 'source' for the loudspeaker, scart and quasi-peak detector + * the value for source is the same as bit 15:8 of DFP registers 0x08, + * 0x0a and 0x0c: 0=mono, 1=stereo or A|B, 2=SCART, 3=stereo or A, 4=stereo or B + * + * this function replaces msp3400c_setstereo + */ +static void msp34xxg_set_source(struct i2c_client *client, int source) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + /* fix matrix mode to stereo and let the msp choose what + * to output according to 'source', as recommended + */ + int value = (source&0x07)<<8|(source==0 ? 0x00:0x20); + dprintk("msp34xxg: set source to %d (0x%x)\n", source, value); + msp3400c_write(client, + I2C_MSP3400C_DFP, + 0x08, /* Loudspeaker Output */ + value); + msp3400c_write(client, + I2C_MSP3400C_DFP, + 0x0a, /* SCART1 DA Output */ + value); + msp3400c_write(client, + I2C_MSP3400C_DFP, + 0x0c, /* Quasi-peak detector */ + value); + /* + * set identification threshold. Personally, I + * I set it to a higher value that the default + * of 0x190 to ignore noisy stereo signals. + * this needs tuning. (recommended range 0x00a0-0x03c0) + * 0x7f0 = forced mono mode + */ + msp3400c_write(client, + I2C_MSP3400C_DEM, + 0x22, /* a2 threshold for stereo/bilingual */ + source==0 ? 0x7f0:stereo_threshold); + msp->source=source; +} + +static void msp34xxg_detect_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + int status = msp3400c_read(client, + I2C_MSP3400C_DEM, + 0x0200 /* STATUS */); + int is_bilingual = status&0x100; + int is_stereo = status&0x40; + + msp->rxsubchans = 0; + if (is_stereo) + msp->rxsubchans |= V4L2_TUNER_SUB_STEREO; + else + msp->rxsubchans |= V4L2_TUNER_SUB_MONO; + if (is_bilingual) { + msp->rxsubchans |= V4L2_TUNER_SUB_LANG1|V4L2_TUNER_SUB_LANG2; + /* I'm supposed to check whether it's SAP or not + * and set only LANG2/SAP in this case. Yet, the MSP + * does a lot of work to hide this and handle everything + * the same way. I don't want to work around it so unless + * this is a problem, I'll handle SAP just like lang1/lang2. + */ + } + dprintk("msp34xxg: status=0x%x, stereo=%d, bilingual=%d -> rxsubchans=%d\n", + status, is_stereo, is_bilingual, msp->rxsubchans); +} + +static void msp34xxg_set_audmode(struct i2c_client *client, int audmode) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + int source = 0; + + switch (audmode) { + case V4L2_TUNER_MODE_MONO: + source=0; /* mono only */ + break; + case V4L2_TUNER_MODE_STEREO: + source=1; /* stereo or A|B, see comment in msp34xxg_get_v4l2_stereo() */ + /* problem: that could also mean 2 (scart input) */ + break; + case V4L2_TUNER_MODE_LANG1: + source=3; /* stereo or A */ + break; + case V4L2_TUNER_MODE_LANG2: + source=4; /* stereo or B */ + break; + default: /* doing nothing: a safe, sane default */ + audmode = 0; + return; + } + msp->audmode = audmode; + msp34xxg_set_source(client, source); +} + + +/* ----------------------------------------------------------------------- */ + +static int msp_attach(struct i2c_adapter *adap, int addr, int kind); +static int msp_detach(struct i2c_client *client); +static int msp_probe(struct i2c_adapter *adap); +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg); + +static int msp_suspend(struct device * dev, u32 state, u32 level); +static int msp_resume(struct device * dev, u32 level); + +static void msp_wake_thread(struct i2c_client *client); + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "i2c msp3400 driver", + .id = I2C_DRIVERID_MSP3400, + .flags = I2C_DF_NOTIFY, + .attach_adapter = msp_probe, + .detach_client = msp_detach, + .command = msp_command, + .driver = { + .suspend = msp_suspend, + .resume = msp_resume, + }, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int msp_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct msp3400c *msp; + struct i2c_client *c; + int (*thread_func)(void *data) = NULL; + + client_template.adapter = adap; + client_template.addr = addr; + + if (-1 == msp3400c_reset(&client_template)) { + dprintk("msp3400: no chip found\n"); + return -1; + } + + if (NULL == (c = kmalloc(sizeof(struct i2c_client),GFP_KERNEL))) + return -ENOMEM; + memcpy(c,&client_template,sizeof(struct i2c_client)); + if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) { + kfree(c); + return -ENOMEM; + } + + memset(msp,0,sizeof(struct msp3400c)); + msp->volume = 58880; /* 0db gain */ + msp->balance = 32768; + msp->bass = 32768; + msp->treble = 32768; + msp->input = -1; + msp->muted = 1; + + i2c_set_clientdata(c, msp); + init_waitqueue_head(&msp->wq); + + if (-1 == msp3400c_reset(c)) { + kfree(msp); + kfree(c); + dprintk("msp3400: no chip found\n"); + return -1; + } + + msp->rev1 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1e); + if (-1 != msp->rev1) + msp->rev2 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1f); + if ((-1 == msp->rev1) || (0 == msp->rev1 && 0 == msp->rev2)) { + kfree(msp); + kfree(c); + printk("msp3400: error while reading chip version\n"); + return -1; + } + +#if 0 + /* this will turn on a 1kHz beep - might be useful for debugging... */ + msp3400c_write(c,I2C_MSP3400C_DFP, 0x0014, 0x1040); +#endif + msp3400c_setvolume(c, msp->muted, msp->volume, msp->balance); + + snprintf(c->name, sizeof(c->name), "MSP34%02d%c-%c%d", + (msp->rev2>>8)&0xff, (msp->rev1&0xff)+'@', + ((msp->rev1>>8)&0xff)+'@', msp->rev2&0x1f); + + msp->opmode = opmode; + if (OPMODE_AUTO == msp->opmode) { +#if 0 /* seems to work for ivtv only, disable by default for now ... */ + if (HAVE_SIMPLER(msp)) + msp->opmode = OPMODE_SIMPLER; + else +#endif + if (HAVE_SIMPLE(msp)) + msp->opmode = OPMODE_SIMPLE; + else + msp->opmode = OPMODE_MANUAL; + } + + /* hello world :-) */ + printk(KERN_INFO "msp34xx: init: chip=%s",i2c_clientname(c)); + if (HAVE_NICAM(msp)) + printk(" +nicam"); + if (HAVE_SIMPLE(msp)) + printk(" +simple"); + if (HAVE_SIMPLER(msp)) + printk(" +simpler"); + if (HAVE_RADIO(msp)) + printk(" +radio"); + + /* version-specific initialization */ + switch (msp->opmode) { + case OPMODE_MANUAL: + printk(" mode=manual"); + thread_func = msp3400c_thread; + break; + case OPMODE_SIMPLE: + printk(" mode=simple"); + thread_func = msp3410d_thread; + break; + case OPMODE_SIMPLER: + printk(" mode=simpler"); + thread_func = msp34xxg_thread; + break; + } + printk("\n"); + + /* startup control thread if needed */ + if (thread_func) { + msp->kthread = kthread_run(thread_func, c, "msp34xx"); + if (NULL == msp->kthread) + printk(KERN_WARNING "msp34xx: kernel_thread() failed\n"); + msp_wake_thread(c); + } + + /* done */ + i2c_attach_client(c); + return 0; +} + +static int msp_detach(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + /* shutdown control thread */ + if (msp->kthread >= 0) { + msp->restart = 1; + kthread_stop(msp->kthread); + } + msp3400c_reset(client); + + i2c_detach_client(client); + kfree(msp); + kfree(client); + return 0; +} + +static int msp_probe(struct i2c_adapter *adap) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, msp_attach); + return 0; +} + +static void msp_wake_thread(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + if (NULL == msp->kthread) + return; + msp3400c_setvolume(client,msp->muted,0,0); + msp->watch_stereo = 0; + msp->restart = 1; + wake_up_interruptible(&msp->wq); +} + +/* ----------------------------------------------------------------------- */ + +static int mode_v4l2_to_v4l1(int rxsubchans) +{ + int mode = 0; + + if (rxsubchans & V4L2_TUNER_SUB_STEREO) + mode |= VIDEO_SOUND_STEREO; + if (rxsubchans & V4L2_TUNER_SUB_LANG2) + mode |= VIDEO_SOUND_LANG2; + if (rxsubchans & V4L2_TUNER_SUB_LANG1) + mode |= VIDEO_SOUND_LANG1; + if (0 == mode) + mode |= VIDEO_SOUND_MONO; + return mode; +} + +static int mode_v4l1_to_v4l2(int mode) +{ + if (mode & VIDEO_SOUND_STEREO) + return V4L2_TUNER_MODE_STEREO; + if (mode & VIDEO_SOUND_LANG2) + return V4L2_TUNER_MODE_LANG2; + if (mode & VIDEO_SOUND_LANG1) + return V4L2_TUNER_MODE_LANG1; + return V4L2_TUNER_MODE_MONO; +} + +static void msp_any_detect_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + switch (msp->opmode) { + case OPMODE_MANUAL: + case OPMODE_SIMPLE: + autodetect_stereo(client); + break; + case OPMODE_SIMPLER: + msp34xxg_detect_stereo(client); + break; + } +} + +static void msp_any_set_audmode(struct i2c_client *client, int audmode) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + + switch (msp->opmode) { + case OPMODE_MANUAL: + case OPMODE_SIMPLE: + msp->watch_stereo = 0; + msp3400c_set_audmode(client, audmode); + break; + case OPMODE_SIMPLER: + msp34xxg_set_audmode(client, audmode); + break; + } +} + +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct msp3400c *msp = i2c_get_clientdata(client); + __u16 *sarg = arg; + int scart = 0; + + switch (cmd) { + + case AUDC_SET_INPUT: + dprintk(KERN_DEBUG "msp34xx: AUDC_SET_INPUT(%d)\n",*sarg); + if (*sarg == msp->input) + break; + msp->input = *sarg; + switch (*sarg) { + case AUDIO_RADIO: + /* Hauppauge uses IN2 for the radio */ + msp->mode = MSP_MODE_FM_RADIO; + scart = SCART_IN2; + break; + case AUDIO_EXTERN_1: + /* IN1 is often used for external input ... */ + msp->mode = MSP_MODE_EXTERN; + scart = SCART_IN1; + break; + case AUDIO_EXTERN_2: + /* ... sometimes it is IN2 through ;) */ + msp->mode = MSP_MODE_EXTERN; + scart = SCART_IN2; + break; + case AUDIO_TUNER: + msp->mode = -1; + break; + default: + if (*sarg & AUDIO_MUTE) + msp3400c_set_scart(client,SCART_MUTE,0); + break; + } + if (scart) { + msp->rxsubchans = V4L2_TUNER_SUB_STEREO; + msp->audmode = V4L2_TUNER_MODE_STEREO; + msp3400c_set_scart(client,scart,0); + msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900); + if (msp->opmode != OPMODE_SIMPLER) + msp3400c_set_audmode(client, msp->audmode); + } + msp_wake_thread(client); + break; + + case AUDC_SET_RADIO: + dprintk(KERN_DEBUG "msp34xx: AUDC_SET_RADIO\n"); + msp->norm = VIDEO_MODE_RADIO; + dprintk(KERN_DEBUG "msp34xx: switching to radio mode\n"); + msp->watch_stereo = 0; + switch (msp->opmode) { + case OPMODE_MANUAL: + /* set msp3400 to FM radio mode */ + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7), + MSP_CARRIER(10.7)); + msp3400c_setvolume(client, msp->muted, + msp->volume, msp->balance); + break; + case OPMODE_SIMPLE: + case OPMODE_SIMPLER: + /* the thread will do for us */ + msp_wake_thread(client); + break; + } + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCGAUDIO\n"); + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE | + VIDEO_AUDIO_MUTABLE; + if (msp->muted) + va->flags |= VIDEO_AUDIO_MUTE; + + va->volume = msp->volume; + va->balance = (va->volume) ? msp->balance : 32768; + va->bass = msp->bass; + va->treble = msp->treble; + + msp_any_detect_stereo(client); + va->mode = mode_v4l2_to_v4l1(msp->rxsubchans); + break; + } + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCSAUDIO\n"); + msp->muted = (va->flags & VIDEO_AUDIO_MUTE); + msp->volume = va->volume; + msp->balance = va->balance; + msp->bass = va->bass; + msp->treble = va->treble; + + msp3400c_setvolume(client, msp->muted, + msp->volume, msp->balance); + msp3400c_setbass(client,msp->bass); + msp3400c_settreble(client,msp->treble); + + if (va->mode != 0 && msp->norm != VIDEO_MODE_RADIO) + msp_any_set_audmode(client,mode_v4l1_to_v4l2(va->mode)); + break; + } + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + dprintk(KERN_DEBUG "msp34xx: VIDIOCSCHAN (norm=%d)\n",vc->norm); + msp->norm = vc->norm; + msp_wake_thread(client); + break; + } + + case VIDIOCSFREQ: + case VIDIOC_S_FREQUENCY: + { + /* new channel -- kick audio carrier scan */ + dprintk(KERN_DEBUG "msp34xx: VIDIOCSFREQ\n"); + msp_wake_thread(client); + break; + } + + /* --- v4l2 ioctls --- */ + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *vt = arg; + + msp_any_detect_stereo(client); + vt->audmode = msp->audmode; + vt->rxsubchans = msp->rxsubchans; + vt->capability = V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1| + V4L2_TUNER_CAP_LANG2; + break; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *vt=(struct v4l2_tuner *)arg; + + /* only set audmode */ + if (vt->audmode != -1 && vt->audmode != 0) + msp_any_set_audmode(client, vt->audmode); + break; + } + + /* msp34xx specific */ + case MSP_SET_MATRIX: + { + struct msp_matrix *mspm = arg; + + dprintk(KERN_DEBUG "msp34xx: MSP_SET_MATRIX\n"); + msp3400c_set_scart(client, mspm->input, mspm->output); + break; + } + + default: + /* nothing */ + break; + } + return 0; +} + +static int msp_suspend(struct device * dev, u32 state, u32 level) +{ + struct i2c_client *c = container_of(dev, struct i2c_client, dev); + + dprintk("msp34xx: suspend\n"); + msp3400c_reset(c); + return 0; +} + +static int msp_resume(struct device * dev, u32 level) +{ + struct i2c_client *c = container_of(dev, struct i2c_client, dev); + + dprintk("msp34xx: resume\n"); + msp_wake_thread(c); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int __init msp3400_init_module(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit msp3400_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(msp3400_init_module); +module_exit(msp3400_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/msp3400.h b/drivers/media/video/msp3400.h new file mode 100644 index 000000000000..d70a954e13aa --- /dev/null +++ b/drivers/media/video/msp3400.h @@ -0,0 +1,36 @@ +#ifndef MSP3400_H +#define MSP3400_H + +/* ---------------------------------------------------------------------- */ + +struct msp_dfpreg { + int reg; + int value; +}; + +struct msp_matrix { + int input; + int output; +}; + +#define MSP_SET_DFPREG _IOW('m',15,struct msp_dfpreg) +#define MSP_GET_DFPREG _IOW('m',16,struct msp_dfpreg) + +/* ioctl for MSP_SET_MATRIX will have to be registered */ +#define MSP_SET_MATRIX _IOW('m',17,struct msp_matrix) + +#define SCART_MASK 0 +#define SCART_IN1 1 +#define SCART_IN2 2 +#define SCART_IN1_DA 3 +#define SCART_IN2_DA 4 +#define SCART_IN3 5 +#define SCART_IN4 6 +#define SCART_MONO 7 +#define SCART_MUTE 8 + +#define SCART_DSP_IN 0 +#define SCART1_OUT 1 +#define SCART2_OUT 2 + +#endif /* MSP3400_H */ diff --git a/drivers/media/video/mt20xx.c b/drivers/media/video/mt20xx.c new file mode 100644 index 000000000000..95ad17b7f38e --- /dev/null +++ b/drivers/media/video/mt20xx.c @@ -0,0 +1,558 @@ +/* + * $Id: mt20xx.c,v 1.4 2005/03/04 09:24:56 kraxel Exp $ + * + * i2c tv tuner chip device driver + * controls microtune tuners, mt2032 + mt2050 at the moment. + */ +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/moduleparam.h> +#include <media/tuner.h> + +/* ---------------------------------------------------------------------- */ + +static unsigned int optimize_vco = 1; +module_param(optimize_vco, int, 0644); + +static unsigned int tv_antenna = 1; +module_param(tv_antenna, int, 0644); + +static unsigned int radio_antenna = 0; +module_param(radio_antenna, int, 0644); + +/* ---------------------------------------------------------------------- */ + +#define MT2032 0x04 +#define MT2030 0x06 +#define MT2040 0x07 +#define MT2050 0x42 + +static char *microtune_part[] = { + [ MT2030 ] = "MT2030", + [ MT2032 ] = "MT2032", + [ MT2040 ] = "MT2040", + [ MT2050 ] = "MT2050", +}; + +// IsSpurInBand()? +static int mt2032_spurcheck(struct i2c_client *c, + int f1, int f2, int spectrum_from,int spectrum_to) +{ + struct tuner *t = i2c_get_clientdata(c); + int n1=1,n2,f; + + f1=f1/1000; //scale to kHz to avoid 32bit overflows + f2=f2/1000; + spectrum_from/=1000; + spectrum_to/=1000; + + tuner_dbg("spurcheck f1=%d f2=%d from=%d to=%d\n", + f1,f2,spectrum_from,spectrum_to); + + do { + n2=-n1; + f=n1*(f1-f2); + do { + n2--; + f=f-f2; + tuner_dbg("spurtest n1=%d n2=%d ftest=%d\n",n1,n2,f); + + if( (f>spectrum_from) && (f<spectrum_to)) + tuner_dbg("mt2032 spurcheck triggered: %d\n",n1); + } while ( (f>(f2-spectrum_to)) || (n2>-5)); + n1++; + } while (n1<5); + + return 1; +} + +static int mt2032_compute_freq(struct i2c_client *c, + unsigned int rfin, + unsigned int if1, unsigned int if2, + unsigned int spectrum_from, + unsigned int spectrum_to, + unsigned char *buf, + int *ret_sel, + unsigned int xogc) //all in Hz +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned int fref,lo1,lo1n,lo1a,s,sel,lo1freq, desired_lo1, + desired_lo2,lo2,lo2n,lo2a,lo2num,lo2freq; + + fref= 5250 *1000; //5.25MHz + desired_lo1=rfin+if1; + + lo1=(2*(desired_lo1/1000)+(fref/1000)) / (2*fref/1000); + lo1n=lo1/8; + lo1a=lo1-(lo1n*8); + + s=rfin/1000/1000+1090; + + if(optimize_vco) { + if(s>1890) sel=0; + else if(s>1720) sel=1; + else if(s>1530) sel=2; + else if(s>1370) sel=3; + else sel=4; // >1090 + } + else { + if(s>1790) sel=0; // <1958 + else if(s>1617) sel=1; + else if(s>1449) sel=2; + else if(s>1291) sel=3; + else sel=4; // >1090 + } + *ret_sel=sel; + + lo1freq=(lo1a+8*lo1n)*fref; + + tuner_dbg("mt2032: rfin=%d lo1=%d lo1n=%d lo1a=%d sel=%d, lo1freq=%d\n", + rfin,lo1,lo1n,lo1a,sel,lo1freq); + + desired_lo2=lo1freq-rfin-if2; + lo2=(desired_lo2)/fref; + lo2n=lo2/8; + lo2a=lo2-(lo2n*8); + lo2num=((desired_lo2/1000)%(fref/1000))* 3780/(fref/1000); //scale to fit in 32bit arith + lo2freq=(lo2a+8*lo2n)*fref + lo2num*(fref/1000)/3780*1000; + + tuner_dbg("mt2032: rfin=%d lo2=%d lo2n=%d lo2a=%d num=%d lo2freq=%d\n", + rfin,lo2,lo2n,lo2a,lo2num,lo2freq); + + if(lo1a<0 || lo1a>7 || lo1n<17 ||lo1n>48 || lo2a<0 ||lo2a >7 ||lo2n<17 || lo2n>30) { + tuner_info("mt2032: frequency parameters out of range: %d %d %d %d\n", + lo1a, lo1n, lo2a,lo2n); + return(-1); + } + + mt2032_spurcheck(c, lo1freq, desired_lo2, spectrum_from, spectrum_to); + // should recalculate lo1 (one step up/down) + + // set up MT2032 register map for transfer over i2c + buf[0]=lo1n-1; + buf[1]=lo1a | (sel<<4); + buf[2]=0x86; // LOGC + buf[3]=0x0f; //reserved + buf[4]=0x1f; + buf[5]=(lo2n-1) | (lo2a<<5); + if(rfin >400*1000*1000) + buf[6]=0xe4; + else + buf[6]=0xf4; // set PKEN per rev 1.2 + buf[7]=8+xogc; + buf[8]=0xc3; //reserved + buf[9]=0x4e; //reserved + buf[10]=0xec; //reserved + buf[11]=(lo2num&0xff); + buf[12]=(lo2num>>8) |0x80; // Lo2RST + + return 0; +} + +static int mt2032_check_lo_lock(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + int try,lock=0; + unsigned char buf[2]; + + for(try=0;try<10;try++) { + buf[0]=0x0e; + i2c_master_send(c,buf,1); + i2c_master_recv(c,buf,1); + tuner_dbg("mt2032 Reg.E=0x%02x\n",buf[0]); + lock=buf[0] &0x06; + + if (lock==6) + break; + + tuner_dbg("mt2032: pll wait 1ms for lock (0x%2x)\n",buf[0]); + udelay(1000); + } + return lock; +} + +static int mt2032_optimize_vco(struct i2c_client *c,int sel,int lock) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned char buf[2]; + int tad1; + + buf[0]=0x0f; + i2c_master_send(c,buf,1); + i2c_master_recv(c,buf,1); + tuner_dbg("mt2032 Reg.F=0x%02x\n",buf[0]); + tad1=buf[0]&0x07; + + if(tad1 ==0) return lock; + if(tad1 ==1) return lock; + + if(tad1==2) { + if(sel==0) + return lock; + else sel--; + } + else { + if(sel<4) + sel++; + else + return lock; + } + + tuner_dbg("mt2032 optimize_vco: sel=%d\n",sel); + + buf[0]=0x0f; + buf[1]=sel; + i2c_master_send(c,buf,2); + lock=mt2032_check_lo_lock(c); + return lock; +} + + +static void mt2032_set_if_freq(struct i2c_client *c, unsigned int rfin, + unsigned int if1, unsigned int if2, + unsigned int from, unsigned int to) +{ + unsigned char buf[21]; + int lint_try,ret,sel,lock=0; + struct tuner *t = i2c_get_clientdata(c); + + tuner_dbg("mt2032_set_if_freq rfin=%d if1=%d if2=%d from=%d to=%d\n", + rfin,if1,if2,from,to); + + buf[0]=0; + ret=i2c_master_send(c,buf,1); + i2c_master_recv(c,buf,21); + + buf[0]=0; + ret=mt2032_compute_freq(c,rfin,if1,if2,from,to,&buf[1],&sel,t->xogc); + if (ret<0) + return; + + // send only the relevant registers per Rev. 1.2 + buf[0]=0; + ret=i2c_master_send(c,buf,4); + buf[5]=5; + ret=i2c_master_send(c,buf+5,4); + buf[11]=11; + ret=i2c_master_send(c,buf+11,3); + if(ret!=3) + tuner_warn("i2c i/o error: rc == %d (should be 3)\n",ret); + + // wait for PLLs to lock (per manual), retry LINT if not. + for(lint_try=0; lint_try<2; lint_try++) { + lock=mt2032_check_lo_lock(c); + + if(optimize_vco) + lock=mt2032_optimize_vco(c,sel,lock); + if(lock==6) break; + + tuner_dbg("mt2032: re-init PLLs by LINT\n"); + buf[0]=7; + buf[1]=0x80 +8+t->xogc; // set LINT to re-init PLLs + i2c_master_send(c,buf,2); + mdelay(10); + buf[1]=8+t->xogc; + i2c_master_send(c,buf,2); + } + + if (lock!=6) + tuner_warn("MT2032 Fatal Error: PLLs didn't lock.\n"); + + buf[0]=2; + buf[1]=0x20; // LOGC for optimal phase noise + ret=i2c_master_send(c,buf,2); + if (ret!=2) + tuner_warn("i2c i/o error: rc == %d (should be 2)\n",ret); +} + + +static void mt2032_set_tv_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + int if2,from,to; + + // signal bandwidth and picture carrier + if (t->std & V4L2_STD_525_60) { + // NTSC + from = 40750*1000; + to = 46750*1000; + if2 = 45750*1000; + } else { + // PAL + from = 32900*1000; + to = 39900*1000; + if2 = 38900*1000; + } + + mt2032_set_if_freq(c, freq*62500 /* freq*1000*1000/16 */, + 1090*1000*1000, if2, from, to); +} + +static void mt2032_set_radio_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + int if2 = t->radio_if2; + + // per Manual for FM tuning: first if center freq. 1085 MHz + mt2032_set_if_freq(c, freq*62500 /* freq*1000*1000/16 */, + 1085*1000*1000,if2,if2,if2); +} + +// Initalization as described in "MT203x Programming Procedures", Rev 1.2, Feb.2001 +static int mt2032_init(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned char buf[21]; + int ret,xogc,xok=0; + + // Initialize Registers per spec. + buf[1]=2; // Index to register 2 + buf[2]=0xff; + buf[3]=0x0f; + buf[4]=0x1f; + ret=i2c_master_send(c,buf+1,4); + + buf[5]=6; // Index register 6 + buf[6]=0xe4; + buf[7]=0x8f; + buf[8]=0xc3; + buf[9]=0x4e; + buf[10]=0xec; + ret=i2c_master_send(c,buf+5,6); + + buf[12]=13; // Index register 13 + buf[13]=0x32; + ret=i2c_master_send(c,buf+12,2); + + // Adjust XOGC (register 7), wait for XOK + xogc=7; + do { + tuner_dbg("mt2032: xogc = 0x%02x\n",xogc&0x07); + mdelay(10); + buf[0]=0x0e; + i2c_master_send(c,buf,1); + i2c_master_recv(c,buf,1); + xok=buf[0]&0x01; + tuner_dbg("mt2032: xok = 0x%02x\n",xok); + if (xok == 1) break; + + xogc--; + tuner_dbg("mt2032: xogc = 0x%02x\n",xogc&0x07); + if (xogc == 3) { + xogc=4; // min. 4 per spec + break; + } + buf[0]=0x07; + buf[1]=0x88 + xogc; + ret=i2c_master_send(c,buf,2); + if (ret!=2) + tuner_warn("i2c i/o error: rc == %d (should be 2)\n",ret); + } while (xok != 1 ); + t->xogc=xogc; + + t->tv_freq = mt2032_set_tv_freq; + t->radio_freq = mt2032_set_radio_freq; + return(1); +} + +static void mt2050_set_antenna(struct i2c_client *c, unsigned char antenna) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned char buf[2]; + int ret; + + buf[0] = 6; + buf[1] = antenna ? 0x11 : 0x10; + ret=i2c_master_send(c,buf,2); + tuner_dbg("mt2050: enabled antenna connector %d\n", antenna); +} + +static void mt2050_set_if_freq(struct i2c_client *c,unsigned int freq, unsigned int if2) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned int if1=1218*1000*1000; + unsigned int f_lo1,f_lo2,lo1,lo2,f_lo1_modulo,f_lo2_modulo,num1,num2,div1a,div1b,div2a,div2b; + int ret; + unsigned char buf[6]; + + tuner_dbg("mt2050_set_if_freq freq=%d if1=%d if2=%d\n", + freq,if1,if2); + + f_lo1=freq+if1; + f_lo1=(f_lo1/1000000)*1000000; + + f_lo2=f_lo1-freq-if2; + f_lo2=(f_lo2/50000)*50000; + + lo1=f_lo1/4000000; + lo2=f_lo2/4000000; + + f_lo1_modulo= f_lo1-(lo1*4000000); + f_lo2_modulo= f_lo2-(lo2*4000000); + + num1=4*f_lo1_modulo/4000000; + num2=4096*(f_lo2_modulo/1000)/4000; + + // todo spurchecks + + div1a=(lo1/12)-1; + div1b=lo1-(div1a+1)*12; + + div2a=(lo2/8)-1; + div2b=lo2-(div2a+1)*8; + + if (tuner_debug > 1) { + tuner_dbg("lo1 lo2 = %d %d\n", lo1, lo2); + tuner_dbg("num1 num2 div1a div1b div2a div2b= %x %x %x %x %x %x\n", + num1,num2,div1a,div1b,div2a,div2b); + } + + buf[0]=1; + buf[1]= 4*div1b + num1; + if(freq<275*1000*1000) buf[1] = buf[1]|0x80; + + buf[2]=div1a; + buf[3]=32*div2b + num2/256; + buf[4]=num2-(num2/256)*256; + buf[5]=div2a; + if(num2!=0) buf[5]=buf[5]|0x40; + + if (tuner_debug > 1) { + int i; + tuner_dbg("bufs is: "); + for(i=0;i<6;i++) + printk("%x ",buf[i]); + printk("\n"); + } + + ret=i2c_master_send(c,buf,6); + if (ret!=6) + tuner_warn("i2c i/o error: rc == %d (should be 6)\n",ret); +} + +static void mt2050_set_tv_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned int if2; + + if (t->std & V4L2_STD_525_60) { + // NTSC + if2 = 45750*1000; + } else { + // PAL + if2 = 38900*1000; + } + if (V4L2_TUNER_DIGITAL_TV == t->mode) { + // DVB (pinnacle 300i) + if2 = 36150*1000; + } + mt2050_set_if_freq(c, freq*62500, if2); + mt2050_set_antenna(c, tv_antenna); +} + +static void mt2050_set_radio_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + int if2 = t->radio_if2; + + mt2050_set_if_freq(c, freq*62500, if2); + mt2050_set_antenna(c, radio_antenna); +} + +static int mt2050_init(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + unsigned char buf[2]; + int ret; + + buf[0]=6; + buf[1]=0x10; + ret=i2c_master_send(c,buf,2); // power + + buf[0]=0x0f; + buf[1]=0x0f; + ret=i2c_master_send(c,buf,2); // m1lo + + buf[0]=0x0d; + ret=i2c_master_send(c,buf,1); + i2c_master_recv(c,buf,1); + + tuner_dbg("mt2050: sro is %x\n",buf[0]); + t->tv_freq = mt2050_set_tv_freq; + t->radio_freq = mt2050_set_radio_freq; + return 0; +} + +int microtune_init(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + char *name; + unsigned char buf[21]; + int company_code; + + memset(buf,0,sizeof(buf)); + t->tv_freq = NULL; + t->radio_freq = NULL; + name = "unknown"; + + i2c_master_send(c,buf,1); + i2c_master_recv(c,buf,21); + if (tuner_debug) { + int i; + tuner_dbg("MT20xx hexdump:"); + for(i=0;i<21;i++) { + printk(" %02x",buf[i]); + if(((i+1)%8)==0) printk(" "); + } + printk("\n"); + } + company_code = buf[0x11] << 8 | buf[0x12]; + tuner_info("microtune: companycode=%04x part=%02x rev=%02x\n", + company_code,buf[0x13],buf[0x14]); + +#if 0 + /* seems to cause more problems than it solves ... */ + switch (company_code) { + case 0x30bf: + case 0x3cbf: + case 0x3dbf: + case 0x4d54: + case 0x8e81: + case 0x8e91: + /* ok (?) */ + break; + default: + tuner_warn("tuner: microtune: unknown companycode\n"); + return 0; + } +#endif + + if (buf[0x13] < ARRAY_SIZE(microtune_part) && + NULL != microtune_part[buf[0x13]]) + name = microtune_part[buf[0x13]]; + switch (buf[0x13]) { + case MT2032: + mt2032_init(c); + break; + case MT2050: + mt2050_init(c); + break; + default: + tuner_info("microtune %s found, not (yet?) supported, sorry :-/\n", + name); + return 0; + } + + strlcpy(c->name, name, sizeof(c->name)); + tuner_info("microtune %s found, OK\n",name); + return 0; +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/mxb.c b/drivers/media/video/mxb.c new file mode 100644 index 000000000000..70bf1f1fad59 --- /dev/null +++ b/drivers/media/video/mxb.c @@ -0,0 +1,1035 @@ +/* + mxb - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + Visit http://www.mihu.de/linux/saa7146/mxb/ + for further details about this card. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> +#include <media/tuner.h> +#include <linux/video_decoder.h> + +#include "mxb.h" +#include "tea6415c.h" +#include "tea6420.h" +#include "tda9840.h" + +#define I2C_SAA7111 0x24 + +#define MXB_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +/* global variable */ +static int mxb_num = 0; + +/* initial frequence the tuner will be tuned to. + in verden (lower saxony, germany) 4148 is a + channel called "phoenix" */ +static int freq = 4148; +module_param(freq, int, 0644); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); + +#define MXB_INPUTS 4 +enum { TUNER, AUX1, AUX3, AUX3_YC }; + +static struct v4l2_input mxb_inputs[MXB_INPUTS] = { + { TUNER, "Tuner", V4L2_INPUT_TYPE_TUNER, 1, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { AUX1, "AUX1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { AUX3, "AUX3 Composite", V4L2_INPUT_TYPE_CAMERA, 4, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { AUX3_YC, "AUX3 S-Video", V4L2_INPUT_TYPE_CAMERA, 4, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +/* this array holds the information, which port of the saa7146 each + input actually uses. the mxb uses port 0 for every input */ +static struct { + int hps_source; + int hps_sync; +} input_port_selection[MXB_INPUTS] = { + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, +}; + +/* this array holds the information of the audio source (mxb_audios), + which has to be switched corresponding to the video source (mxb_channels) */ +static int video_audio_connect[MXB_INPUTS] = + { 0, 1, 3, 3 }; + +/* these are the necessary input-output-pins for bringing one audio source +(see above) to the CD-output */ +static struct tea6420_multiplex TEA6420_cd[MXB_AUDIOS+1][2] = + { + {{1,1,0},{1,1,0}}, /* Tuner */ + {{5,1,0},{6,1,0}}, /* AUX 1 */ + {{4,1,0},{6,1,0}}, /* AUX 2 */ + {{3,1,0},{6,1,0}}, /* AUX 3 */ + {{1,1,0},{3,1,0}}, /* Radio */ + {{1,1,0},{2,1,0}}, /* CD-Rom */ + {{6,1,0},{6,1,0}} /* Mute */ + }; + +/* these are the necessary input-output-pins for bringing one audio source +(see above) to the line-output */ +static struct tea6420_multiplex TEA6420_line[MXB_AUDIOS+1][2] = + { + {{2,3,0},{1,2,0}}, + {{5,3,0},{6,2,0}}, + {{4,3,0},{6,2,0}}, + {{3,3,0},{6,2,0}}, + {{2,3,0},{3,2,0}}, + {{2,3,0},{2,2,0}}, + {{6,3,0},{6,2,0}} /* Mute */ + }; + +#define MAXCONTROLS 1 +static struct v4l2_queryctrl mxb_controls[] = { + { V4L2_CID_AUDIO_MUTE, V4L2_CTRL_TYPE_BOOLEAN, "Mute", 0, 1, 1, 0, 0 }, +}; + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_QUERYCTRL, SAA7146_BEFORE }, + { VIDIOC_G_CTRL, SAA7146_BEFORE }, + { VIDIOC_S_CTRL, SAA7146_BEFORE }, + { VIDIOC_G_TUNER, SAA7146_EXCLUSIVE }, + { VIDIOC_S_TUNER, SAA7146_EXCLUSIVE }, + { VIDIOC_G_FREQUENCY, SAA7146_EXCLUSIVE }, + { VIDIOC_S_FREQUENCY, SAA7146_EXCLUSIVE }, + { VIDIOC_G_AUDIO, SAA7146_EXCLUSIVE }, + { VIDIOC_S_AUDIO, SAA7146_EXCLUSIVE }, + { MXB_S_AUDIO_CD, SAA7146_EXCLUSIVE }, /* custom control */ + { MXB_S_AUDIO_LINE, SAA7146_EXCLUSIVE }, /* custom control */ + { 0, 0 } +}; + +struct mxb +{ + struct video_device *video_dev; + struct video_device *vbi_dev; + + struct i2c_adapter i2c_adapter; + + struct i2c_client* saa7111a; + struct i2c_client* tda9840; + struct i2c_client* tea6415c; + struct i2c_client* tuner; + struct i2c_client* tea6420_1; + struct i2c_client* tea6420_2; + + int cur_mode; /* current audio mode (mono, stereo, ...) */ + int cur_input; /* current input */ + int cur_freq; /* current frequency the tuner is tuned to */ + int cur_mute; /* current mute status */ +}; + +static struct saa7146_extension extension; + +static int mxb_probe(struct saa7146_dev* dev) +{ + struct mxb* mxb = NULL; + struct i2c_client *client; + struct list_head *item; + int result; + + if ((result = request_module("saa7111")) < 0) { + printk("mxb: saa7111 i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tuner")) < 0) { + printk("mxb: tuner i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tea6420")) < 0) { + printk("mxb: tea6420 i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tea6415c")) < 0) { + printk("mxb: tea6415c i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tda9840")) < 0) { + printk("mxb: tda9840 i2c module not available.\n"); + return -ENODEV; + } + + mxb = (struct mxb*)kmalloc(sizeof(struct mxb), GFP_KERNEL); + if( NULL == mxb ) { + DEB_D(("not enough kernel memory.\n")); + return -ENOMEM; + } + memset(mxb, 0x0, sizeof(struct mxb)); + + mxb->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "mxb", + }; + + saa7146_i2c_adapter_prepare(dev, &mxb->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if(i2c_add_adapter(&mxb->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(mxb); + return -EFAULT; + } + + /* loop through all i2c-devices on the bus and look who is there */ + list_for_each(item,&mxb->i2c_adapter.clients) { + client = list_entry(item, struct i2c_client, list); + if( I2C_TEA6420_1 == client->addr ) + mxb->tea6420_1 = client; + if( I2C_TEA6420_2 == client->addr ) + mxb->tea6420_2 = client; + if( I2C_TEA6415C_2 == client->addr ) + mxb->tea6415c = client; + if( I2C_TDA9840 == client->addr ) + mxb->tda9840 = client; + if( I2C_SAA7111 == client->addr ) + mxb->saa7111a = client; + if( 0x60 == client->addr ) + mxb->tuner = client; + } + + /* check if all devices are present */ + if( 0 == mxb->tea6420_1 || 0 == mxb->tea6420_2 || 0 == mxb->tea6415c + || 0 == mxb->tda9840 || 0 == mxb->saa7111a || 0 == mxb->tuner ) { + + printk("mxb: did not find all i2c devices. aborting\n"); + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + + /* we store the pointer in our private data field */ + dev->ext_priv = mxb; + + return 0; +} + +/* some init data for the saa7740, the so-called 'sound arena module'. + there are no specs available, so we simply use some init values */ +static struct { + int length; + char data[9]; +} mxb_saa7740_init[] = { + { 3, { 0x80, 0x00, 0x00 } },{ 3, { 0x80, 0x89, 0x00 } }, + { 3, { 0x80, 0xb0, 0x0a } },{ 3, { 0x00, 0x00, 0x00 } }, + { 3, { 0x49, 0x00, 0x00 } },{ 3, { 0x4a, 0x00, 0x00 } }, + { 3, { 0x4b, 0x00, 0x00 } },{ 3, { 0x4c, 0x00, 0x00 } }, + { 3, { 0x4d, 0x00, 0x00 } },{ 3, { 0x4e, 0x00, 0x00 } }, + { 3, { 0x4f, 0x00, 0x00 } },{ 3, { 0x50, 0x00, 0x00 } }, + { 3, { 0x51, 0x00, 0x00 } },{ 3, { 0x52, 0x00, 0x00 } }, + { 3, { 0x53, 0x00, 0x00 } },{ 3, { 0x54, 0x00, 0x00 } }, + { 3, { 0x55, 0x00, 0x00 } },{ 3, { 0x56, 0x00, 0x00 } }, + { 3, { 0x57, 0x00, 0x00 } },{ 3, { 0x58, 0x00, 0x00 } }, + { 3, { 0x59, 0x00, 0x00 } },{ 3, { 0x5a, 0x00, 0x00 } }, + { 3, { 0x5b, 0x00, 0x00 } },{ 3, { 0x5c, 0x00, 0x00 } }, + { 3, { 0x5d, 0x00, 0x00 } },{ 3, { 0x5e, 0x00, 0x00 } }, + { 3, { 0x5f, 0x00, 0x00 } },{ 3, { 0x60, 0x00, 0x00 } }, + { 3, { 0x61, 0x00, 0x00 } },{ 3, { 0x62, 0x00, 0x00 } }, + { 3, { 0x63, 0x00, 0x00 } },{ 3, { 0x64, 0x00, 0x00 } }, + { 3, { 0x65, 0x00, 0x00 } },{ 3, { 0x66, 0x00, 0x00 } }, + { 3, { 0x67, 0x00, 0x00 } },{ 3, { 0x68, 0x00, 0x00 } }, + { 3, { 0x69, 0x00, 0x00 } },{ 3, { 0x6a, 0x00, 0x00 } }, + { 3, { 0x6b, 0x00, 0x00 } },{ 3, { 0x6c, 0x00, 0x00 } }, + { 3, { 0x6d, 0x00, 0x00 } },{ 3, { 0x6e, 0x00, 0x00 } }, + { 3, { 0x6f, 0x00, 0x00 } },{ 3, { 0x70, 0x00, 0x00 } }, + { 3, { 0x71, 0x00, 0x00 } },{ 3, { 0x72, 0x00, 0x00 } }, + { 3, { 0x73, 0x00, 0x00 } },{ 3, { 0x74, 0x00, 0x00 } }, + { 3, { 0x75, 0x00, 0x00 } },{ 3, { 0x76, 0x00, 0x00 } }, + { 3, { 0x77, 0x00, 0x00 } },{ 3, { 0x41, 0x00, 0x42 } }, + { 3, { 0x42, 0x10, 0x42 } },{ 3, { 0x43, 0x20, 0x42 } }, + { 3, { 0x44, 0x30, 0x42 } },{ 3, { 0x45, 0x00, 0x01 } }, + { 3, { 0x46, 0x00, 0x01 } },{ 3, { 0x47, 0x00, 0x01 } }, + { 3, { 0x48, 0x00, 0x01 } }, + { 9, { 0x01, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x21, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x09, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x29, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x11, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x31, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x19, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x39, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x05, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x25, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x0d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x2d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x15, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x35, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x1d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 9, { 0x3d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 3, { 0x80, 0xb3, 0x0a } }, + {-1, { 0} } +}; + +static const unsigned char mxb_saa7111_init[] = { + 0x00, 0x00, /* 00 - ID byte */ + 0x01, 0x00, /* 01 - reserved */ + + /*front end */ + 0x02, 0xd8, /* 02 - FUSE=x, GUDL=x, MODE=x */ + 0x03, 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x04, 0x00, /* 04 - GAI1=256 */ + 0x05, 0x00, /* 05 - GAI2=256 */ + + /* decoder */ + 0x06, 0xf0, /* 06 - HSB at xx(50Hz) / xx(60Hz) pixels after end of last line */ + 0x07, 0x30, /* 07 - HSS at xx(50Hz) / xx(60Hz) pixels after end of last line */ + 0x08, 0xa8, /* 08 - AUFD=x, FSEL=x, EXFIL=x, VTRC=x, HPLL=x, VNOI=x */ + 0x09, 0x02, /* 09 - BYPS=x, PREF=x, BPSS=x, VBLB=x, UPTCV=x, APER=x */ + 0x0a, 0x80, /* 0a - BRIG=128 */ + 0x0b, 0x47, /* 0b - CONT=1.109 */ + 0x0c, 0x40, /* 0c - SATN=1.0 */ + 0x0d, 0x00, /* 0d - HUE=0 */ + 0x0e, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ + 0x0f, 0x00, /* 0f - reserved */ + 0x10, 0xd0, /* 10 - OFTS=x, HDEL=x, VRLN=x, YDEL=x */ + 0x11, 0x8c, /* 11 - GPSW=x, CM99=x, FECO=x, COMPO=x, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + 0x12, 0x80, /* 12 - xx output control 2 */ + 0x13, 0x30, /* 13 - xx output control 3 */ + 0x14, 0x00, /* 14 - reserved */ + 0x15, 0x15, /* 15 - VBI */ + 0x16, 0x04, /* 16 - VBI */ + 0x17, 0x00, /* 17 - VBI */ +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int mxb_init_done(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct video_decoder_init init; + struct i2c_msg msg; + + int i = 0, err = 0; + struct tea6415c_multiplex vm; + + /* select video mode in saa7111a */ + i = VIDEO_MODE_PAL; + /* fixme: currently pointless: gets overwritten by configuration below */ + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_NORM, &i); + + /* write configuration to saa7111a */ + init.data = mxb_saa7111_init; + init.len = sizeof(mxb_saa7111_init); + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_INIT, &init); + + /* select tuner-output on saa7111a */ + i = 0; + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i); + + /* enable vbi bypass */ + i = 1; + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_VBI_BYPASS, &i); + + /* select a tuner type */ + i = 5; + mxb->tuner->driver->command(mxb->tuner,TUNER_SET_TYPE, &i); + + /* mute audio on tea6420s */ + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[6][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[6][1]); + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_cd[6][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_cd[6][1]); + + /* switch to tuner-channel on tea6415c*/ + vm.out = 17; + vm.in = 3; + mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm); + + /* select tuner-output on multicable on tea6415c*/ + vm.in = 3; + vm.out = 13; + mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm); + + /* tune in some frequency on tuner */ + mxb->tuner->driver->command(mxb->tuner, VIDIOCSFREQ, &freq); + + /* the rest for mxb */ + mxb->cur_input = 0; + mxb->cur_freq = freq; + mxb->cur_mute = 1; + + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + mxb->tda9840->driver->command(mxb->tda9840, TDA9840_SWITCH, &mxb->cur_mode); + + /* check if the saa7740 (aka 'sound arena module') is present + on the mxb. if so, we must initialize it. due to lack of + informations about the saa7740, the values were reverse + engineered. */ + msg.addr = 0x1b; + msg.flags = 0; + msg.len = mxb_saa7740_init[0].length; + msg.buf = &mxb_saa7740_init[0].data[0]; + + if( 1 == (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { + /* the sound arena module is a pos, that's probably the reason + philips refuses to hand out a datasheet for the saa7740... + it seems to screw up the i2c bus, so we disable fast irq + based i2c transactions here and rely on the slow and safe + polling method ... */ + extension.flags &= ~SAA7146_USE_I2C_IRQ; + for(i = 1;;i++) { + if( -1 == mxb_saa7740_init[i].length ) { + break; + } + + msg.len = mxb_saa7740_init[i].length; + msg.buf = &mxb_saa7740_init[i].data[0]; + if( 1 != (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { + DEB_D(("failed to initialize 'sound arena module'.\n")); + goto err; + } + } + INFO(("'sound arena module' detected.\n")); + } +err: + /* the rest for saa7146: you should definitely set some basic values + for the input-port handling of the saa7146. */ + + /* ext->saa has been filled by the core driver */ + + /* some stuff is done via variables */ + saa7146_set_hps_source_and_sync(dev, input_port_selection[mxb->cur_input].hps_source, input_port_selection[mxb->cur_input].hps_sync); + + /* some stuff is done via direct write to the registers */ + + /* this is ugly, but because of the fact that this is completely + hardware dependend, it should be done directly... */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x02000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +/* interrupt-handler. this gets called when irq_mask is != 0. + it must clear the interrupt-bits in irq_mask it has handled */ +/* +void mxb_irq_bh(struct saa7146_dev* dev, u32* irq_mask) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; +} +*/ + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int mxb_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + + DEB_EE(("dev:%p\n",dev)); + + /* checking for i2c-devices can be omitted here, because we + already did this in "mxb_vl42_probe" */ + + saa7146_vv_init(dev,&vv_data); + if( 0 != saa7146_register_device(&mxb->video_dev, dev, "mxb", VFL_TYPE_GRABBER)) { + ERR(("cannot register capture v4l2 device. skipping.\n")); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if( 0 != MXB_BOARD_CAN_DO_VBI(dev)) { + if( 0 != saa7146_register_device(&mxb->vbi_dev, dev, "mxb", VFL_TYPE_VBI)) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + } + } + + i2c_use_client(mxb->tea6420_1); + i2c_use_client(mxb->tea6420_2); + i2c_use_client(mxb->tea6415c); + i2c_use_client(mxb->tda9840); + i2c_use_client(mxb->saa7111a); + i2c_use_client(mxb->tuner); + + printk("mxb: found 'Multimedia eXtension Board'-%d.\n",mxb_num); + + mxb_num++; + mxb_init_done(dev); + return 0; +} + +static int mxb_detach(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + + DEB_EE(("dev:%p\n",dev)); + + i2c_release_client(mxb->tea6420_1); + i2c_release_client(mxb->tea6420_2); + i2c_release_client(mxb->tea6415c); + i2c_release_client(mxb->tda9840); + i2c_release_client(mxb->saa7111a); + i2c_release_client(mxb->tuner); + + saa7146_unregister_device(&mxb->video_dev,dev); + if( 0 != MXB_BOARD_CAN_DO_VBI(dev)) { + saa7146_unregister_device(&mxb->vbi_dev,dev); + } + saa7146_vv_release(dev); + + mxb_num--; + + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + + return 0; +} + +static int mxb_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct saa7146_vv *vv = dev->vv_data; + + switch(cmd) { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + DEB_EE(("VIDIOC_ENUMINPUT %d.\n",i->index)); + if( i->index < 0 || i->index >= MXB_INPUTS) { + return -EINVAL; + } + memcpy(i, &mxb_inputs[i->index], sizeof(struct v4l2_input)); + + return 0; + } + /* the saa7146 provides some controls (brightness, contrast, saturation) + which gets registered *after* this function. because of this we have + to return with a value != 0 even if the function succeded.. */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *qc = arg; + int i; + + for (i = MAXCONTROLS - 1; i >= 0; i--) { + if (mxb_controls[i].id == qc->id) { + *qc = mxb_controls[i]; + DEB_D(("VIDIOC_QUERYCTRL %d.\n",qc->id)); + return 0; + } + } + return -EAGAIN; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *vc = arg; + int i; + + for (i = MAXCONTROLS - 1; i >= 0; i--) { + if (mxb_controls[i].id == vc->id) { + break; + } + } + + if( i < 0 ) { + return -EAGAIN; + } + + switch (vc->id ) { + case V4L2_CID_AUDIO_MUTE: { + vc->value = mxb->cur_mute; + DEB_D(("VIDIOC_G_CTRL V4L2_CID_AUDIO_MUTE:%d.\n",vc->value)); + return 0; + } + } + + DEB_EE(("VIDIOC_G_CTRL V4L2_CID_AUDIO_MUTE:%d.\n",vc->value)); + return 0; + } + + case VIDIOC_S_CTRL: + { + struct v4l2_control *vc = arg; + int i = 0; + + for (i = MAXCONTROLS - 1; i >= 0; i--) { + if (mxb_controls[i].id == vc->id) { + break; + } + } + + if( i < 0 ) { + return -EAGAIN; + } + + switch (vc->id ) { + case V4L2_CID_AUDIO_MUTE: { + mxb->cur_mute = vc->value; + if( 0 == vc->value ) { + /* switch the audio-source */ + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[mxb->cur_input]][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[mxb->cur_input]][1]); + } else { + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[6][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[6][1]); + } + DEB_EE(("VIDIOC_S_CTRL, V4L2_CID_AUDIO_MUTE: %d.\n",vc->value)); + break; + } + } + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *)arg; + *input = mxb->cur_input; + + DEB_EE(("VIDIOC_G_INPUT %d.\n",*input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *)arg; + struct tea6415c_multiplex vm; + int i = 0; + + DEB_EE(("VIDIOC_S_INPUT %d.\n",input)); + + if (input < 0 || input >= MXB_INPUTS) { + return -EINVAL; + } + + /* fixme: locke das setzen des inputs mit hilfe des mutexes + down(&dev->lock); + video_mux(dev,*i); + up(&dev->lock); + */ + + /* fixme: check if streaming capture + if ( 0 != dev->streaming ) { + DEB_D(("VIDIOC_S_INPUT illegal while streaming.\n")); + return -EPERM; + } + */ + + mxb->cur_input = input; + + saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, input_port_selection[input].hps_sync); + + /* prepare switching of tea6415c and saa7111a; + have a look at the 'background'-file for further informations */ + switch( input ) { + + case TUNER: + { + i = 0; + vm.in = 3; + vm.out = 17; + + if ( 0 != mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm)) { + printk("VIDIOC_S_INPUT: could not address tea6415c #1\n"); + return -EFAULT; + } + /* connect tuner-output always to multicable */ + vm.in = 3; + vm.out = 13; + break; + } + case AUX3_YC: + { + /* nothing to be done here. aux3_yc is + directly connected to the saa711a */ + i = 5; + break; + } + case AUX3: + { + /* nothing to be done here. aux3 is + directly connected to the saa711a */ + i = 1; + break; + } + case AUX1: + { + i = 0; + vm.in = 1; + vm.out = 17; + break; + } + } + + /* switch video in tea6415c only if necessary */ + switch( input ) { + case TUNER: + case AUX1: + { + if ( 0 != mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm)) { + printk("VIDIOC_S_INPUT: could not address tea6415c #3\n"); + return -EFAULT; + } + break; + } + default: + { + break; + } + } + + /* switch video in saa7111a */ + if ( 0 != mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i)) { + printk("VIDIOC_S_INPUT: could not address saa7111a #1.\n"); + } + + /* switch the audio-source only if necessary */ + if( 0 == mxb->cur_mute ) { + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[input]][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[input]][1]); + } + + return 0; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + int byte = 0; + + if( 0 != t->index ) { + DEB_D(("VIDIOC_G_TUNER: channel %d does not have a tuner attached.\n", t->index)); + return -EINVAL; + } + + DEB_EE(("VIDIOC_G_TUNER: %d\n", t->index)); + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Television"); + + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */ + t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */ + /* FIXME: add the real signal strength here */ + t->signal = 0xffff; + t->afc = 0; + + mxb->tda9840->driver->command(mxb->tda9840,TDA9840_DETECT, &byte); + t->audmode = mxb->cur_mode; + + if( byte < 0 ) { + t->rxsubchans = V4L2_TUNER_SUB_MONO; + } else { + switch(byte) { + case TDA9840_MONO_DETECT: { + t->rxsubchans = V4L2_TUNER_SUB_MONO; + DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_MONO.\n")); + break; + } + case TDA9840_DUAL_DETECT: { + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_LANG1.\n")); + break; + } + case TDA9840_STEREO_DETECT: { + t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_STEREO.\n")); + break; + } + default: { /* TDA9840_INCORRECT_DETECT */ + t->rxsubchans = V4L2_TUNER_MODE_MONO; + DEB_D(("VIDIOC_G_TUNER: TDA9840_INCORRECT_DETECT => V4L2_TUNER_MODE_MONO\n")); + break; + } + } + } + + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + int result = 0; + int byte = 0; + + if( 0 != t->index ) { + DEB_D(("VIDIOC_S_TUNER: channel %d does not have a tuner attached.\n",t->index)); + return -EINVAL; + } + + switch(t->audmode) { + case V4L2_TUNER_MODE_STEREO: { + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + byte = TDA9840_SET_STEREO; + DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n")); + break; + } + case V4L2_TUNER_MODE_LANG1: { + mxb->cur_mode = V4L2_TUNER_MODE_LANG1; + byte = TDA9840_SET_LANG1; + DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n")); + break; + } + case V4L2_TUNER_MODE_LANG2: { + mxb->cur_mode = V4L2_TUNER_MODE_LANG2; + byte = TDA9840_SET_LANG2; + DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n")); + break; + } + default: { /* case V4L2_TUNER_MODE_MONO: {*/ + mxb->cur_mode = V4L2_TUNER_MODE_MONO; + byte = TDA9840_SET_MONO; + DEB_D(("VIDIOC_S_TUNER: TDA9840_SET_MONO\n")); + break; + } + } + + if( 0 != (result = mxb->tda9840->driver->command(mxb->tda9840, TDA9840_SWITCH, &byte))) { + printk("VIDIOC_S_TUNER error. result:%d, byte:%d\n",result,byte); + } + + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if(0 != mxb->cur_input) { + DEB_D(("VIDIOC_G_FREQ: channel %d does not have a tuner!\n",mxb->cur_input)); + return -EINVAL; + } + + memset(f,0,sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = mxb->cur_freq; + + DEB_EE(("VIDIOC_G_FREQ: freq:0x%08x.\n", mxb->cur_freq)); + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + int t_locked = 0; + int v_byte = 0; + + if (0 != f->tuner) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + if(0 != mxb->cur_input) { + DEB_D(("VIDIOC_S_FREQ: channel %d does not have a tuner!\n",mxb->cur_input)); + return -EINVAL; + } + + DEB_EE(("VIDIOC_S_FREQUENCY: freq:0x%08x.\n",f->frequency)); + + mxb->cur_freq = f->frequency; + + /* tune in desired frequency */ + mxb->tuner->driver->command(mxb->tuner, VIDIOCSFREQ, &mxb->cur_freq); + + /* check if pll of tuner & saa7111a is locked */ +// mxb->tuner->driver->command(mxb->tuner,TUNER_IS_LOCKED, &t_locked); + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_GET_STATUS, &v_byte); + + /* not locked -- anything to do here ? */ + if( 0 == t_locked || 0 == (v_byte & DECODER_STATUS_GOOD)) { + } + + /* hack: changing the frequency should invalidate the vbi-counter (=> alevt) */ + spin_lock(&dev->slock); + vv->vbi_fieldcount = 0; + spin_unlock(&dev->slock); + + return 0; + } + case MXB_S_AUDIO_CD: + { + int i = *(int*)arg; + + if( i < 0 || i >= MXB_AUDIOS ) { + DEB_D(("illegal argument to MXB_S_AUDIO_CD: i:%d.\n",i)); + return -EINVAL; + } + + DEB_EE(("MXB_S_AUDIO_CD: i:%d.\n",i)); + + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_cd[i][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_cd[i][1]); + + return 0; + } + case MXB_S_AUDIO_LINE: + { + int i = *(int*)arg; + + if( i < 0 || i >= MXB_AUDIOS ) { + DEB_D(("illegal argument to MXB_S_AUDIO_LINE: i:%d.\n",i)); + return -EINVAL; + } + + DEB_EE(("MXB_S_AUDIO_LINE: i:%d.\n",i)); + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[i][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[i][1]); + + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + if( a->index < 0 || a->index > MXB_INPUTS ) { + DEB_D(("VIDIOC_G_AUDIO %d out of range.\n",a->index)); + return -EINVAL; + } + + DEB_EE(("VIDIOC_G_AUDIO %d.\n",a->index)); + memcpy(a, &mxb_audios[video_audio_connect[mxb->cur_input]], sizeof(struct v4l2_audio)); + + return 0; + } + case VIDIOC_S_AUDIO: + { + struct v4l2_audio *a = arg; + DEB_D(("VIDIOC_S_AUDIO %d.\n",a->index)); + return 0; + } + default: +/* + DEB2(printk("does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + int zero = 0; + int one = 1; + + if(V4L2_STD_PAL_I == std->id ) { + DEB_D(("VIDIOC_S_STD: setting mxb for PAL_I.\n")); + /* set the 7146 gpio register -- I don't know what this does exactly */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + /* unset the 7111 gpio register -- I don't know what this does exactly */ + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_GPIO, &zero); + } else { + DEB_D(("VIDIOC_S_STD: setting mxb for PAL/NTSC/SECAM.\n")); + /* set the 7146 gpio register -- I don't know what this does exactly */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + /* set the 7111 gpio register -- I don't know what this does exactly */ + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_GPIO, &one); + } + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL-BG", .id = V4L2_STD_PAL_BG, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "PAL-I", .id = V4L2_STD_PAL_I, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_pci_extension_data mxb = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long)&mxb, + }, { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = MXB_INPUTS, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE, + .stds = &standard[0], + .num_stds = sizeof(standard)/sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = mxb_ioctl, +}; + +static struct saa7146_extension extension = { + .name = MXB_IDENTIFIER, + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = mxb_probe, + .attach = mxb_attach, + .detach = mxb_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init mxb_init_module(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit mxb_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(mxb_init_module); +module_exit(mxb_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxb.h b/drivers/media/video/mxb.h new file mode 100644 index 000000000000..2332ed5f7c6b --- /dev/null +++ b/drivers/media/video/mxb.h @@ -0,0 +1,42 @@ +#ifndef __MXB__ +#define __MXB__ + +#define BASE_VIDIOC_MXB 10 + +#define MXB_S_AUDIO_CD _IOW ('V', BASE_VIDIOC_PRIVATE+BASE_VIDIOC_MXB+0, int) +#define MXB_S_AUDIO_LINE _IOW ('V', BASE_VIDIOC_PRIVATE+BASE_VIDIOC_MXB+1, int) + +#define MXB_IDENTIFIER "Multimedia eXtension Board" + +#define MXB_AUDIOS 6 + +/* these are the available audio sources, which can switched + to the line- and cd-output individually */ +static struct v4l2_audio mxb_audios[MXB_AUDIOS] = { + { + .index = 0, + .name = "Tuner", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 1, + .name = "AUX1", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 2, + .name = "AUX2", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 3, + .name = "AUX3", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 4, + .name = "Radio (X9)", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 5, + .name = "CD-ROM (X10)", + .capability = V4L2_AUDCAP_STEREO, + } +}; +#endif diff --git a/drivers/media/video/ovcamchip/Makefile b/drivers/media/video/ovcamchip/Makefile new file mode 100644 index 000000000000..bca41ad93de8 --- /dev/null +++ b/drivers/media/video/ovcamchip/Makefile @@ -0,0 +1,4 @@ +ovcamchip-objs := ovcamchip_core.o ov6x20.o ov6x30.o ov7x10.o ov7x20.o \ + ov76be.o + +obj-$(CONFIG_VIDEO_OVCAMCHIP) += ovcamchip.o diff --git a/drivers/media/video/ovcamchip/ov6x20.c b/drivers/media/video/ovcamchip/ov6x20.c new file mode 100644 index 000000000000..3433619ad93f --- /dev/null +++ b/drivers/media/video/ovcamchip/ov6x20.c @@ -0,0 +1,415 @@ +/* OmniVision OV6620/OV6120 Camera Chip Support Code + * + * Copyright (c) 1999-2004 Mark McClelland <mark@alpha.dyndns.org> + * http://alpha.dyndns.org/ov511/ + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + */ + +#define DEBUG + +#include <linux/slab.h> +#include "ovcamchip_priv.h" + +/* Registers */ +#define REG_GAIN 0x00 /* gain [5:0] */ +#define REG_BLUE 0x01 /* blue gain */ +#define REG_RED 0x02 /* red gain */ +#define REG_SAT 0x03 /* saturation */ +#define REG_CNT 0x05 /* Y contrast */ +#define REG_BRT 0x06 /* Y brightness */ +#define REG_WB_BLUE 0x0C /* WB blue ratio [5:0] */ +#define REG_WB_RED 0x0D /* WB red ratio [5:0] */ +#define REG_EXP 0x10 /* exposure */ + +/* Window parameters */ +#define HWSBASE 0x38 +#define HWEBASE 0x3A +#define VWSBASE 0x05 +#define VWEBASE 0x06 + +struct ov6x20 { + int auto_brt; + int auto_exp; + int backlight; + int bandfilt; + int mirror; +}; + +/* Initial values for use with OV511/OV511+ cameras */ +static struct ovcamchip_regvals regvals_init_6x20_511[] = { + { 0x12, 0x80 }, /* reset */ + { 0x11, 0x01 }, + { 0x03, 0x60 }, + { 0x05, 0x7f }, /* For when autoadjust is off */ + { 0x07, 0xa8 }, + { 0x0c, 0x24 }, + { 0x0d, 0x24 }, + { 0x0f, 0x15 }, /* COMS */ + { 0x10, 0x75 }, /* AEC Exposure time */ + { 0x12, 0x24 }, /* Enable AGC and AWB */ + { 0x14, 0x04 }, + { 0x16, 0x03 }, + { 0x26, 0xb2 }, /* BLC enable */ + /* 0x28: 0x05 Selects RGB format if RGB on */ + { 0x28, 0x05 }, + { 0x2a, 0x04 }, /* Disable framerate adjust */ + { 0x2d, 0x99 }, + { 0x33, 0xa0 }, /* Color Processing Parameter */ + { 0x34, 0xd2 }, /* Max A/D range */ + { 0x38, 0x8b }, + { 0x39, 0x40 }, + + { 0x3c, 0x39 }, /* Enable AEC mode changing */ + { 0x3c, 0x3c }, /* Change AEC mode */ + { 0x3c, 0x24 }, /* Disable AEC mode changing */ + + { 0x3d, 0x80 }, + /* These next two registers (0x4a, 0x4b) are undocumented. They + * control the color balance */ + { 0x4a, 0x80 }, + { 0x4b, 0x80 }, + { 0x4d, 0xd2 }, /* This reduces noise a bit */ + { 0x4e, 0xc1 }, + { 0x4f, 0x04 }, + { 0xff, 0xff }, /* END MARKER */ +}; + +/* Initial values for use with OV518 cameras */ +static struct ovcamchip_regvals regvals_init_6x20_518[] = { + { 0x12, 0x80 }, /* Do a reset */ + { 0x03, 0xc0 }, /* Saturation */ + { 0x05, 0x8a }, /* Contrast */ + { 0x0c, 0x24 }, /* AWB blue */ + { 0x0d, 0x24 }, /* AWB red */ + { 0x0e, 0x8d }, /* Additional 2x gain */ + { 0x0f, 0x25 }, /* Black expanding level = 1.3V */ + { 0x11, 0x01 }, /* Clock div. */ + { 0x12, 0x24 }, /* Enable AGC and AWB */ + { 0x13, 0x01 }, /* (default) */ + { 0x14, 0x80 }, /* Set reserved bit 7 */ + { 0x15, 0x01 }, /* (default) */ + { 0x16, 0x03 }, /* (default) */ + { 0x17, 0x38 }, /* (default) */ + { 0x18, 0xea }, /* (default) */ + { 0x19, 0x04 }, + { 0x1a, 0x93 }, + { 0x1b, 0x00 }, /* (default) */ + { 0x1e, 0xc4 }, /* (default) */ + { 0x1f, 0x04 }, /* (default) */ + { 0x20, 0x20 }, /* Enable 1st stage aperture correction */ + { 0x21, 0x10 }, /* Y offset */ + { 0x22, 0x88 }, /* U offset */ + { 0x23, 0xc0 }, /* Set XTAL power level */ + { 0x24, 0x53 }, /* AEC bright ratio */ + { 0x25, 0x7a }, /* AEC black ratio */ + { 0x26, 0xb2 }, /* BLC enable */ + { 0x27, 0xa2 }, /* Full output range */ + { 0x28, 0x01 }, /* (default) */ + { 0x29, 0x00 }, /* (default) */ + { 0x2a, 0x84 }, /* (default) */ + { 0x2b, 0xa8 }, /* Set custom frame rate */ + { 0x2c, 0xa0 }, /* (reserved) */ + { 0x2d, 0x95 }, /* Enable banding filter */ + { 0x2e, 0x88 }, /* V offset */ + { 0x33, 0x22 }, /* Luminance gamma on */ + { 0x34, 0xc7 }, /* A/D bias */ + { 0x36, 0x12 }, /* (reserved) */ + { 0x37, 0x63 }, /* (reserved) */ + { 0x38, 0x8b }, /* Quick AEC/AEB */ + { 0x39, 0x00 }, /* (default) */ + { 0x3a, 0x0f }, /* (default) */ + { 0x3b, 0x3c }, /* (default) */ + { 0x3c, 0x5c }, /* AEC controls */ + { 0x3d, 0x80 }, /* Drop 1 (bad) frame when AEC change */ + { 0x3e, 0x80 }, /* (default) */ + { 0x3f, 0x02 }, /* (default) */ + { 0x40, 0x10 }, /* (reserved) */ + { 0x41, 0x10 }, /* (reserved) */ + { 0x42, 0x00 }, /* (reserved) */ + { 0x43, 0x7f }, /* (reserved) */ + { 0x44, 0x80 }, /* (reserved) */ + { 0x45, 0x1c }, /* (reserved) */ + { 0x46, 0x1c }, /* (reserved) */ + { 0x47, 0x80 }, /* (reserved) */ + { 0x48, 0x5f }, /* (reserved) */ + { 0x49, 0x00 }, /* (reserved) */ + { 0x4a, 0x00 }, /* Color balance (undocumented) */ + { 0x4b, 0x80 }, /* Color balance (undocumented) */ + { 0x4c, 0x58 }, /* (reserved) */ + { 0x4d, 0xd2 }, /* U *= .938, V *= .838 */ + { 0x4e, 0xa0 }, /* (default) */ + { 0x4f, 0x04 }, /* UV 3-point average */ + { 0x50, 0xff }, /* (reserved) */ + { 0x51, 0x58 }, /* (reserved) */ + { 0x52, 0xc0 }, /* (reserved) */ + { 0x53, 0x42 }, /* (reserved) */ + { 0x27, 0xa6 }, /* Enable manual offset adj. (reg 21 & 22) */ + { 0x12, 0x20 }, + { 0x12, 0x24 }, + + { 0xff, 0xff }, /* END MARKER */ +}; + +/* This initializes the OV6x20 camera chip and relevant variables. */ +static int ov6x20_init(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov6x20 *s; + int rc; + + DDEBUG(4, &c->dev, "entered"); + + switch (c->adapter->id) { + case I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV511: + rc = ov_write_regvals(c, regvals_init_6x20_511); + break; + case I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV518: + rc = ov_write_regvals(c, regvals_init_6x20_518); + break; + default: + dev_err(&c->dev, "ov6x20: Unsupported adapter\n"); + rc = -ENODEV; + } + + if (rc < 0) + return rc; + + ov->spriv = s = kmalloc(sizeof *s, GFP_KERNEL); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof *s); + + s->auto_brt = 1; + s->auto_exp = 1; + + return rc; +} + +static int ov6x20_free(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + + kfree(ov->spriv); + return 0; +} + +static int ov6x20_set_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov6x20 *s = ov->spriv; + int rc; + int v = ctl->value; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_write(c, REG_CNT, v >> 8); + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_write(c, REG_BRT, v >> 8); + break; + case OVCAMCHIP_CID_SAT: + rc = ov_write(c, REG_SAT, v >> 8); + break; + case OVCAMCHIP_CID_HUE: + rc = ov_write(c, REG_RED, 0xFF - (v >> 8)); + if (rc < 0) + goto out; + + rc = ov_write(c, REG_BLUE, v >> 8); + break; + case OVCAMCHIP_CID_EXP: + rc = ov_write(c, REG_EXP, v); + break; + case OVCAMCHIP_CID_FREQ: + { + int sixty = (v == 60); + + rc = ov_write(c, 0x2b, sixty?0xa8:0x28); + if (rc < 0) + goto out; + + rc = ov_write(c, 0x2a, sixty?0x84:0xa4); + break; + } + case OVCAMCHIP_CID_BANDFILT: + rc = ov_write_mask(c, 0x2d, v?0x04:0x00, 0x04); + s->bandfilt = v; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + rc = ov_write_mask(c, 0x2d, v?0x10:0x00, 0x10); + s->auto_brt = v; + break; + case OVCAMCHIP_CID_AUTOEXP: + rc = ov_write_mask(c, 0x13, v?0x01:0x00, 0x01); + s->auto_exp = v; + break; + case OVCAMCHIP_CID_BACKLIGHT: + { + rc = ov_write_mask(c, 0x4e, v?0xe0:0xc0, 0xe0); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x29, v?0x08:0x00, 0x08); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x0e, v?0x80:0x00, 0x80); + s->backlight = v; + break; + } + case OVCAMCHIP_CID_MIRROR: + rc = ov_write_mask(c, 0x12, v?0x40:0x00, 0x40); + s->mirror = v; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + +out: + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, v, rc); + return rc; +} + +static int ov6x20_get_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov6x20 *s = ov->spriv; + int rc = 0; + unsigned char val = 0; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_read(c, REG_CNT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_read(c, REG_BRT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_SAT: + rc = ov_read(c, REG_SAT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_HUE: + rc = ov_read(c, REG_BLUE, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_EXP: + rc = ov_read(c, REG_EXP, &val); + ctl->value = val; + break; + case OVCAMCHIP_CID_BANDFILT: + ctl->value = s->bandfilt; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + ctl->value = s->auto_brt; + break; + case OVCAMCHIP_CID_AUTOEXP: + ctl->value = s->auto_exp; + break; + case OVCAMCHIP_CID_BACKLIGHT: + ctl->value = s->backlight; + break; + case OVCAMCHIP_CID_MIRROR: + ctl->value = s->mirror; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, ctl->value, rc); + return rc; +} + +static int ov6x20_mode_init(struct i2c_client *c, struct ovcamchip_window *win) +{ + /******** QCIF-specific regs ********/ + + ov_write(c, 0x14, win->quarter?0x24:0x04); + + /******** Palette-specific regs ********/ + + /* OV518 needs 8 bit multiplexed in color mode, and 16 bit in B&W */ + if (c->adapter->id == (I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV518)) { + if (win->format == VIDEO_PALETTE_GREY) + ov_write_mask(c, 0x13, 0x00, 0x20); + else + ov_write_mask(c, 0x13, 0x20, 0x20); + } else { + if (win->format == VIDEO_PALETTE_GREY) + ov_write_mask(c, 0x13, 0x20, 0x20); + else + ov_write_mask(c, 0x13, 0x00, 0x20); + } + + /******** Clock programming ********/ + + /* The OV6620 needs special handling. This prevents the + * severe banding that normally occurs */ + + /* Clock down */ + ov_write(c, 0x2a, 0x04); + + ov_write(c, 0x11, win->clockdiv); + + ov_write(c, 0x2a, 0x84); + /* This next setting is critical. It seems to improve + * the gain or the contrast. The "reserved" bits seem + * to have some effect in this case. */ + ov_write(c, 0x2d, 0x85); /* FIXME: This messes up banding filter */ + + return 0; +} + +static int ov6x20_set_window(struct i2c_client *c, struct ovcamchip_window *win) +{ + int ret, hwscale, vwscale; + + ret = ov6x20_mode_init(c, win); + if (ret < 0) + return ret; + + if (win->quarter) { + hwscale = 0; + vwscale = 0; + } else { + hwscale = 1; + vwscale = 1; /* The datasheet says 0; it's wrong */ + } + + ov_write(c, 0x17, HWSBASE + (win->x >> hwscale)); + ov_write(c, 0x18, HWEBASE + ((win->x + win->width) >> hwscale)); + ov_write(c, 0x19, VWSBASE + (win->y >> vwscale)); + ov_write(c, 0x1a, VWEBASE + ((win->y + win->height) >> vwscale)); + + return 0; +} + +static int ov6x20_command(struct i2c_client *c, unsigned int cmd, void *arg) +{ + switch (cmd) { + case OVCAMCHIP_CMD_S_CTRL: + return ov6x20_set_control(c, arg); + case OVCAMCHIP_CMD_G_CTRL: + return ov6x20_get_control(c, arg); + case OVCAMCHIP_CMD_S_MODE: + return ov6x20_set_window(c, arg); + default: + DDEBUG(2, &c->dev, "command not supported: %d", cmd); + return -ENOIOCTLCMD; + } +} + +struct ovcamchip_ops ov6x20_ops = { + .init = ov6x20_init, + .free = ov6x20_free, + .command = ov6x20_command, +}; diff --git a/drivers/media/video/ovcamchip/ov6x30.c b/drivers/media/video/ovcamchip/ov6x30.c new file mode 100644 index 000000000000..44a842379b45 --- /dev/null +++ b/drivers/media/video/ovcamchip/ov6x30.c @@ -0,0 +1,374 @@ +/* OmniVision OV6630/OV6130 Camera Chip Support Code + * + * Copyright (c) 1999-2004 Mark McClelland <mark@alpha.dyndns.org> + * http://alpha.dyndns.org/ov511/ + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + */ + +#define DEBUG + +#include <linux/slab.h> +#include "ovcamchip_priv.h" + +/* Registers */ +#define REG_GAIN 0x00 /* gain [5:0] */ +#define REG_BLUE 0x01 /* blue gain */ +#define REG_RED 0x02 /* red gain */ +#define REG_SAT 0x03 /* saturation [7:3] */ +#define REG_CNT 0x05 /* Y contrast [3:0] */ +#define REG_BRT 0x06 /* Y brightness */ +#define REG_SHARP 0x07 /* sharpness */ +#define REG_WB_BLUE 0x0C /* WB blue ratio [5:0] */ +#define REG_WB_RED 0x0D /* WB red ratio [5:0] */ +#define REG_EXP 0x10 /* exposure */ + +/* Window parameters */ +#define HWSBASE 0x38 +#define HWEBASE 0x3A +#define VWSBASE 0x05 +#define VWEBASE 0x06 + +struct ov6x30 { + int auto_brt; + int auto_exp; + int backlight; + int bandfilt; + int mirror; +}; + +static struct ovcamchip_regvals regvals_init_6x30[] = { + { 0x12, 0x80 }, /* reset */ + { 0x00, 0x1f }, /* Gain */ + { 0x01, 0x99 }, /* Blue gain */ + { 0x02, 0x7c }, /* Red gain */ + { 0x03, 0xc0 }, /* Saturation */ + { 0x05, 0x0a }, /* Contrast */ + { 0x06, 0x95 }, /* Brightness */ + { 0x07, 0x2d }, /* Sharpness */ + { 0x0c, 0x20 }, + { 0x0d, 0x20 }, + { 0x0e, 0x20 }, + { 0x0f, 0x05 }, + { 0x10, 0x9a }, /* "exposure check" */ + { 0x11, 0x00 }, /* Pixel clock = fastest */ + { 0x12, 0x24 }, /* Enable AGC and AWB */ + { 0x13, 0x21 }, + { 0x14, 0x80 }, + { 0x15, 0x01 }, + { 0x16, 0x03 }, + { 0x17, 0x38 }, + { 0x18, 0xea }, + { 0x19, 0x04 }, + { 0x1a, 0x93 }, + { 0x1b, 0x00 }, + { 0x1e, 0xc4 }, + { 0x1f, 0x04 }, + { 0x20, 0x20 }, + { 0x21, 0x10 }, + { 0x22, 0x88 }, + { 0x23, 0xc0 }, /* Crystal circuit power level */ + { 0x25, 0x9a }, /* Increase AEC black pixel ratio */ + { 0x26, 0xb2 }, /* BLC enable */ + { 0x27, 0xa2 }, + { 0x28, 0x00 }, + { 0x29, 0x00 }, + { 0x2a, 0x84 }, /* (keep) */ + { 0x2b, 0xa8 }, /* (keep) */ + { 0x2c, 0xa0 }, + { 0x2d, 0x95 }, /* Enable auto-brightness */ + { 0x2e, 0x88 }, + { 0x33, 0x26 }, + { 0x34, 0x03 }, + { 0x36, 0x8f }, + { 0x37, 0x80 }, + { 0x38, 0x83 }, + { 0x39, 0x80 }, + { 0x3a, 0x0f }, + { 0x3b, 0x3c }, + { 0x3c, 0x1a }, + { 0x3d, 0x80 }, + { 0x3e, 0x80 }, + { 0x3f, 0x0e }, + { 0x40, 0x00 }, /* White bal */ + { 0x41, 0x00 }, /* White bal */ + { 0x42, 0x80 }, + { 0x43, 0x3f }, /* White bal */ + { 0x44, 0x80 }, + { 0x45, 0x20 }, + { 0x46, 0x20 }, + { 0x47, 0x80 }, + { 0x48, 0x7f }, + { 0x49, 0x00 }, + { 0x4a, 0x00 }, + { 0x4b, 0x80 }, + { 0x4c, 0xd0 }, + { 0x4d, 0x10 }, /* U = 0.563u, V = 0.714v */ + { 0x4e, 0x40 }, + { 0x4f, 0x07 }, /* UV average mode, color killer: strongest */ + { 0x50, 0xff }, + { 0x54, 0x23 }, /* Max AGC gain: 18dB */ + { 0x55, 0xff }, + { 0x56, 0x12 }, + { 0x57, 0x81 }, /* (default) */ + { 0x58, 0x75 }, + { 0x59, 0x01 }, /* AGC dark current compensation: +1 */ + { 0x5a, 0x2c }, + { 0x5b, 0x0f }, /* AWB chrominance levels */ + { 0x5c, 0x10 }, + { 0x3d, 0x80 }, + { 0x27, 0xa6 }, + /* Toggle AWB off and on */ + { 0x12, 0x20 }, + { 0x12, 0x24 }, + + { 0xff, 0xff }, /* END MARKER */ +}; + +/* This initializes the OV6x30 camera chip and relevant variables. */ +static int ov6x30_init(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov6x30 *s; + int rc; + + DDEBUG(4, &c->dev, "entered"); + + rc = ov_write_regvals(c, regvals_init_6x30); + if (rc < 0) + return rc; + + ov->spriv = s = kmalloc(sizeof *s, GFP_KERNEL); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof *s); + + s->auto_brt = 1; + s->auto_exp = 1; + + return rc; +} + +static int ov6x30_free(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + + kfree(ov->spriv); + return 0; +} + +static int ov6x30_set_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov6x30 *s = ov->spriv; + int rc; + int v = ctl->value; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_write_mask(c, REG_CNT, v >> 12, 0x0f); + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_write(c, REG_BRT, v >> 8); + break; + case OVCAMCHIP_CID_SAT: + rc = ov_write(c, REG_SAT, v >> 8); + break; + case OVCAMCHIP_CID_HUE: + rc = ov_write(c, REG_RED, 0xFF - (v >> 8)); + if (rc < 0) + goto out; + + rc = ov_write(c, REG_BLUE, v >> 8); + break; + case OVCAMCHIP_CID_EXP: + rc = ov_write(c, REG_EXP, v); + break; + case OVCAMCHIP_CID_FREQ: + { + int sixty = (v == 60); + + rc = ov_write(c, 0x2b, sixty?0xa8:0x28); + if (rc < 0) + goto out; + + rc = ov_write(c, 0x2a, sixty?0x84:0xa4); + break; + } + case OVCAMCHIP_CID_BANDFILT: + rc = ov_write_mask(c, 0x2d, v?0x04:0x00, 0x04); + s->bandfilt = v; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + rc = ov_write_mask(c, 0x2d, v?0x10:0x00, 0x10); + s->auto_brt = v; + break; + case OVCAMCHIP_CID_AUTOEXP: + rc = ov_write_mask(c, 0x28, v?0x00:0x10, 0x10); + s->auto_exp = v; + break; + case OVCAMCHIP_CID_BACKLIGHT: + { + rc = ov_write_mask(c, 0x4e, v?0x80:0x60, 0xe0); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x29, v?0x08:0x00, 0x08); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x28, v?0x02:0x00, 0x02); + s->backlight = v; + break; + } + case OVCAMCHIP_CID_MIRROR: + rc = ov_write_mask(c, 0x12, v?0x40:0x00, 0x40); + s->mirror = v; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + +out: + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, v, rc); + return rc; +} + +static int ov6x30_get_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov6x30 *s = ov->spriv; + int rc = 0; + unsigned char val = 0; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_read(c, REG_CNT, &val); + ctl->value = (val & 0x0f) << 12; + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_read(c, REG_BRT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_SAT: + rc = ov_read(c, REG_SAT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_HUE: + rc = ov_read(c, REG_BLUE, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_EXP: + rc = ov_read(c, REG_EXP, &val); + ctl->value = val; + break; + case OVCAMCHIP_CID_BANDFILT: + ctl->value = s->bandfilt; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + ctl->value = s->auto_brt; + break; + case OVCAMCHIP_CID_AUTOEXP: + ctl->value = s->auto_exp; + break; + case OVCAMCHIP_CID_BACKLIGHT: + ctl->value = s->backlight; + break; + case OVCAMCHIP_CID_MIRROR: + ctl->value = s->mirror; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, ctl->value, rc); + return rc; +} + +static int ov6x30_mode_init(struct i2c_client *c, struct ovcamchip_window *win) +{ + /******** QCIF-specific regs ********/ + + ov_write_mask(c, 0x14, win->quarter?0x20:0x00, 0x20); + + /******** Palette-specific regs ********/ + + if (win->format == VIDEO_PALETTE_GREY) { + if (c->adapter->id == (I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV518)) { + /* Do nothing - we're already in 8-bit mode */ + } else { + ov_write_mask(c, 0x13, 0x20, 0x20); + } + } else { + /* The OV518 needs special treatment. Although both the OV518 + * and the OV6630 support a 16-bit video bus, only the 8 bit Y + * bus is actually used. The UV bus is tied to ground. + * Therefore, the OV6630 needs to be in 8-bit multiplexed + * output mode */ + + if (c->adapter->id == (I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV518)) { + /* Do nothing - we want to stay in 8-bit mode */ + /* Warning: Messing with reg 0x13 breaks OV518 color */ + } else { + ov_write_mask(c, 0x13, 0x00, 0x20); + } + } + + /******** Clock programming ********/ + + ov_write(c, 0x11, win->clockdiv); + + return 0; +} + +static int ov6x30_set_window(struct i2c_client *c, struct ovcamchip_window *win) +{ + int ret, hwscale, vwscale; + + ret = ov6x30_mode_init(c, win); + if (ret < 0) + return ret; + + if (win->quarter) { + hwscale = 0; + vwscale = 0; + } else { + hwscale = 1; + vwscale = 1; /* The datasheet says 0; it's wrong */ + } + + ov_write(c, 0x17, HWSBASE + (win->x >> hwscale)); + ov_write(c, 0x18, HWEBASE + ((win->x + win->width) >> hwscale)); + ov_write(c, 0x19, VWSBASE + (win->y >> vwscale)); + ov_write(c, 0x1a, VWEBASE + ((win->y + win->height) >> vwscale)); + + return 0; +} + +static int ov6x30_command(struct i2c_client *c, unsigned int cmd, void *arg) +{ + switch (cmd) { + case OVCAMCHIP_CMD_S_CTRL: + return ov6x30_set_control(c, arg); + case OVCAMCHIP_CMD_G_CTRL: + return ov6x30_get_control(c, arg); + case OVCAMCHIP_CMD_S_MODE: + return ov6x30_set_window(c, arg); + default: + DDEBUG(2, &c->dev, "command not supported: %d", cmd); + return -ENOIOCTLCMD; + } +} + +struct ovcamchip_ops ov6x30_ops = { + .init = ov6x30_init, + .free = ov6x30_free, + .command = ov6x30_command, +}; diff --git a/drivers/media/video/ovcamchip/ov76be.c b/drivers/media/video/ovcamchip/ov76be.c new file mode 100644 index 000000000000..29bbdc05e3b6 --- /dev/null +++ b/drivers/media/video/ovcamchip/ov76be.c @@ -0,0 +1,303 @@ +/* OmniVision OV76BE Camera Chip Support Code + * + * Copyright (c) 1999-2004 Mark McClelland <mark@alpha.dyndns.org> + * http://alpha.dyndns.org/ov511/ + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + */ + +#define DEBUG + +#include <linux/slab.h> +#include "ovcamchip_priv.h" + +/* OV7610 registers: Since the OV76BE is undocumented, we'll settle for these + * for now. */ +#define REG_GAIN 0x00 /* gain [5:0] */ +#define REG_BLUE 0x01 /* blue channel balance */ +#define REG_RED 0x02 /* red channel balance */ +#define REG_SAT 0x03 /* saturation */ +#define REG_CNT 0x05 /* Y contrast */ +#define REG_BRT 0x06 /* Y brightness */ +#define REG_BLUE_BIAS 0x0C /* blue channel bias [5:0] */ +#define REG_RED_BIAS 0x0D /* red channel bias [5:0] */ +#define REG_GAMMA_COEFF 0x0E /* gamma settings */ +#define REG_WB_RANGE 0x0F /* AEC/ALC/S-AWB settings */ +#define REG_EXP 0x10 /* manual exposure setting */ +#define REG_CLOCK 0x11 /* polarity/clock prescaler */ +#define REG_FIELD_DIVIDE 0x16 /* field interval/mode settings */ +#define REG_HWIN_START 0x17 /* horizontal window start */ +#define REG_HWIN_END 0x18 /* horizontal window end */ +#define REG_VWIN_START 0x19 /* vertical window start */ +#define REG_VWIN_END 0x1A /* vertical window end */ +#define REG_PIXEL_SHIFT 0x1B /* pixel shift */ +#define REG_YOFFSET 0x21 /* Y channel offset */ +#define REG_UOFFSET 0x22 /* U channel offset */ +#define REG_ECW 0x24 /* exposure white level for AEC */ +#define REG_ECB 0x25 /* exposure black level for AEC */ +#define REG_FRAMERATE_H 0x2A /* frame rate MSB + misc */ +#define REG_FRAMERATE_L 0x2B /* frame rate LSB */ +#define REG_ALC 0x2C /* Auto Level Control settings */ +#define REG_VOFFSET 0x2E /* V channel offset adjustment */ +#define REG_ARRAY_BIAS 0x2F /* array bias -- don't change */ +#define REG_YGAMMA 0x33 /* misc gamma settings [7:6] */ +#define REG_BIAS_ADJUST 0x34 /* misc bias settings */ + +/* Window parameters */ +#define HWSBASE 0x38 +#define HWEBASE 0x3a +#define VWSBASE 0x05 +#define VWEBASE 0x05 + +struct ov76be { + int auto_brt; + int auto_exp; + int bandfilt; + int mirror; +}; + +/* NOTE: These are the same as the 7x10 settings, but should eventually be + * optimized for the OV76BE */ +static struct ovcamchip_regvals regvals_init_76be[] = { + { 0x10, 0xff }, + { 0x16, 0x03 }, + { 0x28, 0x24 }, + { 0x2b, 0xac }, + { 0x12, 0x00 }, + { 0x38, 0x81 }, + { 0x28, 0x24 }, /* 0c */ + { 0x0f, 0x85 }, /* lg's setting */ + { 0x15, 0x01 }, + { 0x20, 0x1c }, + { 0x23, 0x2a }, + { 0x24, 0x10 }, + { 0x25, 0x8a }, + { 0x26, 0xa2 }, + { 0x27, 0xc2 }, + { 0x2a, 0x04 }, + { 0x2c, 0xfe }, + { 0x2d, 0x93 }, + { 0x30, 0x71 }, + { 0x31, 0x60 }, + { 0x32, 0x26 }, + { 0x33, 0x20 }, + { 0x34, 0x48 }, + { 0x12, 0x24 }, + { 0x11, 0x01 }, + { 0x0c, 0x24 }, + { 0x0d, 0x24 }, + { 0xff, 0xff }, /* END MARKER */ +}; + +/* This initializes the OV76be camera chip and relevant variables. */ +static int ov76be_init(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov76be *s; + int rc; + + DDEBUG(4, &c->dev, "entered"); + + rc = ov_write_regvals(c, regvals_init_76be); + if (rc < 0) + return rc; + + ov->spriv = s = kmalloc(sizeof *s, GFP_KERNEL); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof *s); + + s->auto_brt = 1; + s->auto_exp = 1; + + return rc; +} + +static int ov76be_free(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + + kfree(ov->spriv); + return 0; +} + +static int ov76be_set_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov76be *s = ov->spriv; + int rc; + int v = ctl->value; + + switch (ctl->id) { + case OVCAMCHIP_CID_BRIGHT: + rc = ov_write(c, REG_BRT, v >> 8); + break; + case OVCAMCHIP_CID_SAT: + rc = ov_write(c, REG_SAT, v >> 8); + break; + case OVCAMCHIP_CID_EXP: + rc = ov_write(c, REG_EXP, v); + break; + case OVCAMCHIP_CID_FREQ: + { + int sixty = (v == 60); + + rc = ov_write_mask(c, 0x2a, sixty?0x00:0x80, 0x80); + if (rc < 0) + goto out; + + rc = ov_write(c, 0x2b, sixty?0x00:0xac); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x76, 0x01, 0x01); + break; + } + case OVCAMCHIP_CID_BANDFILT: + rc = ov_write_mask(c, 0x2d, v?0x04:0x00, 0x04); + s->bandfilt = v; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + rc = ov_write_mask(c, 0x2d, v?0x10:0x00, 0x10); + s->auto_brt = v; + break; + case OVCAMCHIP_CID_AUTOEXP: + rc = ov_write_mask(c, 0x13, v?0x01:0x00, 0x01); + s->auto_exp = v; + break; + case OVCAMCHIP_CID_MIRROR: + rc = ov_write_mask(c, 0x12, v?0x40:0x00, 0x40); + s->mirror = v; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + +out: + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, v, rc); + return rc; +} + +static int ov76be_get_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov76be *s = ov->spriv; + int rc = 0; + unsigned char val = 0; + + switch (ctl->id) { + case OVCAMCHIP_CID_BRIGHT: + rc = ov_read(c, REG_BRT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_SAT: + rc = ov_read(c, REG_SAT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_EXP: + rc = ov_read(c, REG_EXP, &val); + ctl->value = val; + break; + case OVCAMCHIP_CID_BANDFILT: + ctl->value = s->bandfilt; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + ctl->value = s->auto_brt; + break; + case OVCAMCHIP_CID_AUTOEXP: + ctl->value = s->auto_exp; + break; + case OVCAMCHIP_CID_MIRROR: + ctl->value = s->mirror; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, ctl->value, rc); + return rc; +} + +static int ov76be_mode_init(struct i2c_client *c, struct ovcamchip_window *win) +{ + int qvga = win->quarter; + + /******** QVGA-specific regs ********/ + + ov_write(c, 0x14, qvga?0xa4:0x84); + + /******** Palette-specific regs ********/ + + if (win->format == VIDEO_PALETTE_GREY) { + ov_write_mask(c, 0x0e, 0x40, 0x40); + ov_write_mask(c, 0x13, 0x20, 0x20); + } else { + ov_write_mask(c, 0x0e, 0x00, 0x40); + ov_write_mask(c, 0x13, 0x00, 0x20); + } + + /******** Clock programming ********/ + + ov_write(c, 0x11, win->clockdiv); + + /******** Resolution-specific ********/ + + if (win->width == 640 && win->height == 480) + ov_write(c, 0x35, 0x9e); + else + ov_write(c, 0x35, 0x1e); + + return 0; +} + +static int ov76be_set_window(struct i2c_client *c, struct ovcamchip_window *win) +{ + int ret, hwscale, vwscale; + + ret = ov76be_mode_init(c, win); + if (ret < 0) + return ret; + + if (win->quarter) { + hwscale = 1; + vwscale = 0; + } else { + hwscale = 2; + vwscale = 1; + } + + ov_write(c, 0x17, HWSBASE + (win->x >> hwscale)); + ov_write(c, 0x18, HWEBASE + ((win->x + win->width) >> hwscale)); + ov_write(c, 0x19, VWSBASE + (win->y >> vwscale)); + ov_write(c, 0x1a, VWEBASE + ((win->y + win->height) >> vwscale)); + + return 0; +} + +static int ov76be_command(struct i2c_client *c, unsigned int cmd, void *arg) +{ + switch (cmd) { + case OVCAMCHIP_CMD_S_CTRL: + return ov76be_set_control(c, arg); + case OVCAMCHIP_CMD_G_CTRL: + return ov76be_get_control(c, arg); + case OVCAMCHIP_CMD_S_MODE: + return ov76be_set_window(c, arg); + default: + DDEBUG(2, &c->dev, "command not supported: %d", cmd); + return -ENOIOCTLCMD; + } +} + +struct ovcamchip_ops ov76be_ops = { + .init = ov76be_init, + .free = ov76be_free, + .command = ov76be_command, +}; diff --git a/drivers/media/video/ovcamchip/ov7x10.c b/drivers/media/video/ovcamchip/ov7x10.c new file mode 100644 index 000000000000..6c383d4b14fa --- /dev/null +++ b/drivers/media/video/ovcamchip/ov7x10.c @@ -0,0 +1,335 @@ +/* OmniVision OV7610/OV7110 Camera Chip Support Code + * + * Copyright (c) 1999-2004 Mark McClelland <mark@alpha.dyndns.org> + * http://alpha.dyndns.org/ov511/ + * + * Color fixes by by Orion Sky Lawlor <olawlor@acm.org> (2/26/2000) + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + */ + +#define DEBUG + +#include <linux/slab.h> +#include "ovcamchip_priv.h" + +/* Registers */ +#define REG_GAIN 0x00 /* gain [5:0] */ +#define REG_BLUE 0x01 /* blue channel balance */ +#define REG_RED 0x02 /* red channel balance */ +#define REG_SAT 0x03 /* saturation */ +#define REG_CNT 0x05 /* Y contrast */ +#define REG_BRT 0x06 /* Y brightness */ +#define REG_BLUE_BIAS 0x0C /* blue channel bias [5:0] */ +#define REG_RED_BIAS 0x0D /* red channel bias [5:0] */ +#define REG_GAMMA_COEFF 0x0E /* gamma settings */ +#define REG_WB_RANGE 0x0F /* AEC/ALC/S-AWB settings */ +#define REG_EXP 0x10 /* manual exposure setting */ +#define REG_CLOCK 0x11 /* polarity/clock prescaler */ +#define REG_FIELD_DIVIDE 0x16 /* field interval/mode settings */ +#define REG_HWIN_START 0x17 /* horizontal window start */ +#define REG_HWIN_END 0x18 /* horizontal window end */ +#define REG_VWIN_START 0x19 /* vertical window start */ +#define REG_VWIN_END 0x1A /* vertical window end */ +#define REG_PIXEL_SHIFT 0x1B /* pixel shift */ +#define REG_YOFFSET 0x21 /* Y channel offset */ +#define REG_UOFFSET 0x22 /* U channel offset */ +#define REG_ECW 0x24 /* exposure white level for AEC */ +#define REG_ECB 0x25 /* exposure black level for AEC */ +#define REG_FRAMERATE_H 0x2A /* frame rate MSB + misc */ +#define REG_FRAMERATE_L 0x2B /* frame rate LSB */ +#define REG_ALC 0x2C /* Auto Level Control settings */ +#define REG_VOFFSET 0x2E /* V channel offset adjustment */ +#define REG_ARRAY_BIAS 0x2F /* array bias -- don't change */ +#define REG_YGAMMA 0x33 /* misc gamma settings [7:6] */ +#define REG_BIAS_ADJUST 0x34 /* misc bias settings */ + +/* Window parameters */ +#define HWSBASE 0x38 +#define HWEBASE 0x3a +#define VWSBASE 0x05 +#define VWEBASE 0x05 + +struct ov7x10 { + int auto_brt; + int auto_exp; + int bandfilt; + int mirror; +}; + +/* Lawrence Glaister <lg@jfm.bc.ca> reports: + * + * Register 0x0f in the 7610 has the following effects: + * + * 0x85 (AEC method 1): Best overall, good contrast range + * 0x45 (AEC method 2): Very overexposed + * 0xa5 (spec sheet default): Ok, but the black level is + * shifted resulting in loss of contrast + * 0x05 (old driver setting): very overexposed, too much + * contrast + */ +static struct ovcamchip_regvals regvals_init_7x10[] = { + { 0x10, 0xff }, + { 0x16, 0x03 }, + { 0x28, 0x24 }, + { 0x2b, 0xac }, + { 0x12, 0x00 }, + { 0x38, 0x81 }, + { 0x28, 0x24 }, /* 0c */ + { 0x0f, 0x85 }, /* lg's setting */ + { 0x15, 0x01 }, + { 0x20, 0x1c }, + { 0x23, 0x2a }, + { 0x24, 0x10 }, + { 0x25, 0x8a }, + { 0x26, 0xa2 }, + { 0x27, 0xc2 }, + { 0x2a, 0x04 }, + { 0x2c, 0xfe }, + { 0x2d, 0x93 }, + { 0x30, 0x71 }, + { 0x31, 0x60 }, + { 0x32, 0x26 }, + { 0x33, 0x20 }, + { 0x34, 0x48 }, + { 0x12, 0x24 }, + { 0x11, 0x01 }, + { 0x0c, 0x24 }, + { 0x0d, 0x24 }, + { 0xff, 0xff }, /* END MARKER */ +}; + +/* This initializes the OV7x10 camera chip and relevant variables. */ +static int ov7x10_init(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov7x10 *s; + int rc; + + DDEBUG(4, &c->dev, "entered"); + + rc = ov_write_regvals(c, regvals_init_7x10); + if (rc < 0) + return rc; + + ov->spriv = s = kmalloc(sizeof *s, GFP_KERNEL); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof *s); + + s->auto_brt = 1; + s->auto_exp = 1; + + return rc; +} + +static int ov7x10_free(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + + kfree(ov->spriv); + return 0; +} + +static int ov7x10_set_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov7x10 *s = ov->spriv; + int rc; + int v = ctl->value; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_write(c, REG_CNT, v >> 8); + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_write(c, REG_BRT, v >> 8); + break; + case OVCAMCHIP_CID_SAT: + rc = ov_write(c, REG_SAT, v >> 8); + break; + case OVCAMCHIP_CID_HUE: + rc = ov_write(c, REG_RED, 0xFF - (v >> 8)); + if (rc < 0) + goto out; + + rc = ov_write(c, REG_BLUE, v >> 8); + break; + case OVCAMCHIP_CID_EXP: + rc = ov_write(c, REG_EXP, v); + break; + case OVCAMCHIP_CID_FREQ: + { + int sixty = (v == 60); + + rc = ov_write_mask(c, 0x2a, sixty?0x00:0x80, 0x80); + if (rc < 0) + goto out; + + rc = ov_write(c, 0x2b, sixty?0x00:0xac); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x13, 0x10, 0x10); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x13, 0x00, 0x10); + break; + } + case OVCAMCHIP_CID_BANDFILT: + rc = ov_write_mask(c, 0x2d, v?0x04:0x00, 0x04); + s->bandfilt = v; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + rc = ov_write_mask(c, 0x2d, v?0x10:0x00, 0x10); + s->auto_brt = v; + break; + case OVCAMCHIP_CID_AUTOEXP: + rc = ov_write_mask(c, 0x29, v?0x00:0x80, 0x80); + s->auto_exp = v; + break; + case OVCAMCHIP_CID_MIRROR: + rc = ov_write_mask(c, 0x12, v?0x40:0x00, 0x40); + s->mirror = v; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + +out: + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, v, rc); + return rc; +} + +static int ov7x10_get_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov7x10 *s = ov->spriv; + int rc = 0; + unsigned char val = 0; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_read(c, REG_CNT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_read(c, REG_BRT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_SAT: + rc = ov_read(c, REG_SAT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_HUE: + rc = ov_read(c, REG_BLUE, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_EXP: + rc = ov_read(c, REG_EXP, &val); + ctl->value = val; + break; + case OVCAMCHIP_CID_BANDFILT: + ctl->value = s->bandfilt; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + ctl->value = s->auto_brt; + break; + case OVCAMCHIP_CID_AUTOEXP: + ctl->value = s->auto_exp; + break; + case OVCAMCHIP_CID_MIRROR: + ctl->value = s->mirror; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, ctl->value, rc); + return rc; +} + +static int ov7x10_mode_init(struct i2c_client *c, struct ovcamchip_window *win) +{ + int qvga = win->quarter; + + /******** QVGA-specific regs ********/ + + ov_write(c, 0x14, qvga?0x24:0x04); + + /******** Palette-specific regs ********/ + + if (win->format == VIDEO_PALETTE_GREY) { + ov_write_mask(c, 0x0e, 0x40, 0x40); + ov_write_mask(c, 0x13, 0x20, 0x20); + } else { + ov_write_mask(c, 0x0e, 0x00, 0x40); + ov_write_mask(c, 0x13, 0x00, 0x20); + } + + /******** Clock programming ********/ + + ov_write(c, 0x11, win->clockdiv); + + /******** Resolution-specific ********/ + + if (win->width == 640 && win->height == 480) + ov_write(c, 0x35, 0x9e); + else + ov_write(c, 0x35, 0x1e); + + return 0; +} + +static int ov7x10_set_window(struct i2c_client *c, struct ovcamchip_window *win) +{ + int ret, hwscale, vwscale; + + ret = ov7x10_mode_init(c, win); + if (ret < 0) + return ret; + + if (win->quarter) { + hwscale = 1; + vwscale = 0; + } else { + hwscale = 2; + vwscale = 1; + } + + ov_write(c, 0x17, HWSBASE + (win->x >> hwscale)); + ov_write(c, 0x18, HWEBASE + ((win->x + win->width) >> hwscale)); + ov_write(c, 0x19, VWSBASE + (win->y >> vwscale)); + ov_write(c, 0x1a, VWEBASE + ((win->y + win->height) >> vwscale)); + + return 0; +} + +static int ov7x10_command(struct i2c_client *c, unsigned int cmd, void *arg) +{ + switch (cmd) { + case OVCAMCHIP_CMD_S_CTRL: + return ov7x10_set_control(c, arg); + case OVCAMCHIP_CMD_G_CTRL: + return ov7x10_get_control(c, arg); + case OVCAMCHIP_CMD_S_MODE: + return ov7x10_set_window(c, arg); + default: + DDEBUG(2, &c->dev, "command not supported: %d", cmd); + return -ENOIOCTLCMD; + } +} + +struct ovcamchip_ops ov7x10_ops = { + .init = ov7x10_init, + .free = ov7x10_free, + .command = ov7x10_command, +}; diff --git a/drivers/media/video/ovcamchip/ov7x20.c b/drivers/media/video/ovcamchip/ov7x20.c new file mode 100644 index 000000000000..3c8c48f338ba --- /dev/null +++ b/drivers/media/video/ovcamchip/ov7x20.c @@ -0,0 +1,455 @@ +/* OmniVision OV7620/OV7120 Camera Chip Support Code + * + * Copyright (c) 1999-2004 Mark McClelland <mark@alpha.dyndns.org> + * http://alpha.dyndns.org/ov511/ + * + * OV7620 fixes by Charl P. Botha <cpbotha@ieee.org> + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + */ + +#define DEBUG + +#include <linux/slab.h> +#include "ovcamchip_priv.h" + +/* Registers */ +#define REG_GAIN 0x00 /* gain [5:0] */ +#define REG_BLUE 0x01 /* blue gain */ +#define REG_RED 0x02 /* red gain */ +#define REG_SAT 0x03 /* saturation */ +#define REG_BRT 0x06 /* Y brightness */ +#define REG_SHARP 0x07 /* analog sharpness */ +#define REG_BLUE_BIAS 0x0C /* WB blue ratio [5:0] */ +#define REG_RED_BIAS 0x0D /* WB red ratio [5:0] */ +#define REG_EXP 0x10 /* exposure */ + +/* Default control settings. Values are in terms of V4L2 controls. */ +#define OV7120_DFL_BRIGHT 0x60 +#define OV7620_DFL_BRIGHT 0x60 +#define OV7120_DFL_SAT 0xb0 +#define OV7620_DFL_SAT 0xc0 +#define DFL_AUTO_EXP 1 +#define DFL_AUTO_GAIN 1 +#define OV7120_DFL_GAIN 0x00 +#define OV7620_DFL_GAIN 0x00 +/* NOTE: Since autoexposure is the default, these aren't programmed into the + * OV7x20 chip. They are just here because V4L2 expects a default */ +#define OV7120_DFL_EXP 0x7f +#define OV7620_DFL_EXP 0x7f + +/* Window parameters */ +#define HWSBASE 0x2F /* From 7620.SET (spec is wrong) */ +#define HWEBASE 0x2F +#define VWSBASE 0x05 +#define VWEBASE 0x05 + +struct ov7x20 { + int auto_brt; + int auto_exp; + int auto_gain; + int backlight; + int bandfilt; + int mirror; +}; + +/* Contrast look-up table */ +static unsigned char ctab[] = { + 0x01, 0x05, 0x09, 0x11, 0x15, 0x35, 0x37, 0x57, + 0x5b, 0xa5, 0xa7, 0xc7, 0xc9, 0xcf, 0xef, 0xff +}; + +/* Settings for (Black & White) OV7120 camera chip */ +static struct ovcamchip_regvals regvals_init_7120[] = { + { 0x12, 0x80 }, /* reset */ + { 0x13, 0x00 }, /* Autoadjust off */ + { 0x12, 0x20 }, /* Disable AWB */ + { 0x13, DFL_AUTO_GAIN?0x01:0x00 }, /* Autoadjust on (if desired) */ + { 0x00, OV7120_DFL_GAIN }, + { 0x01, 0x80 }, + { 0x02, 0x80 }, + { 0x03, OV7120_DFL_SAT }, + { 0x06, OV7120_DFL_BRIGHT }, + { 0x07, 0x00 }, + { 0x0c, 0x20 }, + { 0x0d, 0x20 }, + { 0x11, 0x01 }, + { 0x14, 0x84 }, + { 0x15, 0x01 }, + { 0x16, 0x03 }, + { 0x17, 0x2f }, + { 0x18, 0xcf }, + { 0x19, 0x06 }, + { 0x1a, 0xf5 }, + { 0x1b, 0x00 }, + { 0x20, 0x08 }, + { 0x21, 0x80 }, + { 0x22, 0x80 }, + { 0x23, 0x00 }, + { 0x26, 0xa0 }, + { 0x27, 0xfa }, + { 0x28, 0x20 }, /* DON'T set bit 6. It is for the OV7620 only */ + { 0x29, DFL_AUTO_EXP?0x00:0x80 }, + { 0x2a, 0x10 }, + { 0x2b, 0x00 }, + { 0x2c, 0x88 }, + { 0x2d, 0x95 }, + { 0x2e, 0x80 }, + { 0x2f, 0x44 }, + { 0x60, 0x20 }, + { 0x61, 0x02 }, + { 0x62, 0x5f }, + { 0x63, 0xd5 }, + { 0x64, 0x57 }, + { 0x65, 0x83 }, /* OV says "don't change this value" */ + { 0x66, 0x55 }, + { 0x67, 0x92 }, + { 0x68, 0xcf }, + { 0x69, 0x76 }, + { 0x6a, 0x22 }, + { 0x6b, 0xe2 }, + { 0x6c, 0x40 }, + { 0x6d, 0x48 }, + { 0x6e, 0x80 }, + { 0x6f, 0x0d }, + { 0x70, 0x89 }, + { 0x71, 0x00 }, + { 0x72, 0x14 }, + { 0x73, 0x54 }, + { 0x74, 0xa0 }, + { 0x75, 0x8e }, + { 0x76, 0x00 }, + { 0x77, 0xff }, + { 0x78, 0x80 }, + { 0x79, 0x80 }, + { 0x7a, 0x80 }, + { 0x7b, 0xe6 }, + { 0x7c, 0x00 }, + { 0x24, 0x3a }, + { 0x25, 0x60 }, + { 0xff, 0xff }, /* END MARKER */ +}; + +/* Settings for (color) OV7620 camera chip */ +static struct ovcamchip_regvals regvals_init_7620[] = { + { 0x12, 0x80 }, /* reset */ + { 0x00, OV7620_DFL_GAIN }, + { 0x01, 0x80 }, + { 0x02, 0x80 }, + { 0x03, OV7620_DFL_SAT }, + { 0x06, OV7620_DFL_BRIGHT }, + { 0x07, 0x00 }, + { 0x0c, 0x24 }, + { 0x0c, 0x24 }, + { 0x0d, 0x24 }, + { 0x11, 0x01 }, + { 0x12, 0x24 }, + { 0x13, DFL_AUTO_GAIN?0x01:0x00 }, + { 0x14, 0x84 }, + { 0x15, 0x01 }, + { 0x16, 0x03 }, + { 0x17, 0x2f }, + { 0x18, 0xcf }, + { 0x19, 0x06 }, + { 0x1a, 0xf5 }, + { 0x1b, 0x00 }, + { 0x20, 0x18 }, + { 0x21, 0x80 }, + { 0x22, 0x80 }, + { 0x23, 0x00 }, + { 0x26, 0xa2 }, + { 0x27, 0xea }, + { 0x28, 0x20 }, + { 0x29, DFL_AUTO_EXP?0x00:0x80 }, + { 0x2a, 0x10 }, + { 0x2b, 0x00 }, + { 0x2c, 0x88 }, + { 0x2d, 0x91 }, + { 0x2e, 0x80 }, + { 0x2f, 0x44 }, + { 0x60, 0x27 }, + { 0x61, 0x02 }, + { 0x62, 0x5f }, + { 0x63, 0xd5 }, + { 0x64, 0x57 }, + { 0x65, 0x83 }, + { 0x66, 0x55 }, + { 0x67, 0x92 }, + { 0x68, 0xcf }, + { 0x69, 0x76 }, + { 0x6a, 0x22 }, + { 0x6b, 0x00 }, + { 0x6c, 0x02 }, + { 0x6d, 0x44 }, + { 0x6e, 0x80 }, + { 0x6f, 0x1d }, + { 0x70, 0x8b }, + { 0x71, 0x00 }, + { 0x72, 0x14 }, + { 0x73, 0x54 }, + { 0x74, 0x00 }, + { 0x75, 0x8e }, + { 0x76, 0x00 }, + { 0x77, 0xff }, + { 0x78, 0x80 }, + { 0x79, 0x80 }, + { 0x7a, 0x80 }, + { 0x7b, 0xe2 }, + { 0x7c, 0x00 }, + { 0xff, 0xff }, /* END MARKER */ +}; + +/* Returns index into the specified look-up table, with 'n' elements, for which + * the value is greater than or equal to "val". If a match isn't found, (n-1) + * is returned. The entries in the table must be in ascending order. */ +static inline int ov7x20_lut_find(unsigned char lut[], int n, unsigned char val) +{ + int i = 0; + + while (lut[i] < val && i < n) + i++; + + return i; +} + +/* This initializes the OV7x20 camera chip and relevant variables. */ +static int ov7x20_init(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov7x20 *s; + int rc; + + DDEBUG(4, &c->dev, "entered"); + + if (ov->mono) + rc = ov_write_regvals(c, regvals_init_7120); + else + rc = ov_write_regvals(c, regvals_init_7620); + + if (rc < 0) + return rc; + + ov->spriv = s = kmalloc(sizeof *s, GFP_KERNEL); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof *s); + + s->auto_brt = 1; + s->auto_exp = DFL_AUTO_EXP; + s->auto_gain = DFL_AUTO_GAIN; + + return 0; +} + +static int ov7x20_free(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + + kfree(ov->spriv); + return 0; +} + +static int ov7x20_set_v4l1_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov7x20 *s = ov->spriv; + int rc; + int v = ctl->value; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + { + /* Use Y gamma control instead. Bit 0 enables it. */ + rc = ov_write(c, 0x64, ctab[v >> 12]); + break; + } + case OVCAMCHIP_CID_BRIGHT: + /* 7620 doesn't like manual changes when in auto mode */ + if (!s->auto_brt) + rc = ov_write(c, REG_BRT, v >> 8); + else + rc = 0; + break; + case OVCAMCHIP_CID_SAT: + rc = ov_write(c, REG_SAT, v >> 8); + break; + case OVCAMCHIP_CID_EXP: + if (!s->auto_exp) + rc = ov_write(c, REG_EXP, v); + else + rc = -EBUSY; + break; + case OVCAMCHIP_CID_FREQ: + { + int sixty = (v == 60); + + rc = ov_write_mask(c, 0x2a, sixty?0x00:0x80, 0x80); + if (rc < 0) + goto out; + + rc = ov_write(c, 0x2b, sixty?0x00:0xac); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x76, 0x01, 0x01); + break; + } + case OVCAMCHIP_CID_BANDFILT: + rc = ov_write_mask(c, 0x2d, v?0x04:0x00, 0x04); + s->bandfilt = v; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + rc = ov_write_mask(c, 0x2d, v?0x10:0x00, 0x10); + s->auto_brt = v; + break; + case OVCAMCHIP_CID_AUTOEXP: + rc = ov_write_mask(c, 0x13, v?0x01:0x00, 0x01); + s->auto_exp = v; + break; + case OVCAMCHIP_CID_BACKLIGHT: + { + rc = ov_write_mask(c, 0x68, v?0xe0:0xc0, 0xe0); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x29, v?0x08:0x00, 0x08); + if (rc < 0) + goto out; + + rc = ov_write_mask(c, 0x28, v?0x02:0x00, 0x02); + s->backlight = v; + break; + } + case OVCAMCHIP_CID_MIRROR: + rc = ov_write_mask(c, 0x12, v?0x40:0x00, 0x40); + s->mirror = v; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + +out: + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, v, rc); + return rc; +} + +static int ov7x20_get_v4l1_control(struct i2c_client *c, + struct ovcamchip_control *ctl) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + struct ov7x20 *s = ov->spriv; + int rc = 0; + unsigned char val = 0; + + switch (ctl->id) { + case OVCAMCHIP_CID_CONT: + rc = ov_read(c, 0x64, &val); + ctl->value = ov7x20_lut_find(ctab, 16, val) << 12; + break; + case OVCAMCHIP_CID_BRIGHT: + rc = ov_read(c, REG_BRT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_SAT: + rc = ov_read(c, REG_SAT, &val); + ctl->value = val << 8; + break; + case OVCAMCHIP_CID_EXP: + rc = ov_read(c, REG_EXP, &val); + ctl->value = val; + break; + case OVCAMCHIP_CID_BANDFILT: + ctl->value = s->bandfilt; + break; + case OVCAMCHIP_CID_AUTOBRIGHT: + ctl->value = s->auto_brt; + break; + case OVCAMCHIP_CID_AUTOEXP: + ctl->value = s->auto_exp; + break; + case OVCAMCHIP_CID_BACKLIGHT: + ctl->value = s->backlight; + break; + case OVCAMCHIP_CID_MIRROR: + ctl->value = s->mirror; + break; + default: + DDEBUG(2, &c->dev, "control not supported: %d", ctl->id); + return -EPERM; + } + + DDEBUG(3, &c->dev, "id=%d, arg=%d, rc=%d", ctl->id, ctl->value, rc); + return rc; +} + +static int ov7x20_mode_init(struct i2c_client *c, struct ovcamchip_window *win) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + int qvga = win->quarter; + + /******** QVGA-specific regs ********/ + ov_write_mask(c, 0x14, qvga?0x20:0x00, 0x20); + ov_write_mask(c, 0x28, qvga?0x00:0x20, 0x20); + ov_write(c, 0x24, qvga?0x20:0x3a); + ov_write(c, 0x25, qvga?0x30:0x60); + ov_write_mask(c, 0x2d, qvga?0x40:0x00, 0x40); + if (!ov->mono) + ov_write_mask(c, 0x67, qvga?0xf0:0x90, 0xf0); + ov_write_mask(c, 0x74, qvga?0x20:0x00, 0x20); + + /******** Clock programming ********/ + + ov_write(c, 0x11, win->clockdiv); + + return 0; +} + +static int ov7x20_set_window(struct i2c_client *c, struct ovcamchip_window *win) +{ + int ret, hwscale, vwscale; + + ret = ov7x20_mode_init(c, win); + if (ret < 0) + return ret; + + if (win->quarter) { + hwscale = 1; + vwscale = 0; + } else { + hwscale = 2; + vwscale = 1; + } + + ov_write(c, 0x17, HWSBASE + (win->x >> hwscale)); + ov_write(c, 0x18, HWEBASE + ((win->x + win->width) >> hwscale)); + ov_write(c, 0x19, VWSBASE + (win->y >> vwscale)); + ov_write(c, 0x1a, VWEBASE + ((win->y + win->height) >> vwscale)); + + return 0; +} + +static int ov7x20_command(struct i2c_client *c, unsigned int cmd, void *arg) +{ + switch (cmd) { + case OVCAMCHIP_CMD_S_CTRL: + return ov7x20_set_v4l1_control(c, arg); + case OVCAMCHIP_CMD_G_CTRL: + return ov7x20_get_v4l1_control(c, arg); + case OVCAMCHIP_CMD_S_MODE: + return ov7x20_set_window(c, arg); + default: + DDEBUG(2, &c->dev, "command not supported: %d", cmd); + return -ENOIOCTLCMD; + } +} + +struct ovcamchip_ops ov7x20_ops = { + .init = ov7x20_init, + .free = ov7x20_free, + .command = ov7x20_command, +}; diff --git a/drivers/media/video/ovcamchip/ovcamchip_core.c b/drivers/media/video/ovcamchip/ovcamchip_core.c new file mode 100644 index 000000000000..54dd5612d3b8 --- /dev/null +++ b/drivers/media/video/ovcamchip/ovcamchip_core.c @@ -0,0 +1,444 @@ +/* Shared Code for OmniVision Camera Chip Drivers + * + * Copyright (c) 2004 Mark McClelland <mark@alpha.dyndns.org> + * http://alpha.dyndns.org/ov511/ + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include "ovcamchip_priv.h" + +#define DRIVER_VERSION "v2.27 for Linux 2.6" +#define DRIVER_AUTHOR "Mark McClelland <mark@alpha.dyndns.org>" +#define DRIVER_DESC "OV camera chip I2C driver" + +#define PINFO(fmt, args...) printk(KERN_INFO "ovcamchip: " fmt "\n" , ## args); +#define PERROR(fmt, args...) printk(KERN_ERR "ovcamchip: " fmt "\n" , ## args); + +#ifdef DEBUG +int ovcamchip_debug = 0; +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, + "Debug level: 0=none, 1=inits, 2=warning, 3=config, 4=functions, 5=all"); +#endif + +/* By default, let bridge driver tell us if chip is monochrome. mono=0 + * will ignore that and always treat chips as color. mono=1 will force + * monochrome mode for all chips. */ +static int mono = -1; +module_param(mono, int, 0); +MODULE_PARM_DESC(mono, + "1=chips are monochrome (OVx1xx), 0=force color, -1=autodetect (default)"); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* Registers common to all chips, that are needed for detection */ +#define GENERIC_REG_ID_HIGH 0x1C /* manufacturer ID MSB */ +#define GENERIC_REG_ID_LOW 0x1D /* manufacturer ID LSB */ +#define GENERIC_REG_COM_I 0x29 /* misc ID bits */ + +extern struct ovcamchip_ops ov6x20_ops; +extern struct ovcamchip_ops ov6x30_ops; +extern struct ovcamchip_ops ov7x10_ops; +extern struct ovcamchip_ops ov7x20_ops; +extern struct ovcamchip_ops ov76be_ops; + +static char *chip_names[NUM_CC_TYPES] = { + [CC_UNKNOWN] = "Unknown chip", + [CC_OV76BE] = "OV76BE", + [CC_OV7610] = "OV7610", + [CC_OV7620] = "OV7620", + [CC_OV7620AE] = "OV7620AE", + [CC_OV6620] = "OV6620", + [CC_OV6630] = "OV6630", + [CC_OV6630AE] = "OV6630AE", + [CC_OV6630AF] = "OV6630AF", +}; + +/* Forward declarations */ +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* ----------------------------------------------------------------------- */ + +int ov_write_regvals(struct i2c_client *c, struct ovcamchip_regvals *rvals) +{ + int rc; + + while (rvals->reg != 0xff) { + rc = ov_write(c, rvals->reg, rvals->val); + if (rc < 0) + return rc; + rvals++; + } + + return 0; +} + +/* Writes bits at positions specified by mask to an I2C reg. Bits that are in + * the same position as 1's in "mask" are cleared and set to "value". Bits + * that are in the same position as 0's in "mask" are preserved, regardless + * of their respective state in "value". + */ +int ov_write_mask(struct i2c_client *c, + unsigned char reg, + unsigned char value, + unsigned char mask) +{ + int rc; + unsigned char oldval, newval; + + if (mask == 0xff) { + newval = value; + } else { + rc = ov_read(c, reg, &oldval); + if (rc < 0) + return rc; + + oldval &= (~mask); /* Clear the masked bits */ + value &= mask; /* Enforce mask on value */ + newval = oldval | value; /* Set the desired bits */ + } + + return ov_write(c, reg, newval); +} + +/* ----------------------------------------------------------------------- */ + +/* Reset the chip and ensure that I2C is synchronized. Returns <0 if failure. + */ +static int init_camchip(struct i2c_client *c) +{ + int i, success; + unsigned char high, low; + + /* Reset the chip */ + ov_write(c, 0x12, 0x80); + + /* Wait for it to initialize */ + msleep(150); + + for (i = 0, success = 0; i < I2C_DETECT_RETRIES && !success; i++) { + if (ov_read(c, GENERIC_REG_ID_HIGH, &high) >= 0) { + if (ov_read(c, GENERIC_REG_ID_LOW, &low) >= 0) { + if (high == 0x7F && low == 0xA2) { + success = 1; + continue; + } + } + } + + /* Reset the chip */ + ov_write(c, 0x12, 0x80); + + /* Wait for it to initialize */ + msleep(150); + + /* Dummy read to sync I2C */ + ov_read(c, 0x00, &low); + } + + if (!success) + return -EIO; + + PDEBUG(1, "I2C synced in %d attempt(s)", i); + + return 0; +} + +/* This detects the OV7610, OV7620, or OV76BE chip. */ +static int ov7xx0_detect(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + int rc; + unsigned char val; + + PDEBUG(4, ""); + + /* Detect chip (sub)type */ + rc = ov_read(c, GENERIC_REG_COM_I, &val); + if (rc < 0) { + PERROR("Error detecting ov7xx0 type"); + return rc; + } + + if ((val & 3) == 3) { + PINFO("Camera chip is an OV7610"); + ov->subtype = CC_OV7610; + } else if ((val & 3) == 1) { + rc = ov_read(c, 0x15, &val); + if (rc < 0) { + PERROR("Error detecting ov7xx0 type"); + return rc; + } + + if (val & 1) { + PINFO("Camera chip is an OV7620AE"); + /* OV7620 is a close enough match for now. There are + * some definite differences though, so this should be + * fixed */ + ov->subtype = CC_OV7620; + } else { + PINFO("Camera chip is an OV76BE"); + ov->subtype = CC_OV76BE; + } + } else if ((val & 3) == 0) { + PINFO("Camera chip is an OV7620"); + ov->subtype = CC_OV7620; + } else { + PERROR("Unknown camera chip version: %d", val & 3); + return -ENOSYS; + } + + if (ov->subtype == CC_OV76BE) + ov->sops = &ov76be_ops; + else if (ov->subtype == CC_OV7620) + ov->sops = &ov7x20_ops; + else + ov->sops = &ov7x10_ops; + + return 0; +} + +/* This detects the OV6620, OV6630, OV6630AE, or OV6630AF chip. */ +static int ov6xx0_detect(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + int rc; + unsigned char val; + + PDEBUG(4, ""); + + /* Detect chip (sub)type */ + rc = ov_read(c, GENERIC_REG_COM_I, &val); + if (rc < 0) { + PERROR("Error detecting ov6xx0 type"); + return -1; + } + + if ((val & 3) == 0) { + ov->subtype = CC_OV6630; + PINFO("Camera chip is an OV6630"); + } else if ((val & 3) == 1) { + ov->subtype = CC_OV6620; + PINFO("Camera chip is an OV6620"); + } else if ((val & 3) == 2) { + ov->subtype = CC_OV6630; + PINFO("Camera chip is an OV6630AE"); + } else if ((val & 3) == 3) { + ov->subtype = CC_OV6630; + PINFO("Camera chip is an OV6630AF"); + } + + if (ov->subtype == CC_OV6620) + ov->sops = &ov6x20_ops; + else + ov->sops = &ov6x30_ops; + + return 0; +} + +static int ovcamchip_detect(struct i2c_client *c) +{ + /* Ideally we would just try a single register write and see if it NAKs. + * That isn't possible since the OV518 can't report I2C transaction + * failures. So, we have to try to initialize the chip (i.e. reset it + * and check the ID registers) to detect its presence. */ + + /* Test for 7xx0 */ + PDEBUG(3, "Testing for 0V7xx0"); + c->addr = OV7xx0_SID; + if (init_camchip(c) < 0) { + /* Test for 6xx0 */ + PDEBUG(3, "Testing for 0V6xx0"); + c->addr = OV6xx0_SID; + if (init_camchip(c) < 0) { + return -ENODEV; + } else { + if (ov6xx0_detect(c) < 0) { + PERROR("Failed to init OV6xx0"); + return -EIO; + } + } + } else { + if (ov7xx0_detect(c) < 0) { + PERROR("Failed to init OV7xx0"); + return -EIO; + } + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int ovcamchip_attach(struct i2c_adapter *adap) +{ + int rc = 0; + struct ovcamchip *ov; + struct i2c_client *c; + + /* I2C is not a PnP bus, so we can never be certain that we're talking + * to the right chip. To prevent damage to EEPROMS and such, only + * attach to adapters that are known to contain OV camera chips. */ + + switch (adap->id) { + case (I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV511): + case (I2C_ALGO_SMBUS | I2C_HW_SMBUS_OV518): + case (I2C_ALGO_SMBUS | I2C_HW_SMBUS_OVFX2): + case (I2C_ALGO_SMBUS | I2C_HW_SMBUS_W9968CF): + PDEBUG(1, "Adapter ID 0x%06x accepted", adap->id); + break; + default: + PDEBUG(1, "Adapter ID 0x%06x rejected", adap->id); + return -ENODEV; + } + + c = kmalloc(sizeof *c, GFP_KERNEL); + if (!c) { + rc = -ENOMEM; + goto no_client; + } + memcpy(c, &client_template, sizeof *c); + c->adapter = adap; + strcpy(i2c_clientname(c), "OV????"); + + ov = kmalloc(sizeof *ov, GFP_KERNEL); + if (!ov) { + rc = -ENOMEM; + goto no_ov; + } + memset(ov, 0, sizeof *ov); + i2c_set_clientdata(c, ov); + + rc = ovcamchip_detect(c); + if (rc < 0) + goto error; + + strcpy(i2c_clientname(c), chip_names[ov->subtype]); + + PDEBUG(1, "Camera chip detection complete"); + + i2c_attach_client(c); + + return rc; +error: + kfree(ov); +no_ov: + kfree(c); +no_client: + PDEBUG(1, "returning %d", rc); + return rc; +} + +static int ovcamchip_detach(struct i2c_client *c) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + int rc; + + rc = ov->sops->free(c); + if (rc < 0) + return rc; + + i2c_detach_client(c); + + kfree(ov); + kfree(c); + return 0; +} + +static int ovcamchip_command(struct i2c_client *c, unsigned int cmd, void *arg) +{ + struct ovcamchip *ov = i2c_get_clientdata(c); + + if (!ov->initialized && + cmd != OVCAMCHIP_CMD_Q_SUBTYPE && + cmd != OVCAMCHIP_CMD_INITIALIZE) { + dev_err(&c->dev, "ERROR: Camera chip not initialized yet!\n"); + return -EPERM; + } + + switch (cmd) { + case OVCAMCHIP_CMD_Q_SUBTYPE: + { + *(int *)arg = ov->subtype; + return 0; + } + case OVCAMCHIP_CMD_INITIALIZE: + { + int rc; + + if (mono == -1) + ov->mono = *(int *)arg; + else + ov->mono = mono; + + if (ov->mono) { + if (ov->subtype != CC_OV7620) + dev_warn(&c->dev, "Warning: Monochrome not " + "implemented for this chip\n"); + else + dev_info(&c->dev, "Initializing chip as " + "monochrome\n"); + } + + rc = ov->sops->init(c); + if (rc < 0) + return rc; + + ov->initialized = 1; + return 0; + } + default: + return ov->sops->command(c, cmd, arg); + } +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "ovcamchip", + .id = I2C_DRIVERID_OVCAMCHIP, + .class = I2C_CLASS_CAM_DIGITAL, + .flags = I2C_DF_NOTIFY, + .attach_adapter = ovcamchip_attach, + .detach_client = ovcamchip_detach, + .command = ovcamchip_command, +}; + +static struct i2c_client client_template = { + I2C_DEVNAME("(unset)"), + .driver = &driver, +}; + +static int __init ovcamchip_init(void) +{ +#ifdef DEBUG + ovcamchip_debug = debug; +#endif + + PINFO(DRIVER_VERSION " : " DRIVER_DESC); + return i2c_add_driver(&driver); +} + +static void __exit ovcamchip_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(ovcamchip_init); +module_exit(ovcamchip_exit); diff --git a/drivers/media/video/ovcamchip/ovcamchip_priv.h b/drivers/media/video/ovcamchip/ovcamchip_priv.h new file mode 100644 index 000000000000..575e612a5546 --- /dev/null +++ b/drivers/media/video/ovcamchip/ovcamchip_priv.h @@ -0,0 +1,87 @@ +/* OmniVision* camera chip driver private definitions for core code and + * chip-specific code + * + * Copyright (c) 1999-2004 Mark McClelland + * + * 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. NO WARRANTY OF ANY KIND is expressed or implied. + * + * * OmniVision is a trademark of OmniVision Technologies, Inc. This driver + * is not sponsored or developed by them. + */ + +#ifndef __LINUX_OVCAMCHIP_PRIV_H +#define __LINUX_OVCAMCHIP_PRIV_H + +#include <media/ovcamchip.h> + +#ifdef DEBUG +extern int ovcamchip_debug; +#endif + +#define PDEBUG(level, fmt, args...) \ + if (ovcamchip_debug >= (level)) pr_debug("[%s:%d] " fmt "\n", \ + __FUNCTION__, __LINE__ , ## args) + +#define DDEBUG(level, dev, fmt, args...) \ + if (ovcamchip_debug >= (level)) dev_dbg(dev, "[%s:%d] " fmt "\n", \ + __FUNCTION__, __LINE__ , ## args) + +/* Number of times to retry chip detection. Increase this if you are getting + * "Failed to init camera chip" */ +#define I2C_DETECT_RETRIES 10 + +struct ovcamchip_regvals { + unsigned char reg; + unsigned char val; +}; + +struct ovcamchip_ops { + int (*init)(struct i2c_client *); + int (*free)(struct i2c_client *); + int (*command)(struct i2c_client *, unsigned int, void *); +}; + +struct ovcamchip { + struct ovcamchip_ops *sops; + void *spriv; /* Private data for OV7x10.c etc... */ + int subtype; /* = SEN_OV7610 etc... */ + int mono; /* Monochrome chip? (invalid until init) */ + int initialized; /* OVCAMCHIP_CMD_INITIALIZE was successful */ +}; + +/* --------------------------------- */ +/* I2C I/O */ +/* --------------------------------- */ + +static inline int ov_read(struct i2c_client *c, unsigned char reg, + unsigned char *value) +{ + int rc; + + rc = i2c_smbus_read_byte_data(c, reg); + *value = (unsigned char) rc; + return rc; +} + +static inline int ov_write(struct i2c_client *c, unsigned char reg, + unsigned char value ) +{ + return i2c_smbus_write_byte_data(c, reg, value); +} + +/* --------------------------------- */ +/* FUNCTION PROTOTYPES */ +/* --------------------------------- */ + +/* Functions in ovcamchip_core.c */ + +extern int ov_write_regvals(struct i2c_client *c, + struct ovcamchip_regvals *rvals); + +extern int ov_write_mask(struct i2c_client *c, unsigned char reg, + unsigned char value, unsigned char mask); + +#endif diff --git a/drivers/media/video/planb.c b/drivers/media/video/planb.c new file mode 100644 index 000000000000..b19c33434eaf --- /dev/null +++ b/drivers/media/video/planb.c @@ -0,0 +1,2303 @@ +/* + planb - PlanB frame grabber driver + + PlanB is used in the 7x00/8x00 series of PowerMacintosh + Computers as video input DMA controller. + + Copyright (C) 1998 Michel Lanners (mlan@cpu.lu) + + Based largely on the bttv driver by Ralph Metzler (rjkm@thp.uni-koeln.de) + + Additional debugging and coding by Takashi Oe (toe@unlserve.unl.edu) + + 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. +*/ + +/* $Id: planb.c,v 1.18 1999/05/02 17:36:34 mlan Exp $ */ + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/videodev.h> +#include <linux/wait.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/prom.h> +#include <asm/dbdma.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <asm/irq.h> +#include <asm/semaphore.h> + +#include "planb.h" +#include "saa7196.h" + +/* Would you mind for some ugly debugging? */ +#if 0 +#define DEBUG(x...) printk(KERN_DEBUG ## x) /* Debug driver */ +#else +#define DEBUG(x...) /* Don't debug driver */ +#endif + +#if 0 +#define IDEBUG(x...) printk(KERN_DEBUG ## x) /* Debug interrupt part */ +#else +#define IDEBUG(x...) /* Don't debug interrupt part */ +#endif + +/* Ever seen a Mac with more than 1 of these? */ +#define PLANB_MAX 1 + +static int planb_num; +static struct planb planbs[PLANB_MAX]; +static volatile struct planb_registers *planb_regs; + +static int def_norm = PLANB_DEF_NORM; /* default norm */ +static int video_nr = -1; + +MODULE_PARM(def_norm, "i"); +MODULE_PARM_DESC(def_norm, "Default startup norm (0=PAL, 1=NTSC, 2=SECAM)"); +MODULE_PARM(video_nr,"i"); +MODULE_LICENSE("GPL"); + + +/* ------------------ PlanB Exported Functions ------------------ */ +static long planb_write(struct video_device *, const char *, unsigned long, int); +static long planb_read(struct video_device *, char *, unsigned long, int); +static int planb_open(struct video_device *, int); +static void planb_close(struct video_device *); +static int planb_ioctl(struct video_device *, unsigned int, void *); +static int planb_init_done(struct video_device *); +static int planb_mmap(struct video_device *, const char *, unsigned long); +static void planb_irq(int, void *, struct pt_regs *); +static void release_planb(void); +int init_planbs(struct video_init *); + +/* ------------------ PlanB Internal Functions ------------------ */ +static int planb_prepare_open(struct planb *); +static void planb_prepare_close(struct planb *); +static void saa_write_reg(unsigned char, unsigned char); +static unsigned char saa_status(int, struct planb *); +static void saa_set(unsigned char, unsigned char, struct planb *); +static void saa_init_regs(struct planb *); +static int grabbuf_alloc(struct planb *); +static int vgrab(struct planb *, struct video_mmap *); +static void add_clip(struct planb *, struct video_clip *); +static void fill_cmd_buff(struct planb *); +static void cmd_buff(struct planb *); +static volatile struct dbdma_cmd *setup_grab_cmd(int, struct planb *); +static void overlay_start(struct planb *); +static void overlay_stop(struct planb *); +static inline void tab_cmd_dbdma(volatile struct dbdma_cmd *, unsigned short, + unsigned int); +static inline void tab_cmd_store(volatile struct dbdma_cmd *, unsigned int, + unsigned int); +static inline void tab_cmd_gen(volatile struct dbdma_cmd *, unsigned short, + unsigned short, unsigned int, unsigned int); +static int init_planb(struct planb *); +static int find_planb(void); +static void planb_pre_capture(int, int, struct planb *); +static volatile struct dbdma_cmd *cmd_geo_setup(volatile struct dbdma_cmd *, + int, int, int, int, int, struct planb *); +static inline void planb_dbdma_stop(volatile struct dbdma_regs *); +static unsigned int saa_geo_setup(int, int, int, int, struct planb *); +static inline int overlay_is_active(struct planb *); + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +static int grabbuf_alloc(struct planb *pb) +{ + int i, npage; + + npage = MAX_GBUFFERS * ((PLANB_MAX_FBUF / PAGE_SIZE + 1) +#ifndef PLANB_GSCANLINE + + MAX_LNUM +#endif /* PLANB_GSCANLINE */ + ); + if ((pb->rawbuf = (unsigned char**) kmalloc (npage + * sizeof(unsigned long), GFP_KERNEL)) == 0) + return -ENOMEM; + for (i = 0; i < npage; i++) { + pb->rawbuf[i] = (unsigned char *)__get_free_pages(GFP_KERNEL + |GFP_DMA, 0); + if (!pb->rawbuf[i]) + break; + SetPageReserved(virt_to_page(pb->rawbuf[i])); + } + if (i-- < npage) { + printk(KERN_DEBUG "PlanB: init_grab: grab buffer not allocated\n"); + for (; i > 0; i--) { + ClearPageReserved(virt_to_page(pb->rawbuf[i])); + free_pages((unsigned long)pb->rawbuf[i], 0); + } + kfree(pb->rawbuf); + return -ENOBUFS; + } + pb->rawbuf_size = npage; + return 0; +} + +/*****************************/ +/* Hardware access functions */ +/*****************************/ + +static void saa_write_reg(unsigned char addr, unsigned char val) +{ + planb_regs->saa_addr = addr; eieio(); + planb_regs->saa_regval = val; eieio(); + return; +} + +/* return status byte 0 or 1: */ +static unsigned char saa_status(int byte, struct planb *pb) +{ + saa_regs[pb->win.norm][SAA7196_STDC] = + (saa_regs[pb->win.norm][SAA7196_STDC] & ~2) | ((byte & 1) << 1); + saa_write_reg (SAA7196_STDC, saa_regs[pb->win.norm][SAA7196_STDC]); + + /* Let's wait 30msec for this one */ + msleep_interruptible(30); + + return (unsigned char)in_8 (&planb_regs->saa_status); +} + +static void saa_set(unsigned char addr, unsigned char val, struct planb *pb) +{ + if(saa_regs[pb->win.norm][addr] != val) { + saa_regs[pb->win.norm][addr] = val; + saa_write_reg (addr, val); + } + return; +} + +static void saa_init_regs(struct planb *pb) +{ + int i; + + for (i = 0; i < SAA7196_NUMREGS; i++) + saa_write_reg (i, saa_regs[pb->win.norm][i]); +} + +static unsigned int saa_geo_setup(int width, int height, int interlace, int bpp, + struct planb *pb) +{ + int ht, norm = pb->win.norm; + + switch(bpp) { + case 2: + /* RGB555+a 1x16-bit + 16-bit transparent */ + saa_regs[norm][SAA7196_FMTS] &= ~0x3; + break; + case 1: + case 4: + /* RGB888 1x24-bit + 8-bit transparent */ + saa_regs[norm][SAA7196_FMTS] &= ~0x1; + saa_regs[norm][SAA7196_FMTS] |= 0x2; + break; + default: + return -EINVAL; + } + ht = (interlace ? height / 2 : height); + saa_regs[norm][SAA7196_OUTPIX] = (unsigned char) (width & 0x00ff); + saa_regs[norm][SAA7196_HFILT] = (saa_regs[norm][SAA7196_HFILT] & ~0x3) + | (width >> 8 & 0x3); + saa_regs[norm][SAA7196_OUTLINE] = (unsigned char) (ht & 0xff); + saa_regs[norm][SAA7196_VYP] = (saa_regs[norm][SAA7196_VYP] & ~0x3) + | (ht >> 8 & 0x3); + /* feed both fields if interlaced, or else feed only even fields */ + saa_regs[norm][SAA7196_FMTS] = (interlace) ? + (saa_regs[norm][SAA7196_FMTS] & ~0x60) + : (saa_regs[norm][SAA7196_FMTS] | 0x60); + /* transparent mode; extended format enabled */ + saa_regs[norm][SAA7196_DPATH] |= 0x3; + + return 0; +} + +/***************************/ +/* DBDMA support functions */ +/***************************/ + +static inline void planb_dbdma_restart(volatile struct dbdma_regs *ch) +{ + out_le32(&ch->control, PLANB_CLR(RUN)); + out_le32(&ch->control, PLANB_SET(RUN|WAKE) | PLANB_CLR(PAUSE)); +} + +static inline void planb_dbdma_stop(volatile struct dbdma_regs *ch) +{ + int i = 0; + + out_le32(&ch->control, PLANB_CLR(RUN) | PLANB_SET(FLUSH)); + while((in_le32(&ch->status) == (ACTIVE | FLUSH)) && (i < 999)) { + IDEBUG("PlanB: waiting for DMA to stop\n"); + i++; + } +} + +static inline void tab_cmd_dbdma(volatile struct dbdma_cmd *ch, + unsigned short command, unsigned int cmd_dep) +{ + st_le16(&ch->command, command); + st_le32(&ch->cmd_dep, cmd_dep); +} + +static inline void tab_cmd_store(volatile struct dbdma_cmd *ch, + unsigned int phy_addr, unsigned int cmd_dep) +{ + st_le16(&ch->command, STORE_WORD | KEY_SYSTEM); + st_le16(&ch->req_count, 4); + st_le32(&ch->phy_addr, phy_addr); + st_le32(&ch->cmd_dep, cmd_dep); +} + +static inline void tab_cmd_gen(volatile struct dbdma_cmd *ch, + unsigned short command, unsigned short req_count, + unsigned int phy_addr, unsigned int cmd_dep) +{ + st_le16(&ch->command, command); + st_le16(&ch->req_count, req_count); + st_le32(&ch->phy_addr, phy_addr); + st_le32(&ch->cmd_dep, cmd_dep); +} + +static volatile struct dbdma_cmd *cmd_geo_setup( + volatile struct dbdma_cmd *c1, int width, int height, int interlace, + int bpp, int clip, struct planb *pb) +{ + int norm = pb->win.norm; + + if((saa_geo_setup(width, height, interlace, bpp, pb)) != 0) + return (volatile struct dbdma_cmd *)NULL; + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_FMTS); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_FMTS]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_DPATH); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_DPATH]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->even), + bpp | ((clip)? PLANB_CLIPMASK: 0)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->odd), + bpp | ((clip)? PLANB_CLIPMASK: 0)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_OUTPIX); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_OUTPIX]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_HFILT); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_HFILT]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_OUTLINE); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_OUTLINE]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_VYP); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_VYP]); + return c1; +} + +/******************************/ +/* misc. supporting functions */ +/******************************/ + +static inline void planb_lock(struct planb *pb) +{ + down(&pb->lock); +} + +static inline void planb_unlock(struct planb *pb) +{ + up(&pb->lock); +} + +/***************/ +/* Driver Core */ +/***************/ + +static int planb_prepare_open(struct planb *pb) +{ + int i, size; + + /* allocate memory for two plus alpha command buffers (size: max lines, + plus 40 commands handling, plus 1 alignment), plus dummy command buf, + plus clipmask buffer, plus frame grabbing status */ + size = (pb->tab_size*(2+MAX_GBUFFERS*TAB_FACTOR)+1+MAX_GBUFFERS + * PLANB_DUMMY)*sizeof(struct dbdma_cmd) + +(PLANB_MAXLINES*((PLANB_MAXPIXELS+7)& ~7))/8 + +MAX_GBUFFERS*sizeof(unsigned int); + if ((pb->priv_space = kmalloc (size, GFP_KERNEL)) == 0) + return -ENOMEM; + memset ((void *) pb->priv_space, 0, size); + pb->overlay_last1 = pb->ch1_cmd = (volatile struct dbdma_cmd *) + DBDMA_ALIGN (pb->priv_space); + pb->overlay_last2 = pb->ch2_cmd = pb->ch1_cmd + pb->tab_size; + pb->ch1_cmd_phys = virt_to_bus(pb->ch1_cmd); + pb->cap_cmd[0] = pb->ch2_cmd + pb->tab_size; + pb->pre_cmd[0] = pb->cap_cmd[0] + pb->tab_size * TAB_FACTOR; + for (i = 1; i < MAX_GBUFFERS; i++) { + pb->cap_cmd[i] = pb->pre_cmd[i-1] + PLANB_DUMMY; + pb->pre_cmd[i] = pb->cap_cmd[i] + pb->tab_size * TAB_FACTOR; + } + pb->frame_stat=(volatile unsigned int *)(pb->pre_cmd[MAX_GBUFFERS-1] + + PLANB_DUMMY); + pb->mask = (unsigned char *)(pb->frame_stat+MAX_GBUFFERS); + + pb->rawbuf = NULL; + pb->rawbuf_size = 0; + pb->grabbing = 0; + for (i = 0; i < MAX_GBUFFERS; i++) { + pb->frame_stat[i] = GBUFFER_UNUSED; + pb->gwidth[i] = 0; + pb->gheight[i] = 0; + pb->gfmt[i] = 0; + pb->gnorm_switch[i] = 0; +#ifndef PLANB_GSCANLINE + pb->lsize[i] = 0; + pb->lnum[i] = 0; +#endif /* PLANB_GSCANLINE */ + } + pb->gcount = 0; + pb->suspend = 0; + pb->last_fr = -999; + pb->prev_last_fr = -999; + + /* Reset DMA controllers */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + + return 0; +} + +static void planb_prepare_close(struct planb *pb) +{ + int i; + + /* make sure the dma's are idle */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + /* free kernel memory of command buffers */ + if(pb->priv_space != 0) { + kfree (pb->priv_space); + pb->priv_space = 0; + pb->cmd_buff_inited = 0; + } + if(pb->rawbuf) { + for (i = 0; i < pb->rawbuf_size; i++) { + ClearPageReserved(virt_to_page(pb->rawbuf[i])); + free_pages((unsigned long)pb->rawbuf[i], 0); + } + kfree(pb->rawbuf); + } + pb->rawbuf = NULL; +} + +/*****************************/ +/* overlay support functions */ +/*****************************/ + +static inline int overlay_is_active(struct planb *pb) +{ + unsigned int size = pb->tab_size * sizeof(struct dbdma_cmd); + unsigned int caddr = (unsigned)in_le32(&pb->planb_base->ch1.cmdptr); + + return (in_le32(&pb->overlay_last1->cmd_dep) == pb->ch1_cmd_phys) + && (caddr < (pb->ch1_cmd_phys + size)) + && (caddr >= (unsigned)pb->ch1_cmd_phys); +} + +static void overlay_start(struct planb *pb) +{ + + DEBUG("PlanB: overlay_start()\n"); + + if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) { + + DEBUG("PlanB: presumably, grabbing is in progress...\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + out_le32 (&pb->planb_base->ch2.cmdptr, + virt_to_bus(pb->ch2_cmd)); + planb_dbdma_restart(&pb->planb_base->ch2); + st_le16 (&pb->ch1_cmd->command, DBDMA_NOP); + tab_cmd_dbdma(pb->last_cmd[pb->last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->ch1_cmd)); + eieio(); + pb->prev_last_fr = pb->last_fr; + pb->last_fr = -2; + if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) { + IDEBUG("PlanB: became inactive " + "in the mean time... reactivating\n"); + planb_dbdma_stop(&pb->planb_base->ch1); + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->ch1_cmd)); + planb_dbdma_restart(&pb->planb_base->ch1); + } + } else { + + DEBUG("PlanB: currently idle, so can do whatever\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + st_le32 (&pb->planb_base->ch2.cmdptr, + virt_to_bus(pb->ch2_cmd)); + st_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->ch1_cmd)); + out_le16 (&pb->ch1_cmd->command, DBDMA_NOP); + planb_dbdma_restart(&pb->planb_base->ch2); + planb_dbdma_restart(&pb->planb_base->ch1); + pb->last_fr = -1; + } + return; +} + +static void overlay_stop(struct planb *pb) +{ + DEBUG("PlanB: overlay_stop()\n"); + + if(pb->last_fr == -1) { + + DEBUG("PlanB: no grabbing, it seems...\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + pb->last_fr = -999; + } else if(pb->last_fr == -2) { + unsigned int cmd_dep; + tab_cmd_dbdma(pb->cap_cmd[pb->prev_last_fr], DBDMA_STOP, 0); + eieio(); + cmd_dep = (unsigned int)in_le32(&pb->overlay_last1->cmd_dep); + if(overlay_is_active(pb)) { + + DEBUG("PlanB: overlay is currently active\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + if(cmd_dep != pb->ch1_cmd_phys) { + out_le32(&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->overlay_last1)); + planb_dbdma_restart(&pb->planb_base->ch1); + } + } + pb->last_fr = pb->prev_last_fr; + pb->prev_last_fr = -999; + } + return; +} + +static void suspend_overlay(struct planb *pb) +{ + int fr = -1; + struct dbdma_cmd last; + + DEBUG("PlanB: suspend_overlay: %d\n", pb->suspend); + + if(pb->suspend++) + return; + if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) { + if(pb->last_fr == -2) { + fr = pb->prev_last_fr; + memcpy(&last, (void*)pb->last_cmd[fr], sizeof(last)); + tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0); + } + if(overlay_is_active(pb)) { + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + pb->suspended.overlay = 1; + pb->suspended.frame = fr; + memcpy(&pb->suspended.cmd, &last, sizeof(last)); + return; + } + } + pb->suspended.overlay = 0; + pb->suspended.frame = fr; + memcpy(&pb->suspended.cmd, &last, sizeof(last)); + return; +} + +static void resume_overlay(struct planb *pb) +{ + + DEBUG("PlanB: resume_overlay: %d\n", pb->suspend); + + if(pb->suspend > 1) + return; + if(pb->suspended.frame != -1) { + memcpy((void*)pb->last_cmd[pb->suspended.frame], + &pb->suspended.cmd, sizeof(pb->suspended.cmd)); + } + if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) { + goto finish; + } + if(pb->suspended.overlay) { + + DEBUG("PlanB: overlay being resumed\n"); + + st_le16 (&pb->ch1_cmd->command, DBDMA_NOP); + st_le16 (&pb->ch2_cmd->command, DBDMA_NOP); + /* Set command buffer addresses */ + st_le32(&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->overlay_last1)); + out_le32(&pb->planb_base->ch2.cmdptr, + virt_to_bus(pb->overlay_last2)); + /* Start the DMA controller */ + out_le32 (&pb->planb_base->ch2.control, + PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE)); + out_le32 (&pb->planb_base->ch1.control, + PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE)); + } else if(pb->suspended.frame != -1) { + out_le32(&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->last_cmd[pb->suspended.frame])); + out_le32 (&pb->planb_base->ch1.control, + PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE)); + } + +finish: + pb->suspend--; + wake_up_interruptible(&pb->suspendq); +} + +static void add_clip(struct planb *pb, struct video_clip *clip) +{ + volatile unsigned char *base; + int xc = clip->x, yc = clip->y; + int wc = clip->width, hc = clip->height; + int ww = pb->win.width, hw = pb->win.height; + int x, y, xtmp1, xtmp2; + + DEBUG("PlanB: clip %dx%d+%d+%d\n", wc, hc, xc, yc); + + if(xc < 0) { + wc += xc; + xc = 0; + } + if(yc < 0) { + hc += yc; + yc = 0; + } + if(xc + wc > ww) + wc = ww - xc; + if(wc <= 0) /* Nothing to do */ + return; + if(yc + hc > hw) + hc = hw - yc; + + for (y = yc; y < yc+hc; y++) { + xtmp1=xc>>3; + xtmp2=(xc+wc)>>3; + base = pb->mask + y*96; + if(xc != 0 || wc >= 8) + *(base + xtmp1) &= (unsigned char)(0x00ff & + (0xff00 >> (xc&7))); + for (x = xtmp1 + 1; x < xtmp2; x++) { + *(base + x) = 0; + } + if(xc < (ww & ~0x7)) + *(base + xtmp2) &= (unsigned char)(0x00ff >> + ((xc+wc) & 7)); + } + + return; +} + +static void fill_cmd_buff(struct planb *pb) +{ + int restore = 0; + volatile struct dbdma_cmd last; + + DEBUG("PlanB: fill_cmd_buff()\n"); + + if(pb->overlay_last1 != pb->ch1_cmd) { + restore = 1; + last = *(pb->overlay_last1); + } + memset ((void *) pb->ch1_cmd, 0, 2 * pb->tab_size + * sizeof(struct dbdma_cmd)); + cmd_buff (pb); + if(restore) + *(pb->overlay_last1) = last; + if(pb->suspended.overlay) { + unsigned long jump_addr = in_le32(&pb->overlay_last1->cmd_dep); + if(jump_addr != pb->ch1_cmd_phys) { + int i; + + DEBUG("PlanB: adjusting ch1's jump address\n"); + + for(i = 0; i < MAX_GBUFFERS; i++) { + if(pb->need_pre_capture[i]) { + if(jump_addr == virt_to_bus(pb->pre_cmd[i])) + goto found; + } else { + if(jump_addr == virt_to_bus(pb->cap_cmd[i])) + goto found; + } + } + + DEBUG("PlanB: not found...\n"); + + goto out; +found: + if(pb->need_pre_capture[i]) + out_le32(&pb->pre_cmd[i]->phy_addr, + virt_to_bus(pb->overlay_last1)); + else + out_le32(&pb->cap_cmd[i]->phy_addr, + virt_to_bus(pb->overlay_last1)); + } + } +out: + pb->cmd_buff_inited = 1; + + return; +} + +static void cmd_buff(struct planb *pb) +{ + int i, bpp, count, nlines, stepsize, interlace; + unsigned long base, jump, addr_com, addr_dep; + volatile struct dbdma_cmd *c1 = pb->ch1_cmd; + volatile struct dbdma_cmd *c2 = pb->ch2_cmd; + + interlace = pb->win.interlace; + bpp = pb->win.bpp; + count = (bpp * ((pb->win.x + pb->win.width > pb->win.swidth) ? + (pb->win.swidth - pb->win.x) : pb->win.width)); + nlines = ((pb->win.y + pb->win.height > pb->win.sheight) ? + (pb->win.sheight - pb->win.y) : pb->win.height); + + /* Do video in: */ + + /* Preamble commands: */ + addr_com = virt_to_bus(c1); + addr_dep = virt_to_bus(&c1->cmd_dep); + tab_cmd_dbdma(c1++, DBDMA_NOP, 0); + jump = virt_to_bus(c1+16); /* 14 by cmd_geo_setup() and 2 for padding */ + if((c1 = cmd_geo_setup(c1, pb->win.width, pb->win.height, interlace, + bpp, 1, pb)) == NULL) { + printk(KERN_WARNING "PlanB: encountered serious problems\n"); + tab_cmd_dbdma(pb->ch1_cmd + 1, DBDMA_STOP, 0); + tab_cmd_dbdma(pb->ch2_cmd + 1, DBDMA_STOP, 0); + return; + } + tab_cmd_store(c1++, addr_com, (unsigned)(DBDMA_NOP | BR_ALWAYS) << 16); + tab_cmd_store(c1++, addr_dep, jump); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel), + PLANB_SET(FIELD_SYNC)); + /* (1) wait for field sync to be set */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + /* wait for field sync to be cleared */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + /* if not odd field, wait until field sync is set again */ + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++; + /* assert ch_sync to ch2 */ + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch2.control), + PLANB_SET(CH_SYNC)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + base = (pb->frame_buffer_phys + pb->offset + pb->win.y * (pb->win.bpl + + pb->win.pad) + pb->win.x * bpp); + + if (interlace) { + stepsize = 2; + jump = virt_to_bus(c1 + (nlines + 1) / 2); + } else { + stepsize = 1; + jump = virt_to_bus(c1 + nlines); + } + + /* even field data: */ + for (i=0; i < nlines; i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, + count, base + i * (pb->win.bpl + pb->win.pad), jump); + + /* For non-interlaced, we use even fields only */ + if (!interlace) + goto cmd_tab_data_end; + + /* Resync to odd field */ + /* (2) wait for field sync to be set */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + /* wait for field sync to be cleared */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + /* if not odd field, wait until field sync is set again */ + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++; + /* assert ch_sync to ch2 */ + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch2.control), + PLANB_SET(CH_SYNC)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + /* odd field data: */ + jump = virt_to_bus(c1 + nlines / 2); + for (i=1; i < nlines; i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count, + base + i * (pb->win.bpl + pb->win.pad), jump); + + /* And jump back to the start */ +cmd_tab_data_end: + pb->overlay_last1 = c1; /* keep a pointer to the last command */ + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->ch1_cmd)); + + /* Clipmask command buffer */ + + /* Preamble commands: */ + tab_cmd_dbdma(c2++, DBDMA_NOP, 0); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.wait_sel), + PLANB_SET(CH_SYNC)); + /* wait until ch1 asserts ch_sync */ + tab_cmd_dbdma(c2++, DBDMA_NOP | WAIT_IFCLR, 0); + /* clear ch_sync asserted by ch1 */ + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.control), + PLANB_CLR(CH_SYNC)); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.wait_sel), + PLANB_SET(FIELD_SYNC)); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel), + PLANB_SET(ODD_FIELD)); + + /* jump to end of even field if appropriate */ + /* this points to (interlace)? pos. C: pos. B */ + jump = (interlace) ? virt_to_bus(c2 + (nlines + 1) / 2 + 2): + virt_to_bus(c2 + nlines + 2); + /* if odd field, skip over to odd field clipmasking */ + tab_cmd_dbdma(c2++, DBDMA_NOP | BR_IFSET, jump); + + /* even field mask: */ + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel), + PLANB_SET(DMA_ABORT)); + /* this points to pos. B */ + jump = (interlace) ? virt_to_bus(c2 + nlines + 1): + virt_to_bus(c2 + nlines); + base = virt_to_bus(pb->mask); + for (i=0; i < nlines; i += stepsize, c2++) + tab_cmd_gen(c2, OUTPUT_MORE | KEY_STREAM0 | BR_IFSET, 96, + base + i * 96, jump); + + /* For non-interlaced, we use only even fields */ + if(!interlace) + goto cmd_tab_mask_end; + + /* odd field mask: */ +/* C */ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel), + PLANB_SET(DMA_ABORT)); + /* this points to pos. B */ + jump = virt_to_bus(c2 + nlines / 2); + base = virt_to_bus(pb->mask); + for (i=1; i < nlines; i += 2, c2++) /* abort if set */ + tab_cmd_gen(c2, OUTPUT_MORE | KEY_STREAM0 | BR_IFSET, 96, + base + i * 96, jump); + + /* Inform channel 1 and jump back to start */ +cmd_tab_mask_end: + /* ok, I just realized this is kind of flawed. */ + /* this part is reached only after odd field clipmasking. */ + /* wanna clean up? */ + /* wait for field sync to be set */ + /* corresponds to fsync (1) of ch1 */ +/* B */ tab_cmd_dbdma(c2++, DBDMA_NOP | WAIT_IFCLR, 0); + /* restart ch1, meant to clear any dead bit or something */ + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch1.control), + PLANB_CLR(RUN)); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch1.control), + PLANB_SET(RUN)); + pb->overlay_last2 = c2; /* keep a pointer to the last command */ + /* start over even field clipmasking */ + tab_cmd_dbdma(c2, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->ch2_cmd)); + + eieio(); + return; +} + +/*********************************/ +/* grabdisplay support functions */ +/*********************************/ + +static int palette2fmt[] = { + 0, + PLANB_GRAY, + 0, + 0, + 0, + PLANB_COLOUR32, + PLANB_COLOUR15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +}; + +#define PLANB_PALETTE_MAX 15 + +static int vgrab(struct planb *pb, struct video_mmap *mp) +{ + unsigned int fr = mp->frame; + unsigned int format; + + if(pb->rawbuf==NULL) { + int err; + if((err=grabbuf_alloc(pb))) + return err; + } + + IDEBUG("PlanB: grab %d: %dx%d(%u)\n", pb->grabbing, + mp->width, mp->height, fr); + + if(pb->grabbing >= MAX_GBUFFERS) + return -ENOBUFS; + if(fr > (MAX_GBUFFERS - 1) || fr < 0) + return -EINVAL; + if(mp->height <= 0 || mp->width <= 0) + return -EINVAL; + if(mp->format < 0 || mp->format >= PLANB_PALETTE_MAX) + return -EINVAL; + if((format = palette2fmt[mp->format]) == 0) + return -EINVAL; + if (mp->height * mp->width * format > PLANB_MAX_FBUF) /* format = bpp */ + return -EINVAL; + + planb_lock(pb); + if(mp->width != pb->gwidth[fr] || mp->height != pb->gheight[fr] || + format != pb->gfmt[fr] || (pb->gnorm_switch[fr])) { + int i; +#ifndef PLANB_GSCANLINE + unsigned int osize = pb->gwidth[fr] * pb->gheight[fr] + * pb->gfmt[fr]; + unsigned int nsize = mp->width * mp->height * format; +#endif + + IDEBUG("PlanB: gwidth = %d, gheight = %d, mp->format = %u\n", + mp->width, mp->height, mp->format); + +#ifndef PLANB_GSCANLINE + if(pb->gnorm_switch[fr]) + nsize = 0; + if (nsize < osize) { + for(i = pb->gbuf_idx[fr]; osize > 0; i++) { + memset((void *)pb->rawbuf[i], 0, PAGE_SIZE); + osize -= PAGE_SIZE; + } + } + for(i = pb->l_fr_addr_idx[fr]; i < pb->l_fr_addr_idx[fr] + + pb->lnum[fr]; i++) + memset((void *)pb->rawbuf[i], 0, PAGE_SIZE); +#else +/* XXX TODO */ +/* + if(pb->gnorm_switch[fr]) + memset((void *)pb->gbuffer[fr], 0, + pb->gbytes_per_line * pb->gheight[fr]); + else { + if(mp-> + for(i = 0; i < pb->gheight[fr]; i++) { + memset((void *)(pb->gbuffer[fr] + + pb->gbytes_per_line * i + } + } +*/ +#endif + pb->gwidth[fr] = mp->width; + pb->gheight[fr] = mp->height; + pb->gfmt[fr] = format; + pb->last_cmd[fr] = setup_grab_cmd(fr, pb); + planb_pre_capture(fr, pb->gfmt[fr], pb); /* gfmt = bpp */ + pb->need_pre_capture[fr] = 1; + pb->gnorm_switch[fr] = 0; + } else + pb->need_pre_capture[fr] = 0; + pb->frame_stat[fr] = GBUFFER_GRABBING; + if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) { + + IDEBUG("PlanB: ch1 inactive, initiating grabbing\n"); + + planb_dbdma_stop(&pb->planb_base->ch1); + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->pre_cmd[fr])); + } else { + tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0); + tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0); + /* let's be on the safe side. here is not timing critical. */ + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), DBDMA_NOP, 0); + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->cap_cmd[fr])); + } + planb_dbdma_restart(&pb->planb_base->ch1); + pb->last_fr = fr; + } else { + int i; + + IDEBUG("PlanB: ch1 active, grabbing being queued\n"); + + if((pb->last_fr == -1) || ((pb->last_fr == -2) && + overlay_is_active(pb))) { + + IDEBUG("PlanB: overlay is active, grabbing defered\n"); + + tab_cmd_dbdma(pb->last_cmd[fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->ch1_cmd)); + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + tab_cmd_store(pb->pre_cmd[fr], + virt_to_bus(&pb->overlay_last1->cmd_dep), + virt_to_bus(pb->ch1_cmd)); + eieio(); + out_le32 (&pb->overlay_last1->cmd_dep, + virt_to_bus(pb->pre_cmd[fr])); + } else { + tab_cmd_store(pb->cap_cmd[fr], + virt_to_bus(&pb->overlay_last1->cmd_dep), + virt_to_bus(pb->ch1_cmd)); + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP, 0); + eieio(); + out_le32 (&pb->overlay_last1->cmd_dep, + virt_to_bus(pb->cap_cmd[fr])); + } + for(i = 0; overlay_is_active(pb) && i < 999; i++) + IDEBUG("PlanB: waiting for overlay done\n"); + tab_cmd_dbdma(pb->ch1_cmd, DBDMA_NOP, 0); + pb->prev_last_fr = fr; + pb->last_fr = -2; + } else if(pb->last_fr == -2) { + + IDEBUG("PlanB: mixed mode detected, grabbing" + " will be done before activating overlay\n"); + + tab_cmd_dbdma(pb->ch1_cmd, DBDMA_NOP, 0); + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + tab_cmd_dbdma(pb->last_cmd[pb->prev_last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->pre_cmd[fr])); + eieio(); + } else { + tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0); + if(pb->gwidth[pb->prev_last_fr] != + pb->gwidth[fr] + || pb->gheight[pb->prev_last_fr] != + pb->gheight[fr] + || pb->gfmt[pb->prev_last_fr] != + pb->gfmt[fr]) + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP, 0); + else + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr] + 16)); + tab_cmd_dbdma(pb->last_cmd[pb->prev_last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr])); + eieio(); + } + tab_cmd_dbdma(pb->last_cmd[fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->ch1_cmd)); + eieio(); + pb->prev_last_fr = fr; + pb->last_fr = -2; + } else { + + IDEBUG("PlanB: active grabbing session detected\n"); + + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + tab_cmd_dbdma(pb->last_cmd[pb->last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->pre_cmd[fr])); + eieio(); + } else { + tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0); + tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0); + if(pb->gwidth[pb->last_fr] != pb->gwidth[fr] + || pb->gheight[pb->last_fr] != + pb->gheight[fr] + || pb->gfmt[pb->last_fr] != + pb->gfmt[fr]) + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP, 0); + else + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr] + 16)); + tab_cmd_dbdma(pb->last_cmd[pb->last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr])); + eieio(); + } + pb->last_fr = fr; + } + if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) { + + IDEBUG("PlanB: became inactive in the mean time..." + "reactivating\n"); + + planb_dbdma_stop(&pb->planb_base->ch1); + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->cap_cmd[fr])); + planb_dbdma_restart(&pb->planb_base->ch1); + } + } + pb->grabbing++; + planb_unlock(pb); + + return 0; +} + +static void planb_pre_capture(int fr, int bpp, struct planb *pb) +{ + volatile struct dbdma_cmd *c1 = pb->pre_cmd[fr]; + int interlace = (pb->gheight[fr] > pb->maxlines/2)? 1: 0; + + tab_cmd_dbdma(c1++, DBDMA_NOP, 0); + if((c1 = cmd_geo_setup(c1, pb->gwidth[fr], pb->gheight[fr], interlace, + bpp, 0, pb)) == NULL) { + printk(KERN_WARNING "PlanB: encountered some problems\n"); + tab_cmd_dbdma(pb->pre_cmd[fr] + 1, DBDMA_STOP, 0); + return; + } + /* Sync to even field */ + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel), + PLANB_SET(FIELD_SYNC)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++; + tab_cmd_dbdma(c1++, DBDMA_NOP | INTR_ALWAYS, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + /* For non-interlaced, we use even fields only */ + if (pb->gheight[fr] <= pb->maxlines/2) + goto cmd_tab_data_end; + /* Sync to odd field */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++; + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); +cmd_tab_data_end: + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->cap_cmd[fr])); + + eieio(); +} + +static volatile struct dbdma_cmd *setup_grab_cmd(int fr, struct planb *pb) +{ + int i, bpp, count, nlines, stepsize, interlace; +#ifdef PLANB_GSCANLINE + int scanline; +#else + int nlpp, leftover1; + unsigned long base; +#endif + unsigned long jump; + int pagei; + volatile struct dbdma_cmd *c1; + volatile struct dbdma_cmd *jump_addr; + + c1 = pb->cap_cmd[fr]; + interlace = (pb->gheight[fr] > pb->maxlines/2)? 1: 0; + bpp = pb->gfmt[fr]; /* gfmt = bpp */ + count = bpp * pb->gwidth[fr]; + nlines = pb->gheight[fr]; +#ifdef PLANB_GSCANLINE + scanline = pb->gbytes_per_line; +#else + pb->lsize[fr] = count; + pb->lnum[fr] = 0; +#endif + + /* Do video in: */ + + /* Preamble commands: */ + tab_cmd_dbdma(c1++, DBDMA_NOP, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(c1 + 16)); c1++; + if((c1 = cmd_geo_setup(c1, pb->gwidth[fr], pb->gheight[fr], interlace, + bpp, 0, pb)) == NULL) { + printk(KERN_WARNING "PlanB: encountered serious problems\n"); + tab_cmd_dbdma(pb->cap_cmd[fr] + 1, DBDMA_STOP, 0); + return (pb->cap_cmd[fr] + 2); + } + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel), + PLANB_SET(FIELD_SYNC)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++; + tab_cmd_dbdma(c1++, DBDMA_NOP | INTR_ALWAYS, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + if (interlace) { + stepsize = 2; + jump_addr = c1 + TAB_FACTOR * (nlines + 1) / 2; + } else { + stepsize = 1; + jump_addr = c1 + TAB_FACTOR * nlines; + } + jump = virt_to_bus(jump_addr); + + /* even field data: */ + + pagei = pb->gbuf_idx[fr]; +#ifdef PLANB_GSCANLINE + for (i = 0; i < nlines; i += stepsize) { + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pagei + + i * scanline / PAGE_SIZE]), jump); + } +#else + i = 0; + leftover1 = 0; + do { + int j; + + base = virt_to_bus(pb->rawbuf[pagei]); + nlpp = (PAGE_SIZE - leftover1) / count / stepsize; + for(j = 0; j < nlpp && i < nlines; j++, i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, + count, base + count * j * stepsize + leftover1, jump); + if(i < nlines) { + int lov0 = PAGE_SIZE - count * nlpp * stepsize - leftover1; + + if(lov0 == 0) + leftover1 = 0; + else { + if(lov0 >= count) { + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, base + + count * nlpp * stepsize + leftover1, jump); + } else { + pb->l_to_addr[fr][pb->lnum[fr]] = pb->rawbuf[pagei] + + count * nlpp * stepsize + leftover1; + pb->l_to_next_idx[fr][pb->lnum[fr]] = pagei + 1; + pb->l_to_next_size[fr][pb->lnum[fr]] = count - lov0; + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pb->l_fr_addr_idx[fr] + + pb->lnum[fr]]), jump); + if(++pb->lnum[fr] > MAX_LNUM) + pb->lnum[fr]--; + } + leftover1 = count * stepsize - lov0; + i += stepsize; + } + } + pagei++; + } while(i < nlines); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, jump); + c1 = jump_addr; +#endif /* PLANB_GSCANLINE */ + + /* For non-interlaced, we use even fields only */ + if (!interlace) + goto cmd_tab_data_end; + + /* Sync to odd field */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++; + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + /* odd field data: */ + jump_addr = c1 + TAB_FACTOR * nlines / 2; + jump = virt_to_bus(jump_addr); +#ifdef PLANB_GSCANLINE + for (i = 1; i < nlines; i += stepsize) { + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pagei + + i * scanline / PAGE_SIZE]), jump); + } +#else + i = 1; + leftover1 = 0; + pagei = pb->gbuf_idx[fr]; + if(nlines <= 1) + goto skip; + do { + int j; + + base = virt_to_bus(pb->rawbuf[pagei]); + nlpp = (PAGE_SIZE - leftover1) / count / stepsize; + if(leftover1 >= count) { + tab_cmd_gen(c1++, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count, + base + leftover1 - count, jump); + i += stepsize; + } + for(j = 0; j < nlpp && i < nlines; j++, i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count, + base + count * (j * stepsize + 1) + leftover1, jump); + if(i < nlines) { + int lov0 = PAGE_SIZE - count * nlpp * stepsize - leftover1; + + if(lov0 == 0) + leftover1 = 0; + else { + if(lov0 > count) { + pb->l_to_addr[fr][pb->lnum[fr]] = pb->rawbuf[pagei] + + count * (nlpp * stepsize + 1) + leftover1; + pb->l_to_next_idx[fr][pb->lnum[fr]] = pagei + 1; + pb->l_to_next_size[fr][pb->lnum[fr]] = count * stepsize + - lov0; + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pb->l_fr_addr_idx[fr] + + pb->lnum[fr]]), jump); + if(++pb->lnum[fr] > MAX_LNUM) + pb->lnum[fr]--; + i += stepsize; + } + leftover1 = count * stepsize - lov0; + } + } + pagei++; + } while(i < nlines); +skip: + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, jump); + c1 = jump_addr; +#endif /* PLANB_GSCANLINE */ + +cmd_tab_data_end: + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->intr_stat), + (fr << 9) | PLANB_FRM_IRQ | PLANB_GEN_IRQ); + /* stop it */ + tab_cmd_dbdma(c1, DBDMA_STOP, 0); + + eieio(); + return c1; +} + +static void planb_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + unsigned int stat, astat; + struct planb *pb = (struct planb *)dev_id; + + IDEBUG("PlanB: planb_irq()\n"); + + /* get/clear interrupt status bits */ + eieio(); + stat = in_le32(&pb->planb_base->intr_stat); + astat = stat & pb->intr_mask; + out_le32(&pb->planb_base->intr_stat, PLANB_FRM_IRQ + & ~astat & stat & ~PLANB_GEN_IRQ); + IDEBUG("PlanB: stat = %X, astat = %X\n", stat, astat); + + if(astat & PLANB_FRM_IRQ) { + unsigned int fr = stat >> 9; +#ifndef PLANB_GSCANLINE + int i; +#endif + IDEBUG("PlanB: PLANB_FRM_IRQ\n"); + + pb->gcount++; + + IDEBUG("PlanB: grab %d: fr = %d, gcount = %d\n", + pb->grabbing, fr, pb->gcount); +#ifndef PLANB_GSCANLINE + IDEBUG("PlanB: %d * %d bytes are being copied over\n", + pb->lnum[fr], pb->lsize[fr]); + for(i = 0; i < pb->lnum[fr]; i++) { + int first = pb->lsize[fr] - pb->l_to_next_size[fr][i]; + + memcpy(pb->l_to_addr[fr][i], + pb->rawbuf[pb->l_fr_addr_idx[fr] + i], + first); + memcpy(pb->rawbuf[pb->l_to_next_idx[fr][i]], + pb->rawbuf[pb->l_fr_addr_idx[fr] + i] + first, + pb->l_to_next_size[fr][i]); + } +#endif + pb->frame_stat[fr] = GBUFFER_DONE; + pb->grabbing--; + wake_up_interruptible(&pb->capq); + return; + } + /* incorrect interrupts? */ + pb->intr_mask = PLANB_CLR_IRQ; + out_le32(&pb->planb_base->intr_stat, PLANB_CLR_IRQ); + printk(KERN_ERR "PlanB: IRQ lockup, cleared intrrupts" + " unconditionally\n"); +} + +/******************************* + * Device Operations functions * + *******************************/ + +static int planb_open(struct video_device *dev, int mode) +{ + struct planb *pb = (struct planb *)dev; + + if (pb->user == 0) { + int err; + if((err = planb_prepare_open(pb)) != 0) + return err; + } + pb->user++; + + DEBUG("PlanB: device opened\n"); + return 0; +} + +static void planb_close(struct video_device *dev) +{ + struct planb *pb = (struct planb *)dev; + + if(pb->user < 1) /* ??? */ + return; + planb_lock(pb); + if (pb->user == 1) { + if (pb->overlay) { + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + pb->overlay = 0; + } + planb_prepare_close(pb); + } + pb->user--; + planb_unlock(pb); + + DEBUG("PlanB: device closed\n"); +} + +static long planb_read(struct video_device *v, char *buf, unsigned long count, + int nonblock) +{ + DEBUG("planb: read request\n"); + return -EINVAL; +} + +static long planb_write(struct video_device *v, const char *buf, + unsigned long count, int nonblock) +{ + DEBUG("planb: write request\n"); + return -EINVAL; +} + +static int planb_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct planb *pb=(struct planb *)dev; + + switch (cmd) + { + case VIDIOCGCAP: + { + struct video_capability b; + + DEBUG("PlanB: IOCTL VIDIOCGCAP\n"); + + strcpy (b.name, pb->video_dev.name); + b.type = VID_TYPE_OVERLAY | VID_TYPE_CLIPPING | + VID_TYPE_FRAMERAM | VID_TYPE_SCALES | + VID_TYPE_CAPTURE; + b.channels = 2; /* composite & svhs */ + b.audios = 0; + b.maxwidth = PLANB_MAXPIXELS; + b.maxheight = PLANB_MAXLINES; + b.minwidth = 32; /* wild guess */ + b.minheight = 32; + if (copy_to_user(arg,&b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCSFBUF: + { + struct video_buffer v; + unsigned short bpp; + unsigned int fmt; + + DEBUG("PlanB: IOCTL VIDIOCSFBUF\n"); + + if (!capable(CAP_SYS_ADMIN) + || !capable(CAP_SYS_RAWIO)) + return -EPERM; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + planb_lock(pb); + switch(v.depth) { + case 8: + bpp = 1; + fmt = PLANB_GRAY; + break; + case 15: + case 16: + bpp = 2; + fmt = PLANB_COLOUR15; + break; + case 24: + case 32: + bpp = 4; + fmt = PLANB_COLOUR32; + break; + default: + planb_unlock(pb); + return -EINVAL; + } + if (bpp * v.width > v.bytesperline) { + planb_unlock(pb); + return -EINVAL; + } + pb->win.bpp = bpp; + pb->win.color_fmt = fmt; + pb->frame_buffer_phys = (unsigned long) v.base; + pb->win.sheight = v.height; + pb->win.swidth = v.width; + pb->picture.depth = pb->win.depth = v.depth; + pb->win.bpl = pb->win.bpp * pb->win.swidth; + pb->win.pad = v.bytesperline - pb->win.bpl; + + DEBUG("PlanB: Display at %p is %d by %d, bytedepth %d," + " bpl %d (+ %d)\n", v.base, v.width,v.height, + pb->win.bpp, pb->win.bpl, pb->win.pad); + + pb->cmd_buff_inited = 0; + if(pb->overlay) { + suspend_overlay(pb); + fill_cmd_buff(pb); + resume_overlay(pb); + } + planb_unlock(pb); + return 0; + } + case VIDIOCGFBUF: + { + struct video_buffer v; + + DEBUG("PlanB: IOCTL VIDIOCGFBUF\n"); + + v.base = (void *)pb->frame_buffer_phys; + v.height = pb->win.sheight; + v.width = pb->win.swidth; + v.depth = pb->win.depth; + v.bytesperline = pb->win.bpl + pb->win.pad; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + { + int i; + + if(copy_from_user(&i, arg, sizeof(i))) + return -EFAULT; + if(i==0) { + DEBUG("PlanB: IOCTL VIDIOCCAPTURE Stop\n"); + + if (!(pb->overlay)) + return 0; + planb_lock(pb); + pb->overlay = 0; + overlay_stop(pb); + planb_unlock(pb); + } else { + DEBUG("PlanB: IOCTL VIDIOCCAPTURE Start\n"); + + if (pb->frame_buffer_phys == 0 || + pb->win.width == 0 || + pb->win.height == 0) + return -EINVAL; + if (pb->overlay) + return 0; + planb_lock(pb); + pb->overlay = 1; + if(!(pb->cmd_buff_inited)) + fill_cmd_buff(pb); + overlay_start(pb); + planb_unlock(pb); + } + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + + DEBUG("PlanB: IOCTL VIDIOCGCHAN\n"); + + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + v.flags = 0; + v.tuners = 0; + v.type = VIDEO_TYPE_CAMERA; + v.norm = pb->win.norm; + switch(v.channel) + { + case 0: + strcpy(v.name,"Composite"); + break; + case 1: + strcpy(v.name,"SVHS"); + break; + default: + return -EINVAL; + break; + } + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel v; + + DEBUG("PlanB: IOCTL VIDIOCSCHAN\n"); + + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + if (v.norm != pb->win.norm) { + int i, maxlines; + + switch (v.norm) + { + case VIDEO_MODE_PAL: + case VIDEO_MODE_SECAM: + maxlines = PLANB_MAXLINES; + break; + case VIDEO_MODE_NTSC: + maxlines = PLANB_NTSC_MAXLINES; + break; + default: + return -EINVAL; + break; + } + planb_lock(pb); + /* empty the grabbing queue */ + wait_event(pb->capq, !pb->grabbing); + pb->maxlines = maxlines; + pb->win.norm = v.norm; + /* Stop overlay if running */ + suspend_overlay(pb); + for(i = 0; i < MAX_GBUFFERS; i++) + pb->gnorm_switch[i] = 1; + /* I know it's an overkill, but.... */ + fill_cmd_buff(pb); + /* ok, now init it accordingly */ + saa_init_regs (pb); + /* restart overlay if it was running */ + resume_overlay(pb); + planb_unlock(pb); + } + + switch(v.channel) + { + case 0: /* Composite */ + saa_set (SAA7196_IOCC, + ((saa_regs[pb->win.norm][SAA7196_IOCC] & + ~7) | 3), pb); + break; + case 1: /* SVHS */ + saa_set (SAA7196_IOCC, + ((saa_regs[pb->win.norm][SAA7196_IOCC] & + ~7) | 4), pb); + break; + default: + return -EINVAL; + break; + } + + return 0; + } + case VIDIOCGPICT: + { + struct video_picture vp = pb->picture; + + DEBUG("PlanB: IOCTL VIDIOCGPICT\n"); + + switch(pb->win.color_fmt) { + case PLANB_GRAY: + vp.palette = VIDEO_PALETTE_GREY; + case PLANB_COLOUR15: + vp.palette = VIDEO_PALETTE_RGB555; + break; + case PLANB_COLOUR32: + vp.palette = VIDEO_PALETTE_RGB32; + break; + default: + vp.palette = 0; + break; + } + + if(copy_to_user(arg,&vp,sizeof(vp))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture vp; + + DEBUG("PlanB: IOCTL VIDIOCSPICT\n"); + + if(copy_from_user(&vp,arg,sizeof(vp))) + return -EFAULT; + pb->picture = vp; + /* Should we do sanity checks here? */ + saa_set (SAA7196_BRIG, (unsigned char) + ((pb->picture.brightness) >> 8), pb); + saa_set (SAA7196_HUEC, (unsigned char) + ((pb->picture.hue) >> 8) ^ 0x80, pb); + saa_set (SAA7196_CSAT, (unsigned char) + ((pb->picture.colour) >> 9), pb); + saa_set (SAA7196_CONT, (unsigned char) + ((pb->picture.contrast) >> 9), pb); + + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip clip; + int i; + + DEBUG("PlanB: IOCTL VIDIOCSWIN\n"); + + if(copy_from_user(&vw,arg,sizeof(vw))) + return -EFAULT; + + planb_lock(pb); + /* Stop overlay if running */ + suspend_overlay(pb); + pb->win.interlace = (vw.height > pb->maxlines/2)? 1: 0; + if (pb->win.x != vw.x || + pb->win.y != vw.y || + pb->win.width != vw.width || + pb->win.height != vw.height || + !pb->cmd_buff_inited) { + pb->win.x = vw.x; + pb->win.y = vw.y; + pb->win.width = vw.width; + pb->win.height = vw.height; + fill_cmd_buff(pb); + } + /* Reset clip mask */ + memset ((void *) pb->mask, 0xff, (pb->maxlines + * ((PLANB_MAXPIXELS + 7) & ~7)) / 8); + /* Add any clip rects */ + for (i = 0; i < vw.clipcount; i++) { + if (copy_from_user(&clip, vw.clips + i, + sizeof(struct video_clip))) + return -EFAULT; + add_clip(pb, &clip); + } + /* restart overlay if it was running */ + resume_overlay(pb); + planb_unlock(pb); + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + + DEBUG("PlanB: IOCTL VIDIOCGWIN\n"); + + vw.x=pb->win.x; + vw.y=pb->win.y; + vw.width=pb->win.width; + vw.height=pb->win.height; + vw.chromakey=0; + vw.flags=0; + if(pb->win.interlace) + vw.flags|=VIDEO_WINDOW_INTERLACE; + if(copy_to_user(arg,&vw,sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCSYNC: { + int i; + + IDEBUG("PlanB: IOCTL VIDIOCSYNC\n"); + + if(copy_from_user((void *)&i,arg,sizeof(int))) + return -EFAULT; + + IDEBUG("PlanB: sync to frame %d\n", i); + + if(i > (MAX_GBUFFERS - 1) || i < 0) + return -EINVAL; +chk_grab: + switch (pb->frame_stat[i]) { + case GBUFFER_UNUSED: + return -EINVAL; + case GBUFFER_GRABBING: + IDEBUG("PlanB: waiting for grab" + " done (%d)\n", i); + interruptible_sleep_on(&pb->capq); + if(signal_pending(current)) + return -EINTR; + goto chk_grab; + case GBUFFER_DONE: + pb->frame_stat[i] = GBUFFER_UNUSED; + break; + } + return 0; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + volatile unsigned int status; + + IDEBUG("PlanB: IOCTL VIDIOCMCAPTURE\n"); + + if(copy_from_user((void *) &vm,(void *)arg,sizeof(vm))) + return -EFAULT; + status = pb->frame_stat[vm.frame]; + if (status != GBUFFER_UNUSED) + return -EBUSY; + + return vgrab(pb, &vm); + } + + case VIDIOCGMBUF: + { + int i; + struct video_mbuf vm; + + DEBUG("PlanB: IOCTL VIDIOCGMBUF\n"); + + memset(&vm, 0 , sizeof(vm)); + vm.size = PLANB_MAX_FBUF * MAX_GBUFFERS; + vm.frames = MAX_GBUFFERS; + for(i = 0; i<MAX_GBUFFERS; i++) + vm.offsets[i] = PLANB_MAX_FBUF * i; + if(copy_to_user((void *)arg, (void *)&vm, sizeof(vm))) + return -EFAULT; + return 0; + } + + case PLANBIOCGSAAREGS: + { + struct planb_saa_regs preg; + + DEBUG("PlanB: IOCTL PLANBIOCGSAAREGS\n"); + + if(copy_from_user(&preg, arg, sizeof(preg))) + return -EFAULT; + if(preg.addr >= SAA7196_NUMREGS) + return -EINVAL; + preg.val = saa_regs[pb->win.norm][preg.addr]; + if(copy_to_user((void *)arg, (void *)&preg, + sizeof(preg))) + return -EFAULT; + return 0; + } + + case PLANBIOCSSAAREGS: + { + struct planb_saa_regs preg; + + DEBUG("PlanB: IOCTL PLANBIOCSSAAREGS\n"); + + if(copy_from_user(&preg, arg, sizeof(preg))) + return -EFAULT; + if(preg.addr >= SAA7196_NUMREGS) + return -EINVAL; + saa_set (preg.addr, preg.val, pb); + return 0; + } + + case PLANBIOCGSTAT: + { + struct planb_stat_regs pstat; + + DEBUG("PlanB: IOCTL PLANBIOCGSTAT\n"); + + pstat.ch1_stat = in_le32(&pb->planb_base->ch1.status); + pstat.ch2_stat = in_le32(&pb->planb_base->ch2.status); + pstat.saa_stat0 = saa_status(0, pb); + pstat.saa_stat1 = saa_status(1, pb); + + if(copy_to_user((void *)arg, (void *)&pstat, + sizeof(pstat))) + return -EFAULT; + return 0; + } + + case PLANBIOCSMODE: { + int v; + + DEBUG("PlanB: IOCTL PLANBIOCSMODE\n"); + + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + switch(v) + { + case PLANB_TV_MODE: + saa_set (SAA7196_STDC, + (saa_regs[pb->win.norm][SAA7196_STDC] & + 0x7f), pb); + break; + case PLANB_VTR_MODE: + saa_set (SAA7196_STDC, + (saa_regs[pb->win.norm][SAA7196_STDC] | + 0x80), pb); + break; + default: + return -EINVAL; + break; + } + pb->win.mode = v; + return 0; + } + case PLANBIOCGMODE: { + int v=pb->win.mode; + + DEBUG("PlanB: IOCTL PLANBIOCGMODE\n"); + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } +#ifdef PLANB_GSCANLINE + case PLANBG_GRAB_BPL: { + int v=pb->gbytes_per_line; + + DEBUG("PlanB: IOCTL PLANBG_GRAB_BPL\n"); + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } +#endif /* PLANB_GSCANLINE */ + case PLANB_INTR_DEBUG: { + int i; + + DEBUG("PlanB: IOCTL PLANB_INTR_DEBUG\n"); + + if(copy_from_user(&i, arg, sizeof(i))) + return -EFAULT; + + /* avoid hang ups all together */ + for (i = 0; i < MAX_GBUFFERS; i++) { + if(pb->frame_stat[i] == GBUFFER_GRABBING) { + pb->frame_stat[i] = GBUFFER_DONE; + } + } + if(pb->grabbing) + pb->grabbing--; + wake_up_interruptible(&pb->capq); + return 0; + } + case PLANB_INV_REGS: { + int i; + struct planb_any_regs any; + + DEBUG("PlanB: IOCTL PLANB_INV_REGS\n"); + + if(copy_from_user(&any, arg, sizeof(any))) + return -EFAULT; + if(any.offset < 0 || any.offset + any.bytes > 0x400) + return -EINVAL; + if(any.bytes > 128) + return -EINVAL; + for (i = 0; i < any.bytes; i++) { + any.data[i] = + in_8((unsigned char *)pb->planb_base + + any.offset + i); + } + if(copy_to_user(arg,&any,sizeof(any))) + return -EFAULT; + return 0; + } + default: + { + DEBUG("PlanB: Unimplemented IOCTL\n"); + return -ENOIOCTLCMD; + } + /* Some IOCTLs are currently unsupported on PlanB */ + case VIDIOCGTUNER: { + DEBUG("PlanB: IOCTL VIDIOCGTUNER\n"); + goto unimplemented; } + case VIDIOCSTUNER: { + DEBUG("PlanB: IOCTL VIDIOCSTUNER\n"); + goto unimplemented; } + case VIDIOCSFREQ: { + DEBUG("PlanB: IOCTL VIDIOCSFREQ\n"); + goto unimplemented; } + case VIDIOCGFREQ: { + DEBUG("PlanB: IOCTL VIDIOCGFREQ\n"); + goto unimplemented; } + case VIDIOCKEY: { + DEBUG("PlanB: IOCTL VIDIOCKEY\n"); + goto unimplemented; } + case VIDIOCSAUDIO: { + DEBUG("PlanB: IOCTL VIDIOCSAUDIO\n"); + goto unimplemented; } + case VIDIOCGAUDIO: { + DEBUG("PlanB: IOCTL VIDIOCGAUDIO\n"); + goto unimplemented; } +unimplemented: + DEBUG(" Unimplemented\n"); + return -ENOIOCTLCMD; + } + return 0; +} + +static int planb_mmap(struct vm_area_struct *vma, struct video_device *dev, const char *adr, unsigned long size) +{ + int i; + struct planb *pb = (struct planb *)dev; + unsigned long start = (unsigned long)adr; + + if (size > MAX_GBUFFERS * PLANB_MAX_FBUF) + return -EINVAL; + if (!pb->rawbuf) { + int err; + if((err=grabbuf_alloc(pb))) + return err; + } + for (i = 0; i < pb->rawbuf_size; i++) { + unsigned long pfn; + + pfn = virt_to_phys((void *)pb->rawbuf[i]) >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start += PAGE_SIZE; + if (size <= PAGE_SIZE) + break; + size -= PAGE_SIZE; + } + return 0; +} + +static struct video_device planb_template= +{ + .owner = THIS_MODULE, + .name = PLANB_DEVICE_NAME, + .type = VID_TYPE_OVERLAY, + .hardware = VID_HARDWARE_PLANB, + .open = planb_open, + .close = planb_close, + .read = planb_read, + .write = planb_write, + .ioctl = planb_ioctl, + .mmap = planb_mmap, /* mmap? */ +}; + +static int init_planb(struct planb *pb) +{ + unsigned char saa_rev; + int i, result; + + memset ((void *) &pb->win, 0, sizeof (struct planb_window)); + /* Simple sanity check */ + if(def_norm >= NUM_SUPPORTED_NORM || def_norm < 0) { + printk(KERN_ERR "PlanB: Option(s) invalid\n"); + return -2; + } + pb->win.norm = def_norm; + pb->win.mode = PLANB_TV_MODE; /* TV mode */ + pb->win.interlace=1; + pb->win.x=0; + pb->win.y=0; + pb->win.width=768; /* 640 */ + pb->win.height=576; /* 480 */ + pb->maxlines=576; +#if 0 + btv->win.cropwidth=768; /* 640 */ + btv->win.cropheight=576; /* 480 */ + btv->win.cropx=0; + btv->win.cropy=0; +#endif + pb->win.pad=0; + pb->win.bpp=4; + pb->win.depth=32; + pb->win.color_fmt=PLANB_COLOUR32; + pb->win.bpl=1024*pb->win.bpp; + pb->win.swidth=1024; + pb->win.sheight=768; +#ifdef PLANB_GSCANLINE + if((pb->gbytes_per_line = PLANB_MAXPIXELS * 4) > PAGE_SIZE + || (pb->gbytes_per_line <= 0)) + return -3; + else { + /* page align pb->gbytes_per_line for DMA purpose */ + for(i = PAGE_SIZE; pb->gbytes_per_line < (i>>1);) + i>>=1; + pb->gbytes_per_line = i; + } +#endif + pb->tab_size = PLANB_MAXLINES + 40; + pb->suspend = 0; + init_MUTEX(&pb->lock); + pb->ch1_cmd = 0; + pb->ch2_cmd = 0; + pb->mask = 0; + pb->priv_space = 0; + pb->offset = 0; + pb->user = 0; + pb->overlay = 0; + init_waitqueue_head(&pb->suspendq); + pb->cmd_buff_inited = 0; + pb->frame_buffer_phys = 0; + + /* Reset DMA controllers */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + + saa_rev = (saa_status(0, pb) & 0xf0) >> 4; + printk(KERN_INFO "PlanB: SAA7196 video processor rev. %d\n", saa_rev); + /* Initialize the SAA registers in memory and on chip */ + saa_init_regs (pb); + + /* clear interrupt mask */ + pb->intr_mask = PLANB_CLR_IRQ; + + result = request_irq(pb->irq, planb_irq, 0, "PlanB", (void *)pb); + if (result < 0) { + if (result==-EINVAL) + printk(KERN_ERR "PlanB: Bad irq number (%d) " + "or handler\n", (int)pb->irq); + else if (result==-EBUSY) + printk(KERN_ERR "PlanB: I don't know why, " + "but IRQ %d is busy\n", (int)pb->irq); + return result; + } + disable_irq(pb->irq); + + /* Now add the template and register the device unit. */ + memcpy(&pb->video_dev,&planb_template,sizeof(planb_template)); + + pb->picture.brightness=0x90<<8; + pb->picture.contrast = 0x70 << 8; + pb->picture.colour = 0x70<<8; + pb->picture.hue = 0x8000; + pb->picture.whiteness = 0; + pb->picture.depth = pb->win.depth; + + pb->frame_stat=NULL; + init_waitqueue_head(&pb->capq); + for(i=0; i<MAX_GBUFFERS; i++) { + pb->gbuf_idx[i] = PLANB_MAX_FBUF * i / PAGE_SIZE; + pb->gwidth[i]=0; + pb->gheight[i]=0; + pb->gfmt[i]=0; + pb->cap_cmd[i]=NULL; +#ifndef PLANB_GSCANLINE + pb->l_fr_addr_idx[i] = MAX_GBUFFERS * (PLANB_MAX_FBUF + / PAGE_SIZE + 1) + MAX_LNUM * i; + pb->lsize[i] = 0; + pb->lnum[i] = 0; +#endif + } + pb->rawbuf=NULL; + pb->grabbing=0; + + /* enable interrupts */ + out_le32(&pb->planb_base->intr_stat, PLANB_CLR_IRQ); + pb->intr_mask = PLANB_FRM_IRQ; + enable_irq(pb->irq); + + if(video_register_device(&pb->video_dev, VFL_TYPE_GRABBER, video_nr)<0) + return -1; + + return 0; +} + +/* + * Scan for a PlanB controller, request the irq and map the io memory + */ + +static int find_planb(void) +{ + struct planb *pb; + struct device_node *planb_devices; + unsigned char dev_fn, confreg, bus; + unsigned int old_base, new_base; + unsigned int irq; + struct pci_dev *pdev; + int rc; + + if (_machine != _MACH_Pmac) + return 0; + + planb_devices = find_devices("planb"); + if (planb_devices == 0) { + planb_num=0; + printk(KERN_WARNING "PlanB: no device found!\n"); + return planb_num; + } + + if (planb_devices->next != NULL) + printk(KERN_ERR "Warning: only using first PlanB device!\n"); + pb = &planbs[0]; + planb_num = 1; + + if (planb_devices->n_addrs != 1) { + printk (KERN_WARNING "PlanB: expecting 1 address for planb " + "(got %d)", planb_devices->n_addrs); + return 0; + } + + if (planb_devices->n_intrs == 0) { + printk(KERN_WARNING "PlanB: no intrs for device %s\n", + planb_devices->full_name); + return 0; + } else { + irq = planb_devices->intrs[0].line; + } + + /* Initialize PlanB's PCI registers */ + + /* There is a bug with the way OF assigns addresses + to the devices behind the chaos bridge. + control needs only 0x1000 of space, but decodes only + the upper 16 bits. It therefore occupies a full 64K. + OF assigns the planb controller memory within this space; + so we need to change that here in order to access planb. */ + + /* We remap to 0xf1000000 in hope that nobody uses it ! */ + + bus = (planb_devices->addrs[0].space >> 16) & 0xff; + dev_fn = (planb_devices->addrs[0].space >> 8) & 0xff; + confreg = planb_devices->addrs[0].space & 0xff; + old_base = planb_devices->addrs[0].address; + new_base = 0xf1000000; + + 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); + if (!pdev) { + printk(KERN_ERR "planb: cannot find slot\n"); + goto err_out; + } + + /* Enable response in memory space, bus mastering, + use memory write and invalidate */ + rc = pci_enable_device(pdev); + if (rc) { + printk(KERN_ERR "planb: cannot enable PCI device %s\n", + pci_name(pdev)); + goto err_out; + } + rc = pci_set_mwi(pdev); + if (rc) { + printk(KERN_ERR "planb: cannot enable MWI on PCI device %s\n", + pci_name(pdev)); + goto err_out_disable; + } + pci_set_master(pdev); + + /* Set the new base address */ + pci_write_config_dword (pdev, confreg, new_base); + + planb_regs = (volatile struct planb_registers *) + ioremap (new_base, 0x400); + pb->planb_base = planb_regs; + pb->planb_base_phys = (struct planb_registers *)new_base; + pb->irq = irq; + + return planb_num; + +err_out_disable: + pci_disable_device(pdev); +err_out: + /* FIXME handle error */ /* comment moved from pci_find_slot, above */ + return 0; +} + +static void release_planb(void) +{ + int i; + struct planb *pb; + + for (i=0;i<planb_num; i++) + { + pb=&planbs[i]; + + /* stop and flash DMAs unconditionally */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + + /* clear and free interrupts */ + pb->intr_mask = PLANB_CLR_IRQ; + out_le32 (&pb->planb_base->intr_stat, PLANB_CLR_IRQ); + free_irq(pb->irq, pb); + + /* make sure all allocated memory are freed */ + planb_prepare_close(pb); + + printk(KERN_INFO "PlanB: unregistering with v4l\n"); + video_unregister_device(&pb->video_dev); + + /* note that iounmap() does nothing on the PPC right now */ + iounmap ((void *)pb->planb_base); + } +} + +static int __init init_planbs(void) +{ + int i; + + if (find_planb()<=0) + return -EIO; + + for (i=0; i<planb_num; i++) { + if (init_planb(&planbs[i])<0) { + printk(KERN_ERR "PlanB: error registering device %d" + " with v4l\n", i); + release_planb(); + return -EIO; + } + printk(KERN_INFO "PlanB: registered device %d with v4l\n", i); + } + return 0; +} + +static void __exit exit_planbs(void) +{ + release_planb(); +} + +module_init(init_planbs); +module_exit(exit_planbs); diff --git a/drivers/media/video/planb.h b/drivers/media/video/planb.h new file mode 100644 index 000000000000..8a0faad16118 --- /dev/null +++ b/drivers/media/video/planb.h @@ -0,0 +1,231 @@ +/* + planb - PlanB frame grabber driver + + PlanB is used in the 7x00/8x00 series of PowerMacintosh + Computers as video input DMA controller. + + Copyright (C) 1998 Michel Lanners (mlan@cpu.lu) + + Based largely on the bttv driver by Ralph Metzler (rjkm@thp.uni-koeln.de) + + Additional debugging and coding by Takashi Oe (toe@unlserve.unl.edu) + + + 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. +*/ + +/* $Id: planb.h,v 1.13 1999/05/03 19:28:56 mlan Exp $ */ + +#ifndef _PLANB_H_ +#define _PLANB_H_ + +#ifdef __KERNEL__ +#include <asm/dbdma.h> +#include "saa7196.h" +#endif /* __KERNEL__ */ + +#define PLANB_DEVICE_NAME "Apple PlanB Video-In" +#define PLANB_REV "1.0" + +#ifdef __KERNEL__ +//#define PLANB_GSCANLINE /* use this if apps have the notion of */ + /* grab buffer scanline */ +/* This should be safe for both PAL and NTSC */ +#define PLANB_MAXPIXELS 768 +#define PLANB_MAXLINES 576 +#define PLANB_NTSC_MAXLINES 480 + +/* Uncomment your preferred norm ;-) */ +#define PLANB_DEF_NORM VIDEO_MODE_PAL +//#define PLANB_DEF_NORM VIDEO_MODE_NTSC +//#define PLANB_DEF_NORM VIDEO_MODE_SECAM + +/* fields settings */ +#define PLANB_GRAY 0x1 /* 8-bit mono? */ +#define PLANB_COLOUR15 0x2 /* 16-bit mode */ +#define PLANB_COLOUR32 0x4 /* 32-bit mode */ +#define PLANB_CLIPMASK 0x8 /* hardware clipmasking */ + +/* misc. flags for PlanB DMA operation */ +#define CH_SYNC 0x1 /* synchronize channels (set by ch1; + cleared by ch2) */ +#define FIELD_SYNC 0x2 /* used for the start of each field + (0 -> 1 -> 0 for ch1; 0 -> 1 for ch2) */ +#define EVEN_FIELD 0x0 /* even field is detected if unset */ +#define DMA_ABORT 0x2 /* error or just out of sync if set */ +#define ODD_FIELD 0x4 /* odd field is detected if set */ + +/* for capture operations */ +#define MAX_GBUFFERS 2 +/* note PLANB_MAX_FBUF must be divisible by PAGE_SIZE */ +#ifdef PLANB_GSCANLINE +#define PLANB_MAX_FBUF 0x240000 /* 576 * 1024 * 4 */ +#define TAB_FACTOR (1) +#else +#define PLANB_MAX_FBUF 0x1b0000 /* 576 * 768 * 4 */ +#define TAB_FACTOR (2) +#endif +#endif /* __KERNEL__ */ + +struct planb_saa_regs { + unsigned char addr; + unsigned char val; +}; + +struct planb_stat_regs { + unsigned int ch1_stat; + unsigned int ch2_stat; + unsigned char saa_stat0; + unsigned char saa_stat1; +}; + +struct planb_any_regs { + unsigned int offset; + unsigned int bytes; + unsigned char data[128]; +}; + +/* planb private ioctls */ +#define PLANBIOCGSAAREGS _IOWR('v', BASE_VIDIOCPRIVATE, struct planb_saa_regs) /* Read a saa7196 reg value */ +#define PLANBIOCSSAAREGS _IOW('v', BASE_VIDIOCPRIVATE + 1, struct planb_saa_regs) /* Set a saa7196 reg value */ +#define PLANBIOCGSTAT _IOR('v', BASE_VIDIOCPRIVATE + 2, struct planb_stat_regs) /* Read planb status */ +#define PLANB_TV_MODE 1 +#define PLANB_VTR_MODE 2 +#define PLANBIOCGMODE _IOR('v', BASE_VIDIOCPRIVATE + 3, int) /* Get TV/VTR mode */ +#define PLANBIOCSMODE _IOW('v', BASE_VIDIOCPRIVATE + 4, int) /* Set TV/VTR mode */ + +#ifdef PLANB_GSCANLINE +#define PLANBG_GRAB_BPL _IOR('v', BASE_VIDIOCPRIVATE + 5, int) /* # of bytes per scanline in grab buffer */ +#endif + +/* call wake_up_interruptible() with appropriate actions */ +#define PLANB_INTR_DEBUG _IOW('v', BASE_VIDIOCPRIVATE + 20, int) +/* investigate which reg does what */ +#define PLANB_INV_REGS _IOWR('v', BASE_VIDIOCPRIVATE + 21, struct planb_any_regs) + +#ifdef __KERNEL__ + +/* Potentially useful macros */ +#define PLANB_SET(x) ((x) << 16 | (x)) +#define PLANB_CLR(x) ((x) << 16) + +/* This represents the physical register layout */ +struct planb_registers { + volatile struct dbdma_regs ch1; /* 0x00: video in */ + volatile unsigned int even; /* 0x40: even field setting */ + volatile unsigned int odd; /* 0x44; odd field setting */ + unsigned int pad1[14]; /* empty? */ + volatile struct dbdma_regs ch2; /* 0x80: clipmask out */ + unsigned int pad2[16]; /* 0xc0: empty? */ + volatile unsigned int reg3; /* 0x100: ???? */ + volatile unsigned int intr_stat; /* 0x104: irq status */ +#define PLANB_CLR_IRQ 0x00 /* clear Plan B interrupt */ +#define PLANB_GEN_IRQ 0x01 /* assert Plan B interrupt */ +#define PLANB_FRM_IRQ 0x0100 /* end of frame */ + unsigned int pad3[1]; /* empty? */ + volatile unsigned int reg5; /* 0x10c: ??? */ + unsigned int pad4[60]; /* empty? */ + volatile unsigned char saa_addr; /* 0x200: SAA subadr */ + char pad5[3]; + volatile unsigned char saa_regval; /* SAA7196 write reg. val */ + char pad6[3]; + volatile unsigned char saa_status; /* SAA7196 status byte */ + /* There is more unused stuff here */ +}; + +struct planb_window { + int x, y; + ushort width, height; + ushort bpp, bpl, depth, pad; + ushort swidth, sheight; + int norm; + int interlace; + u32 color_fmt; + int chromakey; + int mode; /* used to switch between TV/VTR modes */ +}; + +struct planb_suspend { + int overlay; + int frame; + struct dbdma_cmd cmd; +}; + +struct planb { + struct video_device video_dev; + struct video_picture picture; /* Current picture params */ + struct video_audio audio_dev; /* Current audio params */ + + volatile struct planb_registers *planb_base; /* virt base of planb */ + struct planb_registers *planb_base_phys; /* phys base of planb */ + void *priv_space; /* Org. alloc. mem for kfree */ + int user; + unsigned int tab_size; + int maxlines; + struct semaphore lock; + unsigned int irq; /* interrupt number */ + volatile unsigned int intr_mask; + + int overlay; /* overlay running? */ + struct planb_window win; + unsigned long frame_buffer_phys; /* We need phys for DMA */ + int offset; /* offset of pixel 1 */ + volatile struct dbdma_cmd *ch1_cmd; /* Video In DMA cmd buffer */ + volatile struct dbdma_cmd *ch2_cmd; /* Clip Out DMA cmd buffer */ + volatile struct dbdma_cmd *overlay_last1; + volatile struct dbdma_cmd *overlay_last2; + unsigned long ch1_cmd_phys; + volatile unsigned char *mask; /* Clipmask buffer */ + int suspend; + wait_queue_head_t suspendq; + struct planb_suspend suspended; + int cmd_buff_inited; /* cmd buffer inited? */ + + int grabbing; + unsigned int gcount; + wait_queue_head_t capq; + int last_fr; + int prev_last_fr; + unsigned char **rawbuf; + int rawbuf_size; + int gbuf_idx[MAX_GBUFFERS]; + volatile struct dbdma_cmd *cap_cmd[MAX_GBUFFERS]; + volatile struct dbdma_cmd *last_cmd[MAX_GBUFFERS]; + volatile struct dbdma_cmd *pre_cmd[MAX_GBUFFERS]; + int need_pre_capture[MAX_GBUFFERS]; +#define PLANB_DUMMY 40 /* # of command buf's allocated for pre-capture seq. */ + int gwidth[MAX_GBUFFERS], gheight[MAX_GBUFFERS]; + unsigned int gfmt[MAX_GBUFFERS]; + int gnorm_switch[MAX_GBUFFERS]; + volatile unsigned int *frame_stat; +#define GBUFFER_UNUSED 0x00U +#define GBUFFER_GRABBING 0x01U +#define GBUFFER_DONE 0x02U +#ifdef PLANB_GSCANLINE + int gbytes_per_line; +#else +#define MAX_LNUM 431 /* change this if PLANB_MAXLINES or */ + /* PLANB_MAXPIXELS changes */ + int l_fr_addr_idx[MAX_GBUFFERS]; + unsigned char *l_to_addr[MAX_GBUFFERS][MAX_LNUM]; + int l_to_next_idx[MAX_GBUFFERS][MAX_LNUM]; + int l_to_next_size[MAX_GBUFFERS][MAX_LNUM]; + int lsize[MAX_GBUFFERS], lnum[MAX_GBUFFERS]; +#endif +}; + +#endif /* __KERNEL__ */ + +#endif /* _PLANB_H_ */ diff --git a/drivers/media/video/pms.c b/drivers/media/video/pms.c new file mode 100644 index 000000000000..2504207b2e3d --- /dev/null +++ b/drivers/media/video/pms.c @@ -0,0 +1,1060 @@ +/* + * Media Vision Pro Movie Studio + * or + * "all you need is an I2C bus some RAM and a prayer" + * + * This draws heavily on code + * + * (c) Wolfgang Koehler, wolf@first.gmd.de, Dec. 1994 + * Kiefernring 15 + * 14478 Potsdam, Germany + * + * Most of this code is directly derived from his userspace driver. + * His driver works so send any reports to alan@redhat.com unless the + * userspace driver also doesn't work for you... + * + * Changes: + * 08/07/2003 Daniele Bellucci <bellucda@tiscali.it> + * - pms_capture: report back -EFAULT + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <asm/io.h> +#include <linux/sched.h> +#include <linux/videodev.h> +#include <asm/uaccess.h> + + +#define MOTOROLA 1 +#define PHILIPS2 2 +#define PHILIPS1 3 +#define MVVMEMORYWIDTH 0x40 /* 512 bytes */ + +struct pms_device +{ + struct video_device v; + struct video_picture picture; + int height; + int width; + struct semaphore lock; +}; + +struct i2c_info +{ + u8 slave; + u8 sub; + u8 data; + u8 hits; +}; + +static int i2c_count = 0; +static struct i2c_info i2cinfo[64]; + +static int decoder = PHILIPS2; +static int standard = 0; /* 0 - auto 1 - ntsc 2 - pal 3 - secam */ + +/* + * I/O ports and Shared Memory + */ + +static int io_port = 0x250; +static int data_port = 0x251; +static int mem_base = 0xC8000; +static void __iomem *mem; +static int video_nr = -1; + + + +static inline void mvv_write(u8 index, u8 value) +{ + outw(index|(value<<8), io_port); +} + +static inline u8 mvv_read(u8 index) +{ + outb(index, io_port); + return inb(data_port); +} + +static int pms_i2c_stat(u8 slave) +{ + int counter; + int i; + + outb(0x28, io_port); + + counter=0; + while((inb(data_port)&0x01)==0) + if(counter++==256) + break; + + while((inb(data_port)&0x01)!=0) + if(counter++==256) + break; + + outb(slave, io_port); + + counter=0; + while((inb(data_port)&0x01)==0) + if(counter++==256) + break; + + while((inb(data_port)&0x01)!=0) + if(counter++==256) + break; + + for(i=0;i<12;i++) + { + char st=inb(data_port); + if((st&2)!=0) + return -1; + if((st&1)==0) + break; + } + outb(0x29, io_port); + return inb(data_port); +} + +static int pms_i2c_write(u16 slave, u16 sub, u16 data) +{ + int skip=0; + int count; + int i; + + for(i=0;i<i2c_count;i++) + { + if((i2cinfo[i].slave==slave) && + (i2cinfo[i].sub == sub)) + { + if(i2cinfo[i].data==data) + skip=1; + i2cinfo[i].data=data; + i=i2c_count+1; + } + } + + if(i==i2c_count && i2c_count<64) + { + i2cinfo[i2c_count].slave=slave; + i2cinfo[i2c_count].sub=sub; + i2cinfo[i2c_count].data=data; + i2c_count++; + } + + if(skip) + return 0; + + mvv_write(0x29, sub); + mvv_write(0x2A, data); + mvv_write(0x28, slave); + + outb(0x28, io_port); + + count=0; + while((inb(data_port)&1)==0) + if(count>255) + break; + while((inb(data_port)&1)!=0) + if(count>255) + break; + + count=inb(data_port); + + if(count&2) + return -1; + return count; +} + +static int pms_i2c_read(int slave, int sub) +{ + int i=0; + for(i=0;i<i2c_count;i++) + { + if(i2cinfo[i].slave==slave && i2cinfo[i].sub==sub) + return i2cinfo[i].data; + } + return 0; +} + + +static void pms_i2c_andor(int slave, int sub, int and, int or) +{ + u8 tmp; + + tmp=pms_i2c_read(slave, sub); + tmp = (tmp&and)|or; + pms_i2c_write(slave, sub, tmp); +} + +/* + * Control functions + */ + + +static void pms_videosource(short source) +{ + mvv_write(0x2E, source?0x31:0x30); +} + +static void pms_hue(short hue) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, hue); + break; + case PHILIPS2: + pms_i2c_write(0x8A, 0x07, hue); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x07, hue); + break; + } +} + +static void pms_colour(short colour) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, colour); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x12, colour); + break; + } +} + + +static void pms_contrast(short contrast) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, contrast); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x13, contrast); + break; + } +} + +static void pms_brightness(short brightness) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, brightness); + pms_i2c_write(0x8A, 0x00, brightness); + pms_i2c_write(0x8A, 0x00, brightness); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x19, brightness); + break; + } +} + + +static void pms_format(short format) +{ + int target; + standard = format; + + if(decoder==PHILIPS1) + target=0x42; + else if(decoder==PHILIPS2) + target=0x8A; + else + return; + + switch(format) + { + case 0: /* Auto */ + pms_i2c_andor(target, 0x0D, 0xFE,0x00); + pms_i2c_andor(target, 0x0F, 0x3F,0x80); + break; + case 1: /* NTSC */ + pms_i2c_andor(target, 0x0D, 0xFE, 0x00); + pms_i2c_andor(target, 0x0F, 0x3F, 0x40); + break; + case 2: /* PAL */ + pms_i2c_andor(target, 0x0D, 0xFE, 0x00); + pms_i2c_andor(target, 0x0F, 0x3F, 0x00); + break; + case 3: /* SECAM */ + pms_i2c_andor(target, 0x0D, 0xFE, 0x01); + pms_i2c_andor(target, 0x0F, 0x3F, 0x00); + break; + } +} + +#ifdef FOR_FUTURE_EXPANSION + +/* + * These features of the PMS card are not currently exposes. They + * could become a private v4l ioctl for PMSCONFIG or somesuch if + * people need it. We also don't yet use the PMS interrupt. + */ + +static void pms_hstart(short start) +{ + switch(decoder) + { + case PHILIPS1: + pms_i2c_write(0x8A, 0x05, start); + pms_i2c_write(0x8A, 0x18, start); + break; + case PHILIPS2: + pms_i2c_write(0x42, 0x05, start); + pms_i2c_write(0x42, 0x18, start); + break; + } +} + +/* + * Bandpass filters + */ + +static void pms_bandpass(short pass) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xCF, (pass&0x03)<<4); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xCF, (pass&0x03)<<4); +} + +static void pms_antisnow(short snow) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xF3, (snow&0x03)<<2); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xF3, (snow&0x03)<<2); +} + +static void pms_sharpness(short sharp) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xFC, sharp&0x03); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xFC, sharp&0x03); +} + +static void pms_chromaagc(short agc) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x0C, 0x9F, (agc&0x03)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x0C, 0x9F, (agc&0x03)<<5); +} + +static void pms_vertnoise(short noise) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x10, 0xFC, noise&3); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x10, 0xFC, noise&3); +} + +static void pms_forcecolour(short colour) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x0C, 0x7F, (colour&1)<<7); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x0C, 0x7, (colour&1)<<7); +} + +static void pms_antigamma(short gamma) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0xB8, 0x00, 0x7F, (gamma&1)<<7); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x20, 0x7, (gamma&1)<<7); +} + +static void pms_prefilter(short filter) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xBF, (filter&1)<<6); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xBF, (filter&1)<<6); +} + +static void pms_hfilter(short filter) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0xB8, 0x04, 0x1F, (filter&7)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x24, 0x1F, (filter&7)<<5); +} + +static void pms_vfilter(short filter) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0xB8, 0x08, 0x9F, (filter&3)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x28, 0x9F, (filter&3)<<5); +} + +static void pms_killcolour(short colour) +{ + if(decoder==PHILIPS2) + { + pms_i2c_andor(0x8A, 0x08, 0x07, (colour&0x1F)<<3); + pms_i2c_andor(0x8A, 0x09, 0x07, (colour&0x1F)<<3); + } + else if(decoder==PHILIPS1) + { + pms_i2c_andor(0x42, 0x08, 0x07, (colour&0x1F)<<3); + pms_i2c_andor(0x42, 0x09, 0x07, (colour&0x1F)<<3); + } +} + +static void pms_chromagain(short chroma) +{ + if(decoder==PHILIPS2) + { + pms_i2c_write(0x8A, 0x11, chroma); + } + else if(decoder==PHILIPS1) + { + pms_i2c_write(0x42, 0x11, chroma); + } +} + + +static void pms_spacialcompl(short data) +{ + mvv_write(0x3B, data); +} + +static void pms_spacialcomph(short data) +{ + mvv_write(0x3A, data); +} + +static void pms_vstart(short start) +{ + mvv_write(0x16, start); + mvv_write(0x17, (start>>8)&0x01); +} + +#endif + +static void pms_secamcross(short cross) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x0F, 0xDF, (cross&1)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x0F, 0xDF, (cross&1)<<5); +} + + +static void pms_swsense(short sense) +{ + if(decoder==PHILIPS2) + { + pms_i2c_write(0x8A, 0x0A, sense); + pms_i2c_write(0x8A, 0x0B, sense); + } + else if(decoder==PHILIPS1) + { + pms_i2c_write(0x42, 0x0A, sense); + pms_i2c_write(0x42, 0x0B, sense); + } +} + + +static void pms_framerate(short frr) +{ + int fps=(standard==1)?30:25; + if(frr==0) + return; + fps=fps/frr; + mvv_write(0x14,0x80|fps); + mvv_write(0x15,1); +} + +static void pms_vert(u8 deciden, u8 decinum) +{ + mvv_write(0x1C, deciden); /* Denominator */ + mvv_write(0x1D, decinum); /* Numerator */ +} + +/* + * Turn 16bit ratios into best small ratio the chipset can grok + */ + +static void pms_vertdeci(unsigned short decinum, unsigned short deciden) +{ + /* Knock it down by /5 once */ + if(decinum%5==0) + { + deciden/=5; + decinum/=5; + } + /* + * 3's + */ + while(decinum%3==0 && deciden%3==0) + { + deciden/=3; + decinum/=3; + } + /* + * 2's + */ + while(decinum%2==0 && deciden%2==0) + { + decinum/=2; + deciden/=2; + } + /* + * Fudgyify + */ + while(deciden>32) + { + deciden/=2; + decinum=(decinum+1)/2; + } + if(deciden==32) + deciden--; + pms_vert(deciden,decinum); +} + +static void pms_horzdeci(short decinum, short deciden) +{ + if(decinum<=512) + { + if(decinum%5==0) + { + decinum/=5; + deciden/=5; + } + } + else + { + decinum=512; + deciden=640; /* 768 would be ideal */ + } + + while(((decinum|deciden)&1)==0) + { + decinum>>=1; + deciden>>=1; + } + while(deciden>32) + { + deciden>>=1; + decinum=(decinum+1)>>1; + } + if(deciden==32) + deciden--; + + mvv_write(0x24, 0x80|deciden); + mvv_write(0x25, decinum); +} + +static void pms_resolution(short width, short height) +{ + int fg_height; + + fg_height=height; + if(fg_height>280) + fg_height=280; + + mvv_write(0x18, fg_height); + mvv_write(0x19, fg_height>>8); + + if(standard==1) + { + mvv_write(0x1A, 0xFC); + mvv_write(0x1B, 0x00); + if(height>fg_height) + pms_vertdeci(240,240); + else + pms_vertdeci(fg_height,240); + } + else + { + mvv_write(0x1A, 0x1A); + mvv_write(0x1B, 0x01); + if(fg_height>256) + pms_vertdeci(270,270); + else + pms_vertdeci(fg_height, 270); + } + mvv_write(0x12,0); + mvv_write(0x13, MVVMEMORYWIDTH); + mvv_write(0x42, 0x00); + mvv_write(0x43, 0x00); + mvv_write(0x44, MVVMEMORYWIDTH); + + mvv_write(0x22, width+8); + mvv_write(0x23, (width+8)>> 8); + + if(standard==1) + pms_horzdeci(width,640); + else + pms_horzdeci(width+8, 768); + + mvv_write(0x30, mvv_read(0x30)&0xFE); + mvv_write(0x08, mvv_read(0x08)|0x01); + mvv_write(0x01, mvv_read(0x01)&0xFD); + mvv_write(0x32, 0x00); + mvv_write(0x33, MVVMEMORYWIDTH); +} + + +/* + * Set Input + */ + +static void pms_vcrinput(short input) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A,0x0D,0x7F,(input&1)<<7); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42,0x0D,0x7F,(input&1)<<7); +} + + +static int pms_capture(struct pms_device *dev, char __user *buf, int rgb555, int count) +{ + int y; + int dw = 2*dev->width; + + char tmp[dw+32]; /* using a temp buffer is faster than direct */ + int cnt = 0; + int len=0; + unsigned char r8 = 0x5; /* value for reg8 */ + + if (rgb555) + r8 |= 0x20; /* else use untranslated rgb = 565 */ + mvv_write(0x08,r8); /* capture rgb555/565, init DRAM, PC enable */ + +/* printf("%d %d %d %d %d %x %x\n",width,height,voff,nom,den,mvv_buf); */ + + for (y = 0; y < dev->height; y++ ) + { + writeb(0, mem); /* synchronisiert neue Zeile */ + + /* + * This is in truth a fifo, be very careful as if you + * forgot this odd things will occur 8) + */ + + memcpy_fromio(tmp, mem, dw+32); /* discard 16 word */ + cnt -= dev->height; + while (cnt <= 0) + { + /* + * Don't copy too far + */ + int dt=dw; + if(dt+len>count) + dt=count-len; + cnt += dev->height; + if (copy_to_user(buf, tmp+32, dt)) + return len ? len : -EFAULT; + buf += dt; + len += dt; + } + } + return len; +} + + +/* + * Video4linux interfacing + */ + +static int pms_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct pms_device *pd=(struct pms_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + strcpy(b->name, "Mediavision PMS"); + b->type = VID_TYPE_CAPTURE|VID_TYPE_SCALES; + b->channels = 4; + b->audios = 0; + b->maxwidth = 640; + b->maxheight = 480; + b->minwidth = 16; + b->minheight = 16; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + if(v->channel<0 || v->channel>3) + return -EINVAL; + v->flags=0; + v->tuners=1; + /* Good question.. its composite or SVHS so.. */ + v->type = VIDEO_TYPE_CAMERA; + switch(v->channel) + { + case 0: + strcpy(v->name, "Composite");break; + case 1: + strcpy(v->name, "SVideo");break; + case 2: + strcpy(v->name, "Composite(VCR)");break; + case 3: + strcpy(v->name, "SVideo(VCR)");break; + } + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if(v->channel<0 || v->channel>3) + return -EINVAL; + down(&pd->lock); + pms_videosource(v->channel&1); + pms_vcrinput(v->channel>>1); + up(&pd->lock); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + strcpy(v->name, "Format"); + v->rangelow=0; + v->rangehigh=0; + v->flags= VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + switch(standard) + { + case 0: + v->mode = VIDEO_MODE_AUTO; + break; + case 1: + v->mode = VIDEO_MODE_NTSC; + break; + case 2: + v->mode = VIDEO_MODE_PAL; + break; + case 3: + v->mode = VIDEO_MODE_SECAM; + break; + } + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + down(&pd->lock); + switch(v->mode) + { + case VIDEO_MODE_AUTO: + pms_framerate(25); + pms_secamcross(0); + pms_format(0); + break; + case VIDEO_MODE_NTSC: + pms_framerate(30); + pms_secamcross(0); + pms_format(1); + break; + case VIDEO_MODE_PAL: + pms_framerate(25); + pms_secamcross(0); + pms_format(2); + break; + case VIDEO_MODE_SECAM: + pms_framerate(25); + pms_secamcross(1); + pms_format(2); + break; + default: + up(&pd->lock); + return -EINVAL; + } + up(&pd->lock); + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + *p = pd->picture; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + if(!((p->palette==VIDEO_PALETTE_RGB565 && p->depth==16) + ||(p->palette==VIDEO_PALETTE_RGB555 && p->depth==15))) + return -EINVAL; + pd->picture= *p; + + /* + * Now load the card. + */ + + down(&pd->lock); + pms_brightness(p->brightness>>8); + pms_hue(p->hue>>8); + pms_colour(p->colour>>8); + pms_contrast(p->contrast>>8); + up(&pd->lock); + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + if(vw->flags) + return -EINVAL; + if(vw->clipcount) + return -EINVAL; + if(vw->height<16||vw->height>480) + return -EINVAL; + if(vw->width<16||vw->width>640) + return -EINVAL; + pd->width=vw->width; + pd->height=vw->height; + down(&pd->lock); + pms_resolution(pd->width, pd->height); + up(&pd->lock); /* Ok we figured out what to use from our wide choice */ + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + memset(vw,0,sizeof(*vw)); + vw->width=pd->width; + vw->height=pd->height; + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int pms_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, pms_do_ioctl); +} + +static ssize_t pms_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct pms_device *pd=(struct pms_device *)v; + int len; + + down(&pd->lock); + len=pms_capture(pd, buf, (pd->picture.depth==16)?0:1,count); + up(&pd->lock); + return len; +} + +static struct file_operations pms_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = pms_ioctl, + .read = pms_read, + .llseek = no_llseek, +}; + +static struct video_device pms_template= +{ + .owner = THIS_MODULE, + .name = "Mediavision PMS", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_PMS, + .fops = &pms_fops, +}; + +static struct pms_device pms_device; + + +/* + * Probe for and initialise the Mediavision PMS + */ + +static int init_mediavision(void) +{ + int id; + int idec, decst; + int i; + + unsigned char i2c_defs[]={ + 0x4C,0x30,0x00,0xE8, + 0xB6,0xE2,0x00,0x00, + 0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x78,0x98, + 0x00,0x00,0x00,0x00, + 0x34,0x0A,0xF4,0xCE, + 0xE4 + }; + + mem = ioremap(mem_base, 0x800); + if (!mem) + return -ENOMEM; + + if (!request_region(0x9A01, 1, "Mediavision PMS config")) + { + printk(KERN_WARNING "mediavision: unable to detect: 0x9A01 in use.\n"); + iounmap(mem); + return -EBUSY; + } + if (!request_region(io_port, 3, "Mediavision PMS")) + { + printk(KERN_WARNING "mediavision: I/O port %d in use.\n", io_port); + release_region(0x9A01, 1); + iounmap(mem); + return -EBUSY; + } + outb(0xB8, 0x9A01); /* Unlock */ + outb(io_port>>4, 0x9A01); /* Set IO port */ + + + id=mvv_read(3); + decst=pms_i2c_stat(0x43); + + if(decst!=-1) + idec=2; + else if(pms_i2c_stat(0xb9)!=-1) + idec=3; + else if(pms_i2c_stat(0x8b)!=-1) + idec=1; + else + idec=0; + + printk(KERN_INFO "PMS type is %d\n", idec); + if(idec == 0) { + release_region(io_port, 3); + release_region(0x9A01, 1); + iounmap(mem); + return -ENODEV; + } + + /* + * Ok we have a PMS of some sort + */ + + mvv_write(0x04, mem_base>>12); /* Set the memory area */ + + /* Ok now load the defaults */ + + for(i=0;i<0x19;i++) + { + if(i2c_defs[i]==0xFF) + pms_i2c_andor(0x8A, i, 0x07,0x00); + else + pms_i2c_write(0x8A, i, i2c_defs[i]); + } + + pms_i2c_write(0xB8,0x00,0x12); + pms_i2c_write(0xB8,0x04,0x00); + pms_i2c_write(0xB8,0x07,0x00); + pms_i2c_write(0xB8,0x08,0x00); + pms_i2c_write(0xB8,0x09,0xFF); + pms_i2c_write(0xB8,0x0A,0x00); + pms_i2c_write(0xB8,0x0B,0x10); + pms_i2c_write(0xB8,0x10,0x03); + + mvv_write(0x01, 0x00); + mvv_write(0x05, 0xA0); + mvv_write(0x08, 0x25); + mvv_write(0x09, 0x00); + mvv_write(0x0A, 0x20|MVVMEMORYWIDTH); + + mvv_write(0x10, 0x02); + mvv_write(0x1E, 0x0C); + mvv_write(0x1F, 0x03); + mvv_write(0x26, 0x06); + + mvv_write(0x2B, 0x00); + mvv_write(0x2C, 0x20); + mvv_write(0x2D, 0x00); + mvv_write(0x2F, 0x70); + mvv_write(0x32, 0x00); + mvv_write(0x33, MVVMEMORYWIDTH); + mvv_write(0x34, 0x00); + mvv_write(0x35, 0x00); + mvv_write(0x3A, 0x80); + mvv_write(0x3B, 0x10); + mvv_write(0x20, 0x00); + mvv_write(0x21, 0x00); + mvv_write(0x30, 0x22); + return 0; +} + +/* + * Initialization and module stuff + */ + +static int __init init_pms_cards(void) +{ + printk(KERN_INFO "Mediavision Pro Movie Studio driver 0.02\n"); + + data_port = io_port +1; + + if(init_mediavision()) + { + printk(KERN_INFO "Board not found.\n"); + return -ENODEV; + } + memcpy(&pms_device, &pms_template, sizeof(pms_template)); + init_MUTEX(&pms_device.lock); + pms_device.height=240; + pms_device.width=320; + pms_swsense(75); + pms_resolution(320,240); + return video_register_device((struct video_device *)&pms_device, VFL_TYPE_GRABBER, video_nr); +} + +module_param(io_port, int, 0); +module_param(mem_base, int, 0); +module_param(video_nr, int, 0); +MODULE_LICENSE("GPL"); + + +static void __exit shutdown_mediavision(void) +{ + release_region(io_port,3); + release_region(0x9A01, 1); +} + +static void __exit cleanup_pms_module(void) +{ + shutdown_mediavision(); + video_unregister_device((struct video_device *)&pms_device); + iounmap(mem); +} + +module_init(init_pms_cards); +module_exit(cleanup_pms_module); + diff --git a/drivers/media/video/saa5246a.c b/drivers/media/video/saa5246a.c new file mode 100644 index 000000000000..ba69f09cbdd1 --- /dev/null +++ b/drivers/media/video/saa5246a.c @@ -0,0 +1,843 @@ +/* + * Driver for the SAA5246A or SAA5281 Teletext (=Videotext) decoder chips from + * Philips. + * + * Only capturing of Teletext pages is tested. The videotext chips also have a + * TV output but my hardware doesn't use it. For this reason this driver does + * not support changing any TV display settings. + * + * Copyright (C) 2004 Michael Geng <linux@MichaelGeng.de> + * + * Derived from + * + * saa5249 driver + * Copyright (C) 1998 Richard Guenther + * <richard.guenther@student.uni-tuebingen.de> + * + * with changes by + * Alan Cox <Alan.Cox@linux.org> + * + * and + * + * vtx.c + * Copyright (C) 1994-97 Martin Buck <martin-2.buck@student.uni-ulm.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/videotext.h> +#include <linux/videodev.h> +#include "saa5246a.h" + +MODULE_AUTHOR("Michael Geng <linux@MichaelGeng.de>"); +MODULE_DESCRIPTION("Philips SAA5246A, SAA5281 Teletext decoder driver"); +MODULE_LICENSE("GPL"); + +struct saa5246a_device +{ + u8 pgbuf[NUM_DAUS][VTX_VIRTUALSIZE]; + int is_searching[NUM_DAUS]; + struct i2c_client *client; + struct semaphore lock; +}; + +static struct video_device saa_template; /* Declared near bottom */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { I2C_ADDRESS, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +static struct i2c_client client_template; + +static int saa5246a_attach(struct i2c_adapter *adap, int addr, int kind) +{ + int pgbuf; + int err; + struct i2c_client *client; + struct video_device *vd; + struct saa5246a_device *t; + + printk(KERN_INFO "saa5246a: teletext chip found.\n"); + client=kmalloc(sizeof(*client), GFP_KERNEL); + if(client==NULL) + return -ENOMEM; + client_template.adapter = adap; + client_template.addr = addr; + memcpy(client, &client_template, sizeof(*client)); + t = kmalloc(sizeof(*t), GFP_KERNEL); + if(t==NULL) + { + kfree(client); + return -ENOMEM; + } + memset(t, 0, sizeof(*t)); + strlcpy(client->name, IF_NAME, I2C_NAME_SIZE); + init_MUTEX(&t->lock); + + /* + * Now create a video4linux device + */ + + vd = video_device_alloc(); + if(vd==NULL) + { + kfree(t); + kfree(client); + return -ENOMEM; + } + i2c_set_clientdata(client, vd); + memcpy(vd, &saa_template, sizeof(*vd)); + + for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++) + { + memset(t->pgbuf[pgbuf], ' ', sizeof(t->pgbuf[0])); + t->is_searching[pgbuf] = FALSE; + } + vd->priv=t; + + + /* + * Register it + */ + + if((err=video_register_device(vd, VFL_TYPE_VTX,-1))<0) + { + kfree(t); + kfree(client); + video_device_release(vd); + return err; + } + t->client = client; + i2c_attach_client(client); + return 0; +} + +/* + * We do most of the hard work when we become a device on the i2c. + */ +static int saa5246a_probe(struct i2c_adapter *adap) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa5246a_attach); + return 0; +} + +static int saa5246a_detach(struct i2c_client *client) +{ + struct video_device *vd = i2c_get_clientdata(client); + i2c_detach_client(client); + video_unregister_device(vd); + kfree(vd->priv); + kfree(client); + return 0; +} + +static int saa5246a_command(struct i2c_client *device, unsigned int cmd, + void *arg) +{ + return -EINVAL; +} + +/* + * I2C interfaces + */ + +static struct i2c_driver i2c_driver_videotext = +{ + .owner = THIS_MODULE, + .name = IF_NAME, /* name */ + .id = I2C_DRIVERID_SAA5249, /* in i2c.h */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa5246a_probe, + .detach_client = saa5246a_detach, + .command = saa5246a_command +}; + +static struct i2c_client client_template = { + .driver = &i2c_driver_videotext, + .name = "(unset)", +}; + +static int i2c_sendbuf(struct saa5246a_device *t, int reg, int count, u8 *data) +{ + char buf[64]; + + buf[0] = reg; + memcpy(buf+1, data, count); + + if(i2c_master_send(t->client, buf, count+1)==count+1) + return 0; + return -1; +} + +static int i2c_senddata(struct saa5246a_device *t, ...) +{ + unsigned char buf[64]; + int v; + int ct=0; + va_list argp; + va_start(argp,t); + + while((v=va_arg(argp,int))!=-1) + buf[ct++]=v; + return i2c_sendbuf(t, buf[0], ct-1, buf+1); +} + +/* Get count number of bytes from I²C-device at address adr, store them in buf. + * Start & stop handshaking is done by this routine, ack will be sent after the + * last byte to inhibit further sending of data. If uaccess is TRUE, data is + * written to user-space with put_user. Returns -1 if I²C-device didn't send + * acknowledge, 0 otherwise + */ +static int i2c_getdata(struct saa5246a_device *t, int count, u8 *buf) +{ + if(i2c_master_recv(t->client, buf, count)!=count) + return -1; + return 0; +} + +/* When a page is found then the not FOUND bit in one of the status registers + * of the SAA5264A chip is cleared. Unfortunately this bit is not set + * automatically when a new page is requested. Instead this function must be + * called after a page has been requested. + * + * Return value: 0 if successful + */ +static int saa5246a_clear_found_bit(struct saa5246a_device *t, + unsigned char dau_no) +{ + unsigned char row_25_column_8; + + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + + dau_no | + R8_DO_NOT_CLEAR_MEMORY, + + R9_CURSER_ROW_25, + + R10_CURSER_COLUMN_8, + + COMMAND_END) || + i2c_getdata(t, 1, &row_25_column_8)) + { + return -EIO; + } + row_25_column_8 |= ROW25_COLUMN8_PAGE_NOT_FOUND; + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + + dau_no | + R8_DO_NOT_CLEAR_MEMORY, + + R9_CURSER_ROW_25, + + R10_CURSER_COLUMN_8, + + row_25_column_8, + + COMMAND_END)) + { + return -EIO; + } + + return 0; +} + +/* Requests one videotext page as described in req. The fields of req are + * checked and an error is returned if something is invalid. + * + * Return value: 0 if successful + */ +static int saa5246a_request_page(struct saa5246a_device *t, + vtx_pagereq_t *req) +{ + if (req->pagemask < 0 || req->pagemask >= PGMASK_MAX) + return -EINVAL; + if (req->pagemask & PGMASK_PAGE) + if (req->page < 0 || req->page > PAGE_MAX) + return -EINVAL; + if (req->pagemask & PGMASK_HOUR) + if (req->hour < 0 || req->hour > HOUR_MAX) + return -EINVAL; + if (req->pagemask & PGMASK_MINUTE) + if (req->minute < 0 || req->minute > MINUTE_MAX) + return -EINVAL; + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + + if (i2c_senddata(t, SAA5246A_REGISTER_R2, + + R2_IN_R3_SELECT_PAGE_HUNDREDS | + req->pgbuf << 4 | + R2_BANK_0 | + R2_HAMMING_CHECK_OFF, + + HUNDREDS_OF_PAGE(req->page) | + R3_HOLD_PAGE | + (req->pagemask & PG_HUND ? + R3_PAGE_HUNDREDS_DO_CARE : + R3_PAGE_HUNDREDS_DO_NOT_CARE), + + TENS_OF_PAGE(req->page) | + (req->pagemask & PG_TEN ? + R3_PAGE_TENS_DO_CARE : + R3_PAGE_TENS_DO_NOT_CARE), + + UNITS_OF_PAGE(req->page) | + (req->pagemask & PG_UNIT ? + R3_PAGE_UNITS_DO_CARE : + R3_PAGE_UNITS_DO_NOT_CARE), + + TENS_OF_HOUR(req->hour) | + (req->pagemask & HR_TEN ? + R3_HOURS_TENS_DO_CARE : + R3_HOURS_TENS_DO_NOT_CARE), + + UNITS_OF_HOUR(req->hour) | + (req->pagemask & HR_UNIT ? + R3_HOURS_UNITS_DO_CARE : + R3_HOURS_UNITS_DO_NOT_CARE), + + TENS_OF_MINUTE(req->minute) | + (req->pagemask & MIN_TEN ? + R3_MINUTES_TENS_DO_CARE : + R3_MINUTES_TENS_DO_NOT_CARE), + + UNITS_OF_MINUTE(req->minute) | + (req->pagemask & MIN_UNIT ? + R3_MINUTES_UNITS_DO_CARE : + R3_MINUTES_UNITS_DO_NOT_CARE), + + COMMAND_END) || i2c_senddata(t, SAA5246A_REGISTER_R2, + + R2_IN_R3_SELECT_PAGE_HUNDREDS | + req->pgbuf << 4 | + R2_BANK_0 | + R2_HAMMING_CHECK_OFF, + + HUNDREDS_OF_PAGE(req->page) | + R3_UPDATE_PAGE | + (req->pagemask & PG_HUND ? + R3_PAGE_HUNDREDS_DO_CARE : + R3_PAGE_HUNDREDS_DO_NOT_CARE), + + COMMAND_END)) + { + return -EIO; + } + + t->is_searching[req->pgbuf] = TRUE; + return 0; +} + +/* This routine decodes the page number from the infobits contained in line 25. + * + * Parameters: + * infobits: must be bits 0 to 9 of column 25 + * + * Return value: page number coded in hexadecimal, i. e. page 123 is coded 0x123 + */ +static inline int saa5246a_extract_pagenum_from_infobits( + unsigned char infobits[10]) +{ + int page_hundreds, page_tens, page_units; + + page_units = infobits[0] & ROW25_COLUMN0_PAGE_UNITS; + page_tens = infobits[1] & ROW25_COLUMN1_PAGE_TENS; + page_hundreds = infobits[8] & ROW25_COLUMN8_PAGE_HUNDREDS; + + /* page 0x.. means page 8.. */ + if (page_hundreds == 0) + page_hundreds = 8; + + return((page_hundreds << 8) | (page_tens << 4) | page_units); +} + +/* Decodes the hour from the infobits contained in line 25. + * + * Parameters: + * infobits: must be bits 0 to 9 of column 25 + * + * Return: hour coded in hexadecimal, i. e. 12h is coded 0x12 + */ +static inline int saa5246a_extract_hour_from_infobits( + unsigned char infobits[10]) +{ + int hour_tens, hour_units; + + hour_units = infobits[4] & ROW25_COLUMN4_HOUR_UNITS; + hour_tens = infobits[5] & ROW25_COLUMN5_HOUR_TENS; + + return((hour_tens << 4) | hour_units); +} + +/* Decodes the minutes from the infobits contained in line 25. + * + * Parameters: + * infobits: must be bits 0 to 9 of column 25 + * + * Return: minutes coded in hexadecimal, i. e. 10min is coded 0x10 + */ +static inline int saa5246a_extract_minutes_from_infobits( + unsigned char infobits[10]) +{ + int minutes_tens, minutes_units; + + minutes_units = infobits[2] & ROW25_COLUMN2_MINUTES_UNITS; + minutes_tens = infobits[3] & ROW25_COLUMN3_MINUTES_TENS; + + return((minutes_tens << 4) | minutes_units); +} + +/* Reads the status bits contained in the first 10 columns of the first line + * and extracts the information into info. + * + * Return value: 0 if successful + */ +static inline int saa5246a_get_status(struct saa5246a_device *t, + vtx_pageinfo_t *info, unsigned char dau_no) +{ + unsigned char infobits[10]; + int column; + + if (dau_no >= NUM_DAUS) + return -EINVAL; + + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + + dau_no | + R8_DO_NOT_CLEAR_MEMORY, + + R9_CURSER_ROW_25, + + R10_CURSER_COLUMN_0, + + COMMAND_END) || + i2c_getdata(t, 10, infobits)) + { + return -EIO; + } + + info->pagenum = saa5246a_extract_pagenum_from_infobits(infobits); + info->hour = saa5246a_extract_hour_from_infobits(infobits); + info->minute = saa5246a_extract_minutes_from_infobits(infobits); + info->charset = ((infobits[7] & ROW25_COLUMN7_CHARACTER_SET) >> 1); + info->delete = !!(infobits[3] & ROW25_COLUMN3_DELETE_PAGE); + info->headline = !!(infobits[5] & ROW25_COLUMN5_INSERT_HEADLINE); + info->subtitle = !!(infobits[5] & ROW25_COLUMN5_INSERT_SUBTITLE); + info->supp_header = !!(infobits[6] & ROW25_COLUMN6_SUPPRESS_HEADER); + info->update = !!(infobits[6] & ROW25_COLUMN6_UPDATE_PAGE); + info->inter_seq = !!(infobits[6] & ROW25_COLUMN6_INTERRUPTED_SEQUENCE); + info->dis_disp = !!(infobits[6] & ROW25_COLUMN6_SUPPRESS_DISPLAY); + info->serial = !!(infobits[7] & ROW25_COLUMN7_SERIAL_MODE); + info->notfound = !!(infobits[8] & ROW25_COLUMN8_PAGE_NOT_FOUND); + info->pblf = !!(infobits[9] & ROW25_COLUMN9_PAGE_BEING_LOOKED_FOR); + info->hamming = 0; + for (column = 0; column <= 7; column++) { + if (infobits[column] & ROW25_COLUMN0_TO_7_HAMMING_ERROR) { + info->hamming = 1; + break; + } + } + if (!info->hamming && !info->notfound) + t->is_searching[dau_no] = FALSE; + return 0; +} + +/* Reads 1 videotext page buffer of the SAA5246A. + * + * req is used both as input and as output. It contains information which part + * must be read. The videotext page is copied into req->buffer. + * + * Return value: 0 if successful + */ +static inline int saa5246a_get_page(struct saa5246a_device *t, + vtx_pagereq_t *req) +{ + int start, end, size; + char *buf; + int err; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS || + req->start < 0 || req->start > req->end || req->end >= VTX_PAGESIZE) + return -EINVAL; + + buf = kmalloc(VTX_PAGESIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Read "normal" part of page */ + err = -EIO; + + end = min(req->end, VTX_PAGESIZE - 1); + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + req->pgbuf | R8_DO_NOT_CLEAR_MEMORY, + ROW(req->start), COLUMN(req->start), COMMAND_END)) + goto out; + if (i2c_getdata(t, end - req->start + 1, buf)) + goto out; + err = -EFAULT; + if (copy_to_user(req->buffer, buf, end - req->start + 1)) + goto out; + + /* Always get the time from buffer 4, since this stupid SAA5246A only + * updates the currently displayed buffer... + */ + if (REQ_CONTAINS_TIME(req)) { + start = max(req->start, POS_TIME_START); + end = min(req->end, POS_TIME_END); + size = end - start + 1; + err = -EINVAL; + if (size < 0) + goto out; + err = -EIO; + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + R8_ACTIVE_CHAPTER_4 | R8_DO_NOT_CLEAR_MEMORY, + R9_CURSER_ROW_0, start, COMMAND_END)) + goto out; + if (i2c_getdata(t, size, buf)) + goto out; + err = -EFAULT; + if (copy_to_user(req->buffer + start - req->start, buf, size)) + goto out; + } + /* Insert the header from buffer 4 only, if acquisition circuit is still searching for a page */ + if (REQ_CONTAINS_HEADER(req) && t->is_searching[req->pgbuf]) { + start = max(req->start, POS_HEADER_START); + end = min(req->end, POS_HEADER_END); + size = end - start + 1; + err = -EINVAL; + if (size < 0) + goto out; + err = -EIO; + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + R8_ACTIVE_CHAPTER_4 | R8_DO_NOT_CLEAR_MEMORY, + R9_CURSER_ROW_0, start, COMMAND_END)) + goto out; + if (i2c_getdata(t, end - start + 1, buf)) + goto out; + err = -EFAULT; + if (copy_to_user(req->buffer + start - req->start, buf, size)) + goto out; + } + err = 0; +out: + kfree(buf); + return err; +} + +/* Stops the acquisition circuit given in dau_no. The page buffer associated + * with this acquisition circuit will no more be updated. The other daus are + * not affected. + * + * Return value: 0 if successful + */ +static inline int saa5246a_stop_dau(struct saa5246a_device *t, + unsigned char dau_no) +{ + if (dau_no >= NUM_DAUS) + return -EINVAL; + if (i2c_senddata(t, SAA5246A_REGISTER_R2, + + R2_IN_R3_SELECT_PAGE_HUNDREDS | + dau_no << 4 | + R2_BANK_0 | + R2_HAMMING_CHECK_OFF, + + R3_PAGE_HUNDREDS_0 | + R3_HOLD_PAGE | + R3_PAGE_HUNDREDS_DO_NOT_CARE, + + COMMAND_END)) + { + return -EIO; + } + t->is_searching[dau_no] = FALSE; + return 0; +} + +/* Handles ioctls defined in videotext.h + * + * Returns 0 if successful + */ +static int do_saa5246a_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t=vd->priv; + switch(cmd) + { + case VTXIOCGETINFO: + { + vtx_info_t *info = arg; + + info->version_major = MAJOR_VERSION; + info->version_minor = MINOR_VERSION; + info->numpages = NUM_DAUS; + return 0; + } + + case VTXIOCCLRPAGE: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + memset(t->pgbuf[req->pgbuf], ' ', sizeof(t->pgbuf[0])); + return 0; + } + + case VTXIOCCLRFOUND: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + return(saa5246a_clear_found_bit(t, req->pgbuf)); + } + + case VTXIOCPAGEREQ: + { + vtx_pagereq_t *req = arg; + + return(saa5246a_request_page(t, req)); + } + + case VTXIOCGETSTAT: + { + vtx_pagereq_t *req = arg; + vtx_pageinfo_t info; + int rval; + + if ((rval = saa5246a_get_status(t, &info, req->pgbuf))) + return rval; + if(copy_to_user(req->buffer, &info, + sizeof(vtx_pageinfo_t))) + return -EFAULT; + return 0; + } + + case VTXIOCGETPAGE: + { + vtx_pagereq_t *req = arg; + + return(saa5246a_get_page(t, req)); + } + + case VTXIOCSTOPDAU: + { + vtx_pagereq_t *req = arg; + + return(saa5246a_stop_dau(t, req->pgbuf)); + } + + case VTXIOCPUTPAGE: + case VTXIOCSETDISP: + case VTXIOCPUTSTAT: + return 0; + + case VTXIOCCLRCACHE: + { + return 0; + } + + case VTXIOCSETVIRT: + { + /* I do not know what "virtual mode" means */ + return 0; + } + } + return -EINVAL; +} + +/* + * Translates old vtx IOCTLs to new ones + * + * This keeps new kernel versions compatible with old userspace programs. + */ +static inline unsigned int vtx_fix_command(unsigned int cmd) +{ + switch (cmd) { + case VTXIOCGETINFO_OLD: + cmd = VTXIOCGETINFO; + break; + case VTXIOCCLRPAGE_OLD: + cmd = VTXIOCCLRPAGE; + break; + case VTXIOCCLRFOUND_OLD: + cmd = VTXIOCCLRFOUND; + break; + case VTXIOCPAGEREQ_OLD: + cmd = VTXIOCPAGEREQ; + break; + case VTXIOCGETSTAT_OLD: + cmd = VTXIOCGETSTAT; + break; + case VTXIOCGETPAGE_OLD: + cmd = VTXIOCGETPAGE; + break; + case VTXIOCSTOPDAU_OLD: + cmd = VTXIOCSTOPDAU; + break; + case VTXIOCPUTPAGE_OLD: + cmd = VTXIOCPUTPAGE; + break; + case VTXIOCSETDISP_OLD: + cmd = VTXIOCSETDISP; + break; + case VTXIOCPUTSTAT_OLD: + cmd = VTXIOCPUTSTAT; + break; + case VTXIOCCLRCACHE_OLD: + cmd = VTXIOCCLRCACHE; + break; + case VTXIOCSETVIRT_OLD: + cmd = VTXIOCSETVIRT; + break; + } + return cmd; +} + +/* + * Handle the locking + */ +static int saa5246a_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t = vd->priv; + int err; + + cmd = vtx_fix_command(cmd); + down(&t->lock); + err = video_usercopy(inode, file, cmd, arg, do_saa5246a_ioctl); + up(&t->lock); + return err; +} + +static int saa5246a_open(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t = vd->priv; + int err; + + err = video_exclusive_open(inode,file); + if (err < 0) + return err; + + if (t->client==NULL) { + err = -ENODEV; + goto fail; + } + + if (i2c_senddata(t, SAA5246A_REGISTER_R0, + + R0_SELECT_R11 | + R0_PLL_TIME_CONSTANT_LONG | + R0_ENABLE_nODD_EVEN_OUTPUT | + R0_ENABLE_HDR_POLL | + R0_DO_NOT_FORCE_nODD_EVEN_LOW_IF_PICTURE_DISPLAYED | + R0_NO_FREE_RUN_PLL | + R0_NO_AUTOMATIC_FASTEXT_PROMPT, + + R1_NON_INTERLACED_312_312_LINES | + R1_DEW | + R1_EXTENDED_PACKET_DISABLE | + R1_DAUS_ALL_ON | + R1_8_BITS_NO_PARITY | + R1_VCS_TO_SCS, + + COMMAND_END) || + i2c_senddata(t, SAA5246A_REGISTER_R4, + + /* We do not care much for the TV display but nevertheless we + * need the currently displayed page later because only on that + * page the time is updated. */ + R4_DISPLAY_PAGE_4, + + COMMAND_END)) + { + err = -EIO; + goto fail; + } + + return 0; + +fail: + video_exclusive_release(inode,file); + return err; +} + +static int saa5246a_release(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t = vd->priv; + + /* Stop all acquisition circuits. */ + i2c_senddata(t, SAA5246A_REGISTER_R1, + + R1_INTERLACED_312_AND_HALF_312_AND_HALF_LINES | + R1_DEW | + R1_EXTENDED_PACKET_DISABLE | + R1_DAUS_ALL_OFF | + R1_8_BITS_NO_PARITY | + R1_VCS_TO_SCS, + + COMMAND_END); + video_exclusive_release(inode,file); + return 0; +} + +static int __init init_saa_5246a (void) +{ + printk(KERN_INFO + "SAA5246A (or compatible) Teletext decoder driver version %d.%d\n", + MAJOR_VERSION, MINOR_VERSION); + return i2c_add_driver(&i2c_driver_videotext); +} + +static void __exit cleanup_saa_5246a (void) +{ + i2c_del_driver(&i2c_driver_videotext); +} + +module_init(init_saa_5246a); +module_exit(cleanup_saa_5246a); + +static struct file_operations saa_fops = { + .owner = THIS_MODULE, + .open = saa5246a_open, + .release = saa5246a_release, + .ioctl = saa5246a_ioctl, + .llseek = no_llseek, +}; + +static struct video_device saa_template = +{ + .owner = THIS_MODULE, + .name = IF_NAME, + .type = VID_TYPE_TELETEXT, + .hardware = VID_HARDWARE_SAA5249, + .fops = &saa_fops, + .release = video_device_release, + .minor = -1, +}; diff --git a/drivers/media/video/saa5246a.h b/drivers/media/video/saa5246a.h new file mode 100644 index 000000000000..7b91112304e0 --- /dev/null +++ b/drivers/media/video/saa5246a.h @@ -0,0 +1,364 @@ +/* + Driver for the SAA5246A or SAA5281 Teletext (=Videotext) decoder chips from + Philips. + + Copyright (C) 2004 Michael Geng (linux@MichaelGeng.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +#ifndef __SAA5246A_H__ +#define __SAA5246A_H__ + +#define MAJOR_VERSION 1 /* driver major version number */ +#define MINOR_VERSION 8 /* driver minor version number */ + +#define IF_NAME "SAA5246A" + +#define I2C_ADDRESS 17 + +/* Number of DAUs = number of pages that can be searched at the same time. */ +#define NUM_DAUS 4 + +#define NUM_ROWS_PER_PAGE 40 + +/* first column is 0 (not 1) */ +#define POS_TIME_START 32 +#define POS_TIME_END 39 + +#define POS_HEADER_START 7 +#define POS_HEADER_END 31 + +/* Returns TRUE if the part of the videotext page described with req contains + (at least parts of) the time field */ +#define REQ_CONTAINS_TIME(p_req) \ + ((p_req)->start <= POS_TIME_END && \ + (p_req)->end >= POS_TIME_START) + +/* Returns TRUE if the part of the videotext page described with req contains + (at least parts of) the page header */ +#define REQ_CONTAINS_HEADER(p_req) \ + ((p_req)->start <= POS_HEADER_END && \ + (p_req)->end >= POS_HEADER_START) + +#ifndef FALSE +#define FALSE 0 +#define TRUE 1 +#endif + +/*****************************************************************************/ +/* Mode register numbers of the SAA5246A */ +/*****************************************************************************/ +#define SAA5246A_REGISTER_R0 0 +#define SAA5246A_REGISTER_R1 1 +#define SAA5246A_REGISTER_R2 2 +#define SAA5246A_REGISTER_R3 3 +#define SAA5246A_REGISTER_R4 4 +#define SAA5246A_REGISTER_R5 5 +#define SAA5246A_REGISTER_R6 6 +#define SAA5246A_REGISTER_R7 7 +#define SAA5246A_REGISTER_R8 8 +#define SAA5246A_REGISTER_R9 9 +#define SAA5246A_REGISTER_R10 10 +#define SAA5246A_REGISTER_R11 11 +#define SAA5246A_REGISTER_R11B 11 + +/* SAA5246A mode registers often autoincrement to the next register. + Therefore we use variable argument lists. The following macro indicates + the end of a command list. */ +#define COMMAND_END (- 1) + +/*****************************************************************************/ +/* Contents of the mode registers of the SAA5246A */ +/*****************************************************************************/ +/* Register R0 (Advanced Control) */ +#define R0_SELECT_R11 0x00 +#define R0_SELECT_R11B 0x01 + +#define R0_PLL_TIME_CONSTANT_LONG 0x00 +#define R0_PLL_TIME_CONSTANT_SHORT 0x02 + +#define R0_ENABLE_nODD_EVEN_OUTPUT 0x00 +#define R0_DISABLE_nODD_EVEN_OUTPUT 0x04 + +#define R0_ENABLE_HDR_POLL 0x00 +#define R0_DISABLE_HDR_POLL 0x10 + +#define R0_DO_NOT_FORCE_nODD_EVEN_LOW_IF_PICTURE_DISPLAYED 0x00 +#define R0_FORCE_nODD_EVEN_LOW_IF_PICTURE_DISPLAYED 0x20 + +#define R0_NO_FREE_RUN_PLL 0x00 +#define R0_FREE_RUN_PLL 0x40 + +#define R0_NO_AUTOMATIC_FASTEXT_PROMPT 0x00 +#define R0_AUTOMATIC_FASTEXT_PROMPT 0x80 + +/* Register R1 (Mode) */ +#define R1_INTERLACED_312_AND_HALF_312_AND_HALF_LINES 0x00 +#define R1_NON_INTERLACED_312_313_LINES 0x01 +#define R1_NON_INTERLACED_312_312_LINES 0x02 +#define R1_FFB_LEADING_EDGE_IN_FIRST_BROAD_PULSE 0x03 +#define R1_FFB_LEADING_EDGE_IN_SECOND_BROAD_PULSE 0x07 + +#define R1_DEW 0x00 +#define R1_FULL_FIELD 0x08 + +#define R1_EXTENDED_PACKET_DISABLE 0x00 +#define R1_EXTENDED_PACKET_ENABLE 0x10 + +#define R1_DAUS_ALL_ON 0x00 +#define R1_DAUS_ALL_OFF 0x20 + +#define R1_7_BITS_PLUS_PARITY 0x00 +#define R1_8_BITS_NO_PARITY 0x40 + +#define R1_VCS_TO_SCS 0x00 +#define R1_NO_VCS_TO_SCS 0x80 + +/* Register R2 (Page request address) */ +#define R2_IN_R3_SELECT_PAGE_HUNDREDS 0x00 +#define R2_IN_R3_SELECT_PAGE_TENS 0x01 +#define R2_IN_R3_SELECT_PAGE_UNITS 0x02 +#define R2_IN_R3_SELECT_HOURS_TENS 0x03 +#define R2_IN_R3_SELECT_HOURS_UNITS 0x04 +#define R2_IN_R3_SELECT_MINUTES_TENS 0x05 +#define R2_IN_R3_SELECT_MINUTES_UNITS 0x06 + +#define R2_DAU_0 0x00 +#define R2_DAU_1 0x10 +#define R2_DAU_2 0x20 +#define R2_DAU_3 0x30 + +#define R2_BANK_0 0x00 +#define R2_BANK 1 0x40 + +#define R2_HAMMING_CHECK_ON 0x80 +#define R2_HAMMING_CHECK_OFF 0x00 + +/* Register R3 (Page request data) */ +#define R3_PAGE_HUNDREDS_0 0x00 +#define R3_PAGE_HUNDREDS_1 0x01 +#define R3_PAGE_HUNDREDS_2 0x02 +#define R3_PAGE_HUNDREDS_3 0x03 +#define R3_PAGE_HUNDREDS_4 0x04 +#define R3_PAGE_HUNDREDS_5 0x05 +#define R3_PAGE_HUNDREDS_6 0x06 +#define R3_PAGE_HUNDREDS_7 0x07 + +#define R3_HOLD_PAGE 0x00 +#define R3_UPDATE_PAGE 0x08 + +#define R3_PAGE_HUNDREDS_DO_NOT_CARE 0x00 +#define R3_PAGE_HUNDREDS_DO_CARE 0x10 + +#define R3_PAGE_TENS_DO_NOT_CARE 0x00 +#define R3_PAGE_TENS_DO_CARE 0x10 + +#define R3_PAGE_UNITS_DO_NOT_CARE 0x00 +#define R3_PAGE_UNITS_DO_CARE 0x10 + +#define R3_HOURS_TENS_DO_NOT_CARE 0x00 +#define R3_HOURS_TENS_DO_CARE 0x10 + +#define R3_HOURS_UNITS_DO_NOT_CARE 0x00 +#define R3_HOURS_UNITS_DO_CARE 0x10 + +#define R3_MINUTES_TENS_DO_NOT_CARE 0x00 +#define R3_MINUTES_TENS_DO_CARE 0x10 + +#define R3_MINUTES_UNITS_DO_NOT_CARE 0x00 +#define R3_MINUTES_UNITS_DO_CARE 0x10 + +/* Register R4 (Display chapter) */ +#define R4_DISPLAY_PAGE_0 0x00 +#define R4_DISPLAY_PAGE_1 0x01 +#define R4_DISPLAY_PAGE_2 0x02 +#define R4_DISPLAY_PAGE_3 0x03 +#define R4_DISPLAY_PAGE_4 0x04 +#define R4_DISPLAY_PAGE_5 0x05 +#define R4_DISPLAY_PAGE_6 0x06 +#define R4_DISPLAY_PAGE_7 0x07 + +/* Register R5 (Normal display control) */ +#define R5_PICTURE_INSIDE_BOXING_OFF 0x00 +#define R5_PICTURE_INSIDE_BOXING_ON 0x01 + +#define R5_PICTURE_OUTSIDE_BOXING_OFF 0x00 +#define R5_PICTURE_OUTSIDE_BOXING_ON 0x02 + +#define R5_TEXT_INSIDE_BOXING_OFF 0x00 +#define R5_TEXT_INSIDE_BOXING_ON 0x04 + +#define R5_TEXT_OUTSIDE_BOXING_OFF 0x00 +#define R5_TEXT_OUTSIDE_BOXING_ON 0x08 + +#define R5_CONTRAST_REDUCTION_INSIDE_BOXING_OFF 0x00 +#define R5_CONTRAST_REDUCTION_INSIDE_BOXING_ON 0x10 + +#define R5_CONTRAST_REDUCTION_OUTSIDE_BOXING_OFF 0x00 +#define R5_CONTRAST_REDUCTION_OUTSIDE_BOXING_ON 0x20 + +#define R5_BACKGROUND_COLOR_INSIDE_BOXING_OFF 0x00 +#define R5_BACKGROUND_COLOR_INSIDE_BOXING_ON 0x40 + +#define R5_BACKGROUND_COLOR_OUTSIDE_BOXING_OFF 0x00 +#define R5_BACKGROUND_COLOR_OUTSIDE_BOXING_ON 0x80 + +/* Register R6 (Newsflash display) */ +#define R6_NEWSFLASH_PICTURE_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_PICTURE_INSIDE_BOXING_ON 0x01 + +#define R6_NEWSFLASH_PICTURE_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_PICTURE_OUTSIDE_BOXING_ON 0x02 + +#define R6_NEWSFLASH_TEXT_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_TEXT_INSIDE_BOXING_ON 0x04 + +#define R6_NEWSFLASH_TEXT_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_TEXT_OUTSIDE_BOXING_ON 0x08 + +#define R6_NEWSFLASH_CONTRAST_REDUCTION_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_CONTRAST_REDUCTION_INSIDE_BOXING_ON 0x10 + +#define R6_NEWSFLASH_CONTRAST_REDUCTION_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_CONTRAST_REDUCTION_OUTSIDE_BOXING_ON 0x20 + +#define R6_NEWSFLASH_BACKGROUND_COLOR_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_BACKGROUND_COLOR_INSIDE_BOXING_ON 0x40 + +#define R6_NEWSFLASH_BACKGROUND_COLOR_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_BACKGROUND_COLOR_OUTSIDE_BOXING_ON 0x80 + +/* Register R7 (Display mode) */ +#define R7_BOX_OFF_ROW_0 0x00 +#define R7_BOX_ON_ROW_0 0x01 + +#define R7_BOX_OFF_ROW_1_TO_23 0x00 +#define R7_BOX_ON_ROW_1_TO_23 0x02 + +#define R7_BOX_OFF_ROW_24 0x00 +#define R7_BOX_ON_ROW_24 0x04 + +#define R7_SINGLE_HEIGHT 0x00 +#define R7_DOUBLE_HEIGHT 0x08 + +#define R7_TOP_HALF 0x00 +#define R7_BOTTOM_HALF 0x10 + +#define R7_REVEAL_OFF 0x00 +#define R7_REVEAL_ON 0x20 + +#define R7_CURSER_OFF 0x00 +#define R7_CURSER_ON 0x40 + +#define R7_STATUS_BOTTOM 0x00 +#define R7_STATUS_TOP 0x80 + +/* Register R8 (Active chapter) */ +#define R8_ACTIVE_CHAPTER_0 0x00 +#define R8_ACTIVE_CHAPTER_1 0x01 +#define R8_ACTIVE_CHAPTER_2 0x02 +#define R8_ACTIVE_CHAPTER_3 0x03 +#define R8_ACTIVE_CHAPTER_4 0x04 +#define R8_ACTIVE_CHAPTER_5 0x05 +#define R8_ACTIVE_CHAPTER_6 0x06 +#define R8_ACTIVE_CHAPTER_7 0x07 + +#define R8_CLEAR_MEMORY 0x08 +#define R8_DO_NOT_CLEAR_MEMORY 0x00 + +/* Register R9 (Curser row) */ +#define R9_CURSER_ROW_0 0x00 +#define R9_CURSER_ROW_1 0x01 +#define R9_CURSER_ROW_2 0x02 +#define R9_CURSER_ROW_25 0x19 + +/* Register R10 (Curser column) */ +#define R10_CURSER_COLUMN_0 0x00 +#define R10_CURSER_COLUMN_6 0x06 +#define R10_CURSER_COLUMN_8 0x08 + +/*****************************************************************************/ +/* Row 25 control data in column 0 to 9 */ +/*****************************************************************************/ +#define ROW25_COLUMN0_PAGE_UNITS 0x0F + +#define ROW25_COLUMN1_PAGE_TENS 0x0F + +#define ROW25_COLUMN2_MINUTES_UNITS 0x0F + +#define ROW25_COLUMN3_MINUTES_TENS 0x07 +#define ROW25_COLUMN3_DELETE_PAGE 0x08 + +#define ROW25_COLUMN4_HOUR_UNITS 0x0F + +#define ROW25_COLUMN5_HOUR_TENS 0x03 +#define ROW25_COLUMN5_INSERT_HEADLINE 0x04 +#define ROW25_COLUMN5_INSERT_SUBTITLE 0x08 + +#define ROW25_COLUMN6_SUPPRESS_HEADER 0x01 +#define ROW25_COLUMN6_UPDATE_PAGE 0x02 +#define ROW25_COLUMN6_INTERRUPTED_SEQUENCE 0x04 +#define ROW25_COLUMN6_SUPPRESS_DISPLAY 0x08 + +#define ROW25_COLUMN7_SERIAL_MODE 0x01 +#define ROW25_COLUMN7_CHARACTER_SET 0x0E + +#define ROW25_COLUMN8_PAGE_HUNDREDS 0x07 +#define ROW25_COLUMN8_PAGE_NOT_FOUND 0x10 + +#define ROW25_COLUMN9_PAGE_BEING_LOOKED_FOR 0x20 + +#define ROW25_COLUMN0_TO_7_HAMMING_ERROR 0x10 + +/*****************************************************************************/ +/* Helper macros for extracting page, hour and minute digits */ +/*****************************************************************************/ +/* BYTE_POS 0 is at row 0, column 0, + BYTE_POS 1 is at row 0, column 1, + BYTE_POS 40 is at row 1, column 0, (with NUM_ROWS_PER_PAGE = 40) + BYTE_POS 41 is at row 1, column 1, (with NUM_ROWS_PER_PAGE = 40), + ... */ +#define ROW(BYTE_POS) (BYTE_POS / NUM_ROWS_PER_PAGE) +#define COLUMN(BYTE_POS) (BYTE_POS % NUM_ROWS_PER_PAGE) + +/*****************************************************************************/ +/* Helper macros for extracting page, hour and minute digits */ +/*****************************************************************************/ +/* Macros for extracting hundreds, tens and units of a page number which + must be in the range 0 ... 0x799. + Note that page is coded in hexadecimal, i.e. 0x123 means page 123. + page 0x.. means page 8.. */ +#define HUNDREDS_OF_PAGE(page) (((page) / 0x100) & 0x7) +#define TENS_OF_PAGE(page) (((page) / 0x10) & 0xF) +#define UNITS_OF_PAGE(page) ((page) & 0xF) + +/* Macros for extracting tens and units of a hour information which + must be in the range 0 ... 0x24. + Note that hour is coded in hexadecimal, i.e. 0x12 means 12 hours */ +#define TENS_OF_HOUR(hour) ((hour) / 0x10) +#define UNITS_OF_HOUR(hour) ((hour) & 0xF) + +/* Macros for extracting tens and units of a minute information which + must be in the range 0 ... 0x59. + Note that minute is coded in hexadecimal, i.e. 0x12 means 12 minutes */ +#define TENS_OF_MINUTE(minute) ((minute) / 0x10) +#define UNITS_OF_MINUTE(minute) ((minute) & 0xF) + +#define HOUR_MAX 0x23 +#define MINUTE_MAX 0x59 +#define PAGE_MAX 0x8FF + +#endif /* __SAA5246A_H__ */ diff --git a/drivers/media/video/saa5249.c b/drivers/media/video/saa5249.c new file mode 100644 index 000000000000..d74caa139f0a --- /dev/null +++ b/drivers/media/video/saa5249.c @@ -0,0 +1,725 @@ +/* + * Modified in order to keep it compatible both with new and old videotext IOCTLs by + * Michael Geng <linux@MichaelGeng.de> + * + * Cleaned up to use existing videodev interface and allow the idea + * of multiple teletext decoders on the video4linux iface. Changed i2c + * to cover addressing clashes on device busses. It's also rebuilt so + * you can add arbitary multiple teletext devices to Linux video4linux + * now (well 32 anyway). + * + * Alan Cox <Alan.Cox@linux.org> + * + * The original driver was heavily modified to match the i2c interface + * It was truncated to use the WinTV boards, too. + * + * Copyright (c) 1998 Richard Guenther <richard.guenther@student.uni-tuebingen.de> + * + * $Id: saa5249.c,v 1.1 1998/03/30 22:23:23 alan Exp $ + * + * Derived From + * + * vtx.c: + * This is a loadable character-device-driver for videotext-interfaces + * (aka teletext). Please check the Makefile/README for a list of supported + * interfaces. + * + * Copyright (c) 1994-97 Martin Buck <martin-2.buck@student.uni-ulm.de> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <stdarg.h> +#include <linux/i2c.h> +#include <linux/videotext.h> +#include <linux/videodev.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#define VTX_VER_MAJ 1 +#define VTX_VER_MIN 8 + + + +#define NUM_DAUS 4 +#define NUM_BUFS 8 +#define IF_NAME "SAA5249" + +static const int disp_modes[8][3] = +{ + { 0x46, 0x03, 0x03 }, /* DISPOFF */ + { 0x46, 0xcc, 0xcc }, /* DISPNORM */ + { 0x44, 0x0f, 0x0f }, /* DISPTRANS */ + { 0x46, 0xcc, 0x46 }, /* DISPINS */ + { 0x44, 0x03, 0x03 }, /* DISPOFF, interlaced */ + { 0x44, 0xcc, 0xcc }, /* DISPNORM, interlaced */ + { 0x44, 0x0f, 0x0f }, /* DISPTRANS, interlaced */ + { 0x44, 0xcc, 0x46 } /* DISPINS, interlaced */ +}; + + + +#define PAGE_WAIT (300*HZ/1000) /* Time between requesting page and */ + /* checking status bits */ +#define PGBUF_EXPIRE (15*HZ) /* Time to wait before retransmitting */ + /* page regardless of infobits */ +typedef struct { + u8 pgbuf[VTX_VIRTUALSIZE]; /* Page-buffer */ + u8 laststat[10]; /* Last value of infobits for DAU */ + u8 sregs[7]; /* Page-request registers */ + unsigned long expire; /* Time when page will be expired */ + unsigned clrfound : 1; /* VTXIOCCLRFOUND has been called */ + unsigned stopped : 1; /* VTXIOCSTOPDAU has been called */ +} vdau_t; + +struct saa5249_device +{ + vdau_t vdau[NUM_DAUS]; /* Data for virtual DAUs (the 5249 only has one */ + /* real DAU, so we have to simulate some more) */ + int vtx_use_count; + int is_searching[NUM_DAUS]; + int disp_mode; + int virtual_mode; + struct i2c_client *client; + struct semaphore lock; +}; + + +#define CCTWR 34 /* I²C write/read-address of vtx-chip */ +#define CCTRD 35 +#define NOACK_REPEAT 10 /* Retry access this many times on failure */ +#define CLEAR_DELAY (HZ/20) /* Time required to clear a page */ +#define READY_TIMEOUT (30*HZ/1000) /* Time to wait for ready signal of I²C-bus interface */ +#define INIT_DELAY 500 /* Time in usec to wait at initialization of CEA interface */ +#define START_DELAY 10 /* Time in usec to wait before starting write-cycle (CEA) */ + +#define VTX_DEV_MINOR 0 + +/* General defines and debugging support */ + +#ifndef FALSE +#define FALSE 0 +#define TRUE 1 +#endif + +#define RESCHED do { cond_resched(); } while(0) + +static struct video_device saa_template; /* Declared near bottom */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {34>>1,I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +static struct i2c_client client_template; + +static int saa5249_attach(struct i2c_adapter *adap, int addr, int kind) +{ + int pgbuf; + int err; + struct i2c_client *client; + struct video_device *vd; + struct saa5249_device *t; + + printk(KERN_INFO "saa5249: teletext chip found.\n"); + client=kmalloc(sizeof(*client), GFP_KERNEL); + if(client==NULL) + return -ENOMEM; + client_template.adapter = adap; + client_template.addr = addr; + memcpy(client, &client_template, sizeof(*client)); + t = kmalloc(sizeof(*t), GFP_KERNEL); + if(t==NULL) + { + kfree(client); + return -ENOMEM; + } + memset(t, 0, sizeof(*t)); + strlcpy(client->name, IF_NAME, I2C_NAME_SIZE); + init_MUTEX(&t->lock); + + /* + * Now create a video4linux device + */ + + vd = (struct video_device *)kmalloc(sizeof(struct video_device), GFP_KERNEL); + if(vd==NULL) + { + kfree(t); + kfree(client); + return -ENOMEM; + } + i2c_set_clientdata(client, vd); + memcpy(vd, &saa_template, sizeof(*vd)); + + for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++) + { + memset(t->vdau[pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf)); + memset(t->vdau[pgbuf].sregs, 0, sizeof(t->vdau[0].sregs)); + memset(t->vdau[pgbuf].laststat, 0, sizeof(t->vdau[0].laststat)); + t->vdau[pgbuf].expire = 0; + t->vdau[pgbuf].clrfound = TRUE; + t->vdau[pgbuf].stopped = TRUE; + t->is_searching[pgbuf] = FALSE; + } + vd->priv=t; + + + /* + * Register it + */ + + if((err=video_register_device(vd, VFL_TYPE_VTX,-1))<0) + { + kfree(t); + kfree(vd); + kfree(client); + return err; + } + t->client = client; + i2c_attach_client(client); + return 0; +} + +/* + * We do most of the hard work when we become a device on the i2c. + */ + +static int saa5249_probe(struct i2c_adapter *adap) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa5249_attach); + return 0; +} + +static int saa5249_detach(struct i2c_client *client) +{ + struct video_device *vd = i2c_get_clientdata(client); + i2c_detach_client(client); + video_unregister_device(vd); + kfree(vd->priv); + kfree(vd); + kfree(client); + return 0; +} + +static int saa5249_command(struct i2c_client *device, + unsigned int cmd, void *arg) +{ + return -EINVAL; +} + +/* new I2C driver support */ + +static struct i2c_driver i2c_driver_videotext = +{ + .owner = THIS_MODULE, + .name = IF_NAME, /* name */ + .id = I2C_DRIVERID_SAA5249, /* in i2c.h */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa5249_probe, + .detach_client = saa5249_detach, + .command = saa5249_command +}; + +static struct i2c_client client_template = { + .driver = &i2c_driver_videotext, + .name = "(unset)", +}; + +/* + * Wait the given number of jiffies (10ms). This calls the scheduler, so the actual + * delay may be longer. + */ + +static void jdelay(unsigned long delay) +{ + sigset_t oldblocked = current->blocked; + + spin_lock_irq(¤t->sighand->siglock); + sigfillset(¤t->blocked); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + msleep_interruptible(jiffies_to_msecs(delay)); + + spin_lock_irq(¤t->sighand->siglock); + current->blocked = oldblocked; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); +} + + +/* + * I2C interfaces + */ + +static int i2c_sendbuf(struct saa5249_device *t, int reg, int count, u8 *data) +{ + char buf[64]; + + buf[0] = reg; + memcpy(buf+1, data, count); + + if(i2c_master_send(t->client, buf, count+1)==count+1) + return 0; + return -1; +} + +static int i2c_senddata(struct saa5249_device *t, ...) +{ + unsigned char buf[64]; + int v; + int ct=0; + va_list argp; + va_start(argp,t); + + while((v=va_arg(argp,int))!=-1) + buf[ct++]=v; + return i2c_sendbuf(t, buf[0], ct-1, buf+1); +} + +/* Get count number of bytes from I²C-device at address adr, store them in buf. Start & stop + * handshaking is done by this routine, ack will be sent after the last byte to inhibit further + * sending of data. If uaccess is TRUE, data is written to user-space with put_user. + * Returns -1 if I²C-device didn't send acknowledge, 0 otherwise + */ + +static int i2c_getdata(struct saa5249_device *t, int count, u8 *buf) +{ + if(i2c_master_recv(t->client, buf, count)!=count) + return -1; + return 0; +} + + +/* + * Standard character-device-driver functions + */ + +static int do_saa5249_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + static int virtual_mode = FALSE; + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + + switch(cmd) + { + case VTXIOCGETINFO: + { + vtx_info_t *info = arg; + info->version_major = VTX_VER_MAJ; + info->version_minor = VTX_VER_MIN; + info->numpages = NUM_DAUS; + /*info->cct_type = CCT_TYPE;*/ + return 0; + } + + case VTXIOCCLRPAGE: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + memset(t->vdau[req->pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf)); + t->vdau[req->pgbuf].clrfound = TRUE; + return 0; + } + + case VTXIOCCLRFOUND: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req->pgbuf].clrfound = TRUE; + return 0; + } + + case VTXIOCPAGEREQ: + { + vtx_pagereq_t *req = arg; + if (!(req->pagemask & PGMASK_PAGE)) + req->page = 0; + if (!(req->pagemask & PGMASK_HOUR)) + req->hour = 0; + if (!(req->pagemask & PGMASK_MINUTE)) + req->minute = 0; + if (req->page < 0 || req->page > 0x8ff) /* 7FF ?? */ + return -EINVAL; + req->page &= 0x7ff; + if (req->hour < 0 || req->hour > 0x3f || req->minute < 0 || req->minute > 0x7f || + req->pagemask < 0 || req->pagemask >= PGMASK_MAX || req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req->pgbuf].sregs[0] = (req->pagemask & PG_HUND ? 0x10 : 0) | (req->page / 0x100); + t->vdau[req->pgbuf].sregs[1] = (req->pagemask & PG_TEN ? 0x10 : 0) | ((req->page / 0x10) & 0xf); + t->vdau[req->pgbuf].sregs[2] = (req->pagemask & PG_UNIT ? 0x10 : 0) | (req->page & 0xf); + t->vdau[req->pgbuf].sregs[3] = (req->pagemask & HR_TEN ? 0x10 : 0) | (req->hour / 0x10); + t->vdau[req->pgbuf].sregs[4] = (req->pagemask & HR_UNIT ? 0x10 : 0) | (req->hour & 0xf); + t->vdau[req->pgbuf].sregs[5] = (req->pagemask & MIN_TEN ? 0x10 : 0) | (req->minute / 0x10); + t->vdau[req->pgbuf].sregs[6] = (req->pagemask & MIN_UNIT ? 0x10 : 0) | (req->minute & 0xf); + t->vdau[req->pgbuf].stopped = FALSE; + t->vdau[req->pgbuf].clrfound = TRUE; + t->is_searching[req->pgbuf] = TRUE; + return 0; + } + + case VTXIOCGETSTAT: + { + vtx_pagereq_t *req = arg; + u8 infobits[10]; + vtx_pageinfo_t info; + int a; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + if (!t->vdau[req->pgbuf].stopped) + { + if (i2c_senddata(t, 2, 0, -1) || + i2c_sendbuf(t, 3, sizeof(t->vdau[0].sregs), t->vdau[req->pgbuf].sregs) || + i2c_senddata(t, 8, 0, 25, 0, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', -1) || + i2c_senddata(t, 2, 0, t->vdau[req->pgbuf].sregs[0] | 8, -1) || + i2c_senddata(t, 8, 0, 25, 0, -1)) + return -EIO; + jdelay(PAGE_WAIT); + if (i2c_getdata(t, 10, infobits)) + return -EIO; + + if (!(infobits[8] & 0x10) && !(infobits[7] & 0xf0) && /* check FOUND-bit */ + (memcmp(infobits, t->vdau[req->pgbuf].laststat, sizeof(infobits)) || + time_after_eq(jiffies, t->vdau[req->pgbuf].expire))) + { /* check if new page arrived */ + if (i2c_senddata(t, 8, 0, 0, 0, -1) || + i2c_getdata(t, VTX_PAGESIZE, t->vdau[req->pgbuf].pgbuf)) + return -EIO; + t->vdau[req->pgbuf].expire = jiffies + PGBUF_EXPIRE; + memset(t->vdau[req->pgbuf].pgbuf + VTX_PAGESIZE, ' ', VTX_VIRTUALSIZE - VTX_PAGESIZE); + if (t->virtual_mode) + { + /* Packet X/24 */ + if (i2c_senddata(t, 8, 0, 0x20, 0, -1) || + i2c_getdata(t, 40, t->vdau[req->pgbuf].pgbuf + VTX_PAGESIZE + 20 * 40)) + return -EIO; + /* Packet X/27/0 */ + if (i2c_senddata(t, 8, 0, 0x21, 0, -1) || + i2c_getdata(t, 40, t->vdau[req->pgbuf].pgbuf + VTX_PAGESIZE + 16 * 40)) + return -EIO; + /* Packet 8/30/0...8/30/15 + * FIXME: AFAIK, the 5249 does hamming-decoding for some bytes in packet 8/30, + * so we should undo this here. + */ + if (i2c_senddata(t, 8, 0, 0x22, 0, -1) || + i2c_getdata(t, 40, t->vdau[req->pgbuf].pgbuf + VTX_PAGESIZE + 23 * 40)) + return -EIO; + } + t->vdau[req->pgbuf].clrfound = FALSE; + memcpy(t->vdau[req->pgbuf].laststat, infobits, sizeof(infobits)); + } + else + { + memcpy(infobits, t->vdau[req->pgbuf].laststat, sizeof(infobits)); + } + } + else + { + memcpy(infobits, t->vdau[req->pgbuf].laststat, sizeof(infobits)); + } + + info.pagenum = ((infobits[8] << 8) & 0x700) | ((infobits[1] << 4) & 0xf0) | (infobits[0] & 0x0f); + if (info.pagenum < 0x100) + info.pagenum += 0x800; + info.hour = ((infobits[5] << 4) & 0x30) | (infobits[4] & 0x0f); + info.minute = ((infobits[3] << 4) & 0x70) | (infobits[2] & 0x0f); + info.charset = ((infobits[7] >> 1) & 7); + info.delete = !!(infobits[3] & 8); + info.headline = !!(infobits[5] & 4); + info.subtitle = !!(infobits[5] & 8); + info.supp_header = !!(infobits[6] & 1); + info.update = !!(infobits[6] & 2); + info.inter_seq = !!(infobits[6] & 4); + info.dis_disp = !!(infobits[6] & 8); + info.serial = !!(infobits[7] & 1); + info.notfound = !!(infobits[8] & 0x10); + info.pblf = !!(infobits[9] & 0x20); + info.hamming = 0; + for (a = 0; a <= 7; a++) + { + if (infobits[a] & 0xf0) + { + info.hamming = 1; + break; + } + } + if (t->vdau[req->pgbuf].clrfound) + info.notfound = 1; + if(copy_to_user(req->buffer, &info, sizeof(vtx_pageinfo_t))) + return -EFAULT; + if (!info.hamming && !info.notfound) + { + t->is_searching[req->pgbuf] = FALSE; + } + return 0; + } + + case VTXIOCGETPAGE: + { + vtx_pagereq_t *req = arg; + int start, end; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS || req->start < 0 || + req->start > req->end || req->end >= (virtual_mode ? VTX_VIRTUALSIZE : VTX_PAGESIZE)) + return -EINVAL; + if(copy_to_user(req->buffer, &t->vdau[req->pgbuf].pgbuf[req->start], req->end - req->start + 1)) + return -EFAULT; + + /* + * Always read the time directly from SAA5249 + */ + + if (req->start <= 39 && req->end >= 32) + { + int len; + char buf[16]; + start = max(req->start, 32); + end = min(req->end, 39); + len=end-start+1; + if (i2c_senddata(t, 8, 0, 0, start, -1) || + i2c_getdata(t, len, buf)) + return -EIO; + if(copy_to_user(req->buffer+start-req->start, buf, len)) + return -EFAULT; + } + /* Insert the current header if DAU is still searching for a page */ + if (req->start <= 31 && req->end >= 7 && t->is_searching[req->pgbuf]) + { + char buf[32]; + int len; + start = max(req->start, 7); + end = min(req->end, 31); + len=end-start+1; + if (i2c_senddata(t, 8, 0, 0, start, -1) || + i2c_getdata(t, len, buf)) + return -EIO; + if(copy_to_user(req->buffer+start-req->start, buf, len)) + return -EFAULT; + } + return 0; + } + + case VTXIOCSTOPDAU: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req->pgbuf].stopped = TRUE; + t->is_searching[req->pgbuf] = FALSE; + return 0; + } + + case VTXIOCPUTPAGE: + case VTXIOCSETDISP: + case VTXIOCPUTSTAT: + return 0; + + case VTXIOCCLRCACHE: + { + if (i2c_senddata(t, 0, NUM_DAUS, 0, 8, -1) || i2c_senddata(t, 11, + ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', + ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', -1)) + return -EIO; + if (i2c_senddata(t, 3, 0x20, -1)) + return -EIO; + jdelay(10 * CLEAR_DELAY); /* I have no idea how long we have to wait here */ + return 0; + } + + case VTXIOCSETVIRT: + { + /* The SAA5249 has virtual-row reception turned on always */ + t->virtual_mode = (int)(long)arg; + return 0; + } + } + return -EINVAL; +} + +/* + * Translates old vtx IOCTLs to new ones + * + * This keeps new kernel versions compatible with old userspace programs. + */ +static inline unsigned int vtx_fix_command(unsigned int cmd) +{ + switch (cmd) { + case VTXIOCGETINFO_OLD: + cmd = VTXIOCGETINFO; + break; + case VTXIOCCLRPAGE_OLD: + cmd = VTXIOCCLRPAGE; + break; + case VTXIOCCLRFOUND_OLD: + cmd = VTXIOCCLRFOUND; + break; + case VTXIOCPAGEREQ_OLD: + cmd = VTXIOCPAGEREQ; + break; + case VTXIOCGETSTAT_OLD: + cmd = VTXIOCGETSTAT; + break; + case VTXIOCGETPAGE_OLD: + cmd = VTXIOCGETPAGE; + break; + case VTXIOCSTOPDAU_OLD: + cmd = VTXIOCSTOPDAU; + break; + case VTXIOCPUTPAGE_OLD: + cmd = VTXIOCPUTPAGE; + break; + case VTXIOCSETDISP_OLD: + cmd = VTXIOCSETDISP; + break; + case VTXIOCPUTSTAT_OLD: + cmd = VTXIOCPUTSTAT; + break; + case VTXIOCCLRCACHE_OLD: + cmd = VTXIOCCLRCACHE; + break; + case VTXIOCSETVIRT_OLD: + cmd = VTXIOCSETVIRT; + break; + } + return cmd; +} + +/* + * Handle the locking + */ + +static int saa5249_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + int err; + + cmd = vtx_fix_command(cmd); + down(&t->lock); + err = video_usercopy(inode,file,cmd,arg,do_saa5249_ioctl); + up(&t->lock); + return err; +} + +static int saa5249_open(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + int err,pgbuf; + + err = video_exclusive_open(inode,file); + if (err < 0) + return err; + + if (t->client==NULL) { + err = -ENODEV; + goto fail; + } + + if (i2c_senddata(t, 0, 0, -1) || /* Select R11 */ + /* Turn off parity checks (we do this ourselves) */ + i2c_senddata(t, 1, disp_modes[t->disp_mode][0], 0, -1) || + /* Display TV-picture, no virtual rows */ + i2c_senddata(t, 4, NUM_DAUS, disp_modes[t->disp_mode][1], disp_modes[t->disp_mode][2], 7, -1)) /* Set display to page 4 */ + + { + err = -EIO; + goto fail; + } + + for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++) + { + memset(t->vdau[pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf)); + memset(t->vdau[pgbuf].sregs, 0, sizeof(t->vdau[0].sregs)); + memset(t->vdau[pgbuf].laststat, 0, sizeof(t->vdau[0].laststat)); + t->vdau[pgbuf].expire = 0; + t->vdau[pgbuf].clrfound = TRUE; + t->vdau[pgbuf].stopped = TRUE; + t->is_searching[pgbuf] = FALSE; + } + t->virtual_mode=FALSE; + return 0; + + fail: + video_exclusive_release(inode,file); + return err; +} + + + +static int saa5249_release(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + i2c_senddata(t, 1, 0x20, -1); /* Turn off CCT */ + i2c_senddata(t, 5, 3, 3, -1); /* Turn off TV-display */ + video_exclusive_release(inode,file); + return 0; +} + +static int __init init_saa_5249 (void) +{ + printk(KERN_INFO "SAA5249 driver (" IF_NAME " interface) for VideoText version %d.%d\n", + VTX_VER_MAJ, VTX_VER_MIN); + return i2c_add_driver(&i2c_driver_videotext); +} + +static void __exit cleanup_saa_5249 (void) +{ + i2c_del_driver(&i2c_driver_videotext); +} + +module_init(init_saa_5249); +module_exit(cleanup_saa_5249); + +static struct file_operations saa_fops = { + .owner = THIS_MODULE, + .open = saa5249_open, + .release = saa5249_release, + .ioctl = saa5249_ioctl, + .llseek = no_llseek, +}; + +static struct video_device saa_template = +{ + .owner = THIS_MODULE, + .name = IF_NAME, + .type = VID_TYPE_TELETEXT, /*| VID_TYPE_TUNER ?? */ + .hardware = VID_HARDWARE_SAA5249, + .fops = &saa_fops, +}; + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/saa7110.c b/drivers/media/video/saa7110.c new file mode 100644 index 000000000000..64273b438530 --- /dev/null +++ b/drivers/media/video/saa7110.c @@ -0,0 +1,623 @@ +/* + * saa7110 - Philips SAA7110(A) video decoder driver + * + * Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl> + * + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7110 video decoder driver"); +MODULE_AUTHOR("Pauline Middelink"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/videodev.h> +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +#define SAA7110_MAX_INPUT 9 /* 6 CVBS, 3 SVHS */ +#define SAA7110_MAX_OUTPUT 0 /* its a decoder only */ + +#define I2C_SAA7110 0x9C /* or 0x9E */ + +#define SAA7110_NR_REG 0x35 + +struct saa7110 { + u8 reg[SAA7110_NR_REG]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; + + wait_queue_head_t wq; +}; + +/* ----------------------------------------------------------------------- */ +/* I2C support functions */ +/* ----------------------------------------------------------------------- */ + +static int +saa7110_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct saa7110 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7110_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg = *data; /* first register to write to */ + + /* Sanity check */ + if (reg + (len - 1) > SAA7110_NR_REG) + return ret; + + /* the saa7110 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + struct saa7110 *decoder = i2c_get_clientdata(client); + struct i2c_msg msg; + + msg.len = len; + msg.buf = (char *) data; + msg.addr = client->addr; + msg.flags = 0; + ret = i2c_transfer(client->adapter, &msg, 1); + + /* Cache the written data */ + memcpy(decoder->reg + reg, data + 1, len - 1); + } else { + for (++data, --len; len; len--) { + if ((ret = saa7110_write(client, reg++, + *data++)) < 0) + break; + } + } + + return ret; +} + +static inline int +saa7110_read (struct i2c_client *client) +{ + return i2c_smbus_read_byte(client); +} + +/* ----------------------------------------------------------------------- */ +/* SAA7110 functions */ +/* ----------------------------------------------------------------------- */ + +#define FRESP_06H_COMPST 0x03 //0x13 +#define FRESP_06H_SVIDEO 0x83 //0xC0 + + +static int +saa7110_selmux (struct i2c_client *client, + int chan) +{ + static const unsigned char modes[9][8] = { + /* mode 0 */ + {FRESP_06H_COMPST, 0xD9, 0x17, 0x40, 0x03, + 0x44, 0x75, 0x16}, + /* mode 1 */ + {FRESP_06H_COMPST, 0xD8, 0x17, 0x40, 0x03, + 0x44, 0x75, 0x16}, + /* mode 2 */ + {FRESP_06H_COMPST, 0xBA, 0x07, 0x91, 0x03, + 0x60, 0xB5, 0x05}, + /* mode 3 */ + {FRESP_06H_COMPST, 0xB8, 0x07, 0x91, 0x03, + 0x60, 0xB5, 0x05}, + /* mode 4 */ + {FRESP_06H_COMPST, 0x7C, 0x07, 0xD2, 0x83, + 0x60, 0xB5, 0x03}, + /* mode 5 */ + {FRESP_06H_COMPST, 0x78, 0x07, 0xD2, 0x83, + 0x60, 0xB5, 0x03}, + /* mode 6 */ + {FRESP_06H_SVIDEO, 0x59, 0x17, 0x42, 0xA3, + 0x44, 0x75, 0x12}, + /* mode 7 */ + {FRESP_06H_SVIDEO, 0x9A, 0x17, 0xB1, 0x13, + 0x60, 0xB5, 0x14}, + /* mode 8 */ + {FRESP_06H_SVIDEO, 0x3C, 0x27, 0xC1, 0x23, + 0x44, 0x75, 0x21} + }; + struct saa7110 *decoder = i2c_get_clientdata(client); + const unsigned char *ptr = modes[chan]; + + saa7110_write(client, 0x06, ptr[0]); /* Luminance control */ + saa7110_write(client, 0x20, ptr[1]); /* Analog Control #1 */ + saa7110_write(client, 0x21, ptr[2]); /* Analog Control #2 */ + saa7110_write(client, 0x22, ptr[3]); /* Mixer Control #1 */ + saa7110_write(client, 0x2C, ptr[4]); /* Mixer Control #2 */ + saa7110_write(client, 0x30, ptr[5]); /* ADCs gain control */ + saa7110_write(client, 0x31, ptr[6]); /* Mixer Control #3 */ + saa7110_write(client, 0x21, ptr[7]); /* Analog Control #2 */ + decoder->input = chan; + + return 0; +} + +static const unsigned char initseq[1 + SAA7110_NR_REG] = { + 0, 0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF2, 0x03, 0x00, + /* 0x08 */ 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x86, 0x18, 0x90, + /* 0x10 */ 0x00, 0x59, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA, + /* 0x18 */ 0xF2, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x20 */ 0xD9, 0x16, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F, + /* 0x28 */ 0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x03, 0x0C, + /* 0x30 */ 0x44, 0x71, 0x02, 0x8C, 0x02 +}; + +static int +determine_norm (struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct saa7110 *decoder = i2c_get_clientdata(client); + int status; + + /* mode changed, start automatic detection */ + saa7110_write_block(client, initseq, sizeof(initseq)); + saa7110_selmux(client, decoder->input); + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/4); + finish_wait(&decoder->wq, &wait); + status = saa7110_read(client); + if (status & 0x40) { + dprintk(1, KERN_INFO "%s: status=0x%02x (no signal)\n", + I2C_NAME(client), status); + return decoder->norm; // no change + } + if ((status & 3) == 0) { + saa7110_write(client, 0x06, 0x83); + if (status & 0x20) { + dprintk(1, + KERN_INFO + "%s: status=0x%02x (NTSC/no color)\n", + I2C_NAME(client), status); + //saa7110_write(client,0x2E,0x81); + return VIDEO_MODE_NTSC; + } + dprintk(1, KERN_INFO "%s: status=0x%02x (PAL/no color)\n", + I2C_NAME(client), status); + //saa7110_write(client,0x2E,0x9A); + return VIDEO_MODE_PAL; + } + //saa7110_write(client,0x06,0x03); + if (status & 0x20) { /* 60Hz */ + dprintk(1, KERN_INFO "%s: status=0x%02x (NTSC)\n", + I2C_NAME(client), status); + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x50); + saa7110_write(client, 0x11, 0x2C); + //saa7110_write(client,0x2E,0x81); + return VIDEO_MODE_NTSC; + } + + /* 50Hz -> PAL/SECAM */ + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client,0x2E,0x9A); + + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/4); + finish_wait(&decoder->wq, &wait); + + status = saa7110_read(client); + if ((status & 0x03) == 0x01) { + dprintk(1, KERN_INFO "%s: status=0x%02x (SECAM)\n", + I2C_NAME(client), status); + saa7110_write(client, 0x0D, 0x87); + return VIDEO_MODE_SECAM; + } + dprintk(1, KERN_INFO "%s: status=0x%02x (PAL)\n", I2C_NAME(client), + status); + return VIDEO_MODE_PAL; +} + +static int +saa7110_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7110 *decoder = i2c_get_clientdata(client); + int v; + + switch (cmd) { + case 0: + //saa7110_write_block(client, initseq, sizeof(initseq)); + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *dc = arg; + + dc->flags = + VIDEO_DECODER_PAL | VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | VIDEO_DECODER_AUTO; + dc->inputs = SAA7110_MAX_INPUT; + dc->outputs = SAA7110_MAX_OUTPUT; + } + break; + + case DECODER_GET_STATUS: + { + int status; + int res = 0; + + status = saa7110_read(client); + dprintk(1, KERN_INFO "%s: status=0x%02x norm=%d\n", + I2C_NAME(client), status, decoder->norm); + if (!(status & 0x40)) + res |= DECODER_STATUS_GOOD; + if (status & 0x03) + res |= DECODER_STATUS_COLOR; + + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + case VIDEO_MODE_SECAM: + res |= DECODER_STATUS_SECAM; + break; + } + *(int *) arg = res; + } + break; + + case DECODER_SET_NORM: + v = *(int *) arg; + if (decoder->norm != v) { + decoder->norm = v; + //saa7110_write(client, 0x06, 0x03); + switch (v) { + case VIDEO_MODE_NTSC: + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x50); + saa7110_write(client, 0x11, 0x2C); + //saa7110_write(client, 0x2E, 0x81); + dprintk(1, + KERN_INFO "%s: switched to NTSC\n", + I2C_NAME(client)); + break; + case VIDEO_MODE_PAL: + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client, 0x2E, 0x9A); + dprintk(1, + KERN_INFO "%s: switched to PAL\n", + I2C_NAME(client)); + break; + case VIDEO_MODE_SECAM: + saa7110_write(client, 0x0D, 0x87); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client, 0x2E, 0x9A); + dprintk(1, + KERN_INFO + "%s: switched to SECAM\n", + I2C_NAME(client)); + break; + case VIDEO_MODE_AUTO: + dprintk(1, + KERN_INFO + "%s: TV standard detection...\n", + I2C_NAME(client)); + decoder->norm = determine_norm(client); + *(int *) arg = decoder->norm; + break; + default: + return -EPERM; + } + } + break; + + case DECODER_SET_INPUT: + v = *(int *) arg; + if (v < 0 || v > SAA7110_MAX_INPUT) { + dprintk(1, + KERN_INFO "%s: input=%d not available\n", + I2C_NAME(client), v); + return -EINVAL; + } + if (decoder->input != v) { + saa7110_selmux(client, v); + dprintk(1, KERN_INFO "%s: switched to input=%d\n", + I2C_NAME(client), v); + } + break; + + case DECODER_SET_OUTPUT: + v = *(int *) arg; + /* not much choice of outputs */ + if (v != 0) + return -EINVAL; + break; + + case DECODER_ENABLE_OUTPUT: + v = *(int *) arg; + if (decoder->enable != v) { + decoder->enable = v; + saa7110_write(client, 0x0E, v ? 0x18 : 0x80); + dprintk(1, KERN_INFO "%s: YUV %s\n", I2C_NAME(client), + v ? "on" : "off"); + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + saa7110_write(client, 0x19, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7110_write(client, 0x13, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7110_write(client, 0x12, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7110_write(client, 0x07, + (decoder->hue >> 8) - 128); + } + } + break; + + case DECODER_DUMP: + for (v = 0; v < 0x34; v += 16) { + int j; + dprintk(1, KERN_INFO "%s: %03x\n", I2C_NAME(client), + v); + for (j = 0; j < 16; j++) { + dprintk(1, KERN_INFO " %02x", + decoder->reg[v + j]); + } + dprintk(1, KERN_INFO "\n"); + } + break; + + default: + dprintk(1, KERN_INFO "unknown saa7110_command??(%d)\n", + cmd); + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { + I2C_SAA7110 >> 1, + (I2C_SAA7110 >> 1) + 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_saa7110; + +static int +saa7110_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + struct i2c_client *client; + struct saa7110 *decoder; + int rv; + + dprintk(1, + KERN_INFO + "saa7110.c: detecting saa7110 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality + (adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7110; + client->flags = I2C_CLIENT_ALLOW_USE; + strlcpy(I2C_NAME(client), "saa7110", sizeof(I2C_NAME(client))); + + decoder = kmalloc(sizeof(struct saa7110), GFP_KERNEL); + if (decoder == 0) { + kfree(client); + return -ENOMEM; + } + memset(decoder, 0, sizeof(struct saa7110)); + decoder->norm = VIDEO_MODE_PAL; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + init_waitqueue_head(&decoder->wq); + i2c_set_clientdata(client, decoder); + + rv = i2c_attach_client(client); + if (rv) { + kfree(client); + kfree(decoder); + return rv; + } + + rv = saa7110_write_block(client, initseq, sizeof(initseq)); + if (rv < 0) + dprintk(1, KERN_ERR "%s_attach: init status %d\n", + I2C_NAME(client), rv); + else { + int ver, status; + saa7110_write(client, 0x21, 0x10); + saa7110_write(client, 0x0e, 0x18); + saa7110_write(client, 0x0D, 0x04); + ver = saa7110_read(client); + saa7110_write(client, 0x0D, 0x06); + //mdelay(150); + status = saa7110_read(client); + dprintk(1, + KERN_INFO + "%s_attach: SAA7110A version %x at 0x%02x, status=0x%02x\n", + I2C_NAME(client), ver, client->addr << 1, status); + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client, 0x2E, 0x9A); + } + + //saa7110_selmux(client,0); + //determine_norm(client); + /* setup and implicit mode 0 select has been performed */ + + return 0; +} + +static int +saa7110_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7110.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7110_detect_client); +} + +static int +saa7110_detach_client (struct i2c_client *client) +{ + struct saa7110 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7110 = { + .owner = THIS_MODULE, + .name = "saa7110", + + .id = I2C_DRIVERID_SAA7110, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = saa7110_attach_adapter, + .detach_client = saa7110_detach_client, + .command = saa7110_command, +}; + +static int __init +saa7110_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7110); +} + +static void __exit +saa7110_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7110); +} + +module_init(saa7110_init); +module_exit(saa7110_exit); diff --git a/drivers/media/video/saa7111.c b/drivers/media/video/saa7111.c new file mode 100644 index 000000000000..0a873112ae23 --- /dev/null +++ b/drivers/media/video/saa7111.c @@ -0,0 +1,627 @@ +/* + * saa7111 - Philips SAA7111A video decoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * Changes by Michael Hunold <michael@mihu.de> + * - implemented DECODER_SET_GPIO, DECODER_INIT, DECODER_SET_VBI_BYPASS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7111 video decoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct saa7111 { + unsigned char reg[32]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_SAA7111 0x48 + +/* ----------------------------------------------------------------------- */ + +static inline int +saa7111_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct saa7111 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7111_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the saa7111 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct saa7111 *decoder = i2c_get_clientdata(client); + struct i2c_msg msg; + u8 block_data[32]; + + msg.addr = client->addr; + msg.flags = 0; + while (len >= 2) { + msg.buf = (char *) block_data; + msg.len = 0; + block_data[msg.len++] = reg = data[0]; + do { + block_data[msg.len++] = + decoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + msg.len < 32); + if ((ret = i2c_transfer(client->adapter, + &msg, 1)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = saa7111_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static int +saa7111_init_decoder (struct i2c_client *client, + struct video_decoder_init *init) +{ + return saa7111_write_block(client, init->data, init->len); +} + +static inline int +saa7111_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +static const unsigned char saa7111_i2c_init[] = { + 0x00, 0x00, /* 00 - ID byte */ + 0x01, 0x00, /* 01 - reserved */ + + /*front end */ + 0x02, 0xd0, /* 02 - FUSE=3, GUDL=2, MODE=0 */ + 0x03, 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, + * HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x04, 0x00, /* 04 - GAI1=256 */ + 0x05, 0x00, /* 05 - GAI2=256 */ + + /* decoder */ + 0x06, 0xf3, /* 06 - HSB at 13(50Hz) / 17(60Hz) + * pixels after end of last line */ + /*0x07, 0x13, * 07 - HSS at 113(50Hz) / 117(60Hz) pixels + * after end of last line */ + 0x07, 0xe8, /* 07 - HSS seems to be needed to + * work with NTSC, too */ + 0x08, 0xc8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, + * VTRC=1, HPLL=0, VNOI=0 */ + 0x09, 0x01, /* 09 - BYPS=0, PREF=0, BPSS=0, + * VBLB=0, UPTCV=0, APER=1 */ + 0x0a, 0x80, /* 0a - BRIG=128 */ + 0x0b, 0x47, /* 0b - CONT=1.109 */ + 0x0c, 0x40, /* 0c - SATN=1.0 */ + 0x0d, 0x00, /* 0d - HUE=0 */ + 0x0e, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, + * FCTC=0, CHBW=1 */ + 0x0f, 0x00, /* 0f - reserved */ + 0x10, 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ + 0x11, 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, + * OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + 0x12, 0x00, /* 12 - output control 2 */ + 0x13, 0x00, /* 13 - output control 3 */ + 0x14, 0x00, /* 14 - reserved */ + 0x15, 0x00, /* 15 - VBI */ + 0x16, 0x00, /* 16 - VBI */ + 0x17, 0x00, /* 17 - VBI */ +}; + +static int +saa7111_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7111 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + case DECODER_INIT: + { + struct video_decoder_init *init = arg; + if (NULL != init) + return saa7111_init_decoder(client, init); + else { + struct video_decoder_init vdi; + vdi.data = saa7111_i2c_init; + vdi.len = sizeof(saa7111_i2c_init); + return saa7111_init_decoder(client, &vdi); + } + } + + case DECODER_DUMP: + { + int i; + + for (i = 0; i < 32; i += 16) { + int j; + + printk(KERN_DEBUG "%s: %03x", I2C_NAME(client), i); + for (j = 0; j < 16; ++j) { + printk(" %02x", + saa7111_read(client, i + j)); + } + printk("\n"); + } + } + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + cap->flags = VIDEO_DECODER_PAL | + VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | + VIDEO_DECODER_AUTO | + VIDEO_DECODER_CCIR; + cap->inputs = 8; + cap->outputs = 1; + } + break; + + case DECODER_GET_STATUS: + { + int *iarg = arg; + int status; + int res; + + status = saa7111_read(client, 0x1f); + dprintk(1, KERN_DEBUG "%s status: 0x%02x\n", I2C_NAME(client), + status); + res = 0; + if ((status & (1 << 6)) == 0) { + res |= DECODER_STATUS_GOOD; + } + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + case VIDEO_MODE_SECAM: + res |= DECODER_STATUS_SECAM; + break; + default: + case VIDEO_MODE_AUTO: + if ((status & (1 << 5)) != 0) { + res |= DECODER_STATUS_NTSC; + } else { + res |= DECODER_STATUS_PAL; + } + break; + } + if ((status & (1 << 0)) != 0) { + res |= DECODER_STATUS_COLOR; + } + *iarg = res; + } + break; + + case DECODER_SET_GPIO: + { + int *iarg = arg; + if (0 != *iarg) { + saa7111_write(client, 0x11, + (decoder->reg[0x11] | 0x80)); + } else { + saa7111_write(client, 0x11, + (decoder->reg[0x11] & 0x7f)); + } + break; + } + + case DECODER_SET_VBI_BYPASS: + { + int *iarg = arg; + if (0 != *iarg) { + saa7111_write(client, 0x13, + (decoder->reg[0x13] & 0xf0) | 0x0a); + } else { + saa7111_write(client, 0x13, + (decoder->reg[0x13] & 0xf0)); + } + break; + } + + case DECODER_SET_NORM: + { + int *iarg = arg; + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x40); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f)); + break; + + case VIDEO_MODE_PAL: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x00); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f)); + break; + + case VIDEO_MODE_SECAM: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x00); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f) | 0x50); + break; + + case VIDEO_MODE_AUTO: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x80); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f)); + break; + + default: + return -EINVAL; + + } + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + + if (decoder->input != *iarg) { + decoder->input = *iarg; + /* select mode */ + saa7111_write(client, 0x02, + (decoder-> + reg[0x02] & 0xf8) | decoder->input); + /* bypass chrominance trap for modes 4..7 */ + saa7111_write(client, 0x09, + (decoder-> + reg[0x09] & 0x7f) | ((decoder-> + input > + 3) ? 0x80 : + 0)); + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + if (decoder->enable != enable) { + decoder->enable = enable; + + /* RJ: If output should be disabled (for + * playing videos), we also need a open PLL. + * The input is set to 0 (where no input + * source is connected), although this + * is not necessary. + * + * If output should be enabled, we have to + * reverse the above. + */ + + if (decoder->enable) { + saa7111_write(client, 0x02, + (decoder-> + reg[0x02] & 0xf8) | + decoder->input); + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0xfb)); + saa7111_write(client, 0x11, + (decoder-> + reg[0x11] & 0xf3) | 0x0c); + } else { + saa7111_write(client, 0x02, + (decoder->reg[0x02] & 0xf8)); + saa7111_write(client, 0x08, + (decoder-> + reg[0x08] & 0xfb) | 0x04); + saa7111_write(client, 0x11, + (decoder->reg[0x11] & 0xf3)); + } + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + saa7111_write(client, 0x0a, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7111_write(client, 0x0b, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7111_write(client, 0x0c, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7111_write(client, 0x0d, + (decoder->hue - 32768) >> 8); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { I2C_SAA7111 >> 1, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_saa7111; + +static int +saa7111_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct saa7111 *decoder; + struct video_decoder_init vdi; + + dprintk(1, + KERN_INFO + "saa7111.c: detecting saa7111 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7111; + client->flags = I2C_CLIENT_ALLOW_USE; + strlcpy(I2C_NAME(client), "saa7111", sizeof(I2C_NAME(client))); + + decoder = kmalloc(sizeof(struct saa7111), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(decoder, 0, sizeof(struct saa7111)); + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + i2c_set_clientdata(client, decoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(decoder); + return i; + } + + vdi.data = saa7111_i2c_init; + vdi.len = sizeof(saa7111_i2c_init); + i = saa7111_init_decoder(client, &vdi); + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach error: init status %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version %x at address 0x%x\n", + I2C_NAME(client), saa7111_read(client, 0x00) >> 4, + client->addr << 1); + } + + return 0; +} + +static int +saa7111_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7111.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7111_detect_client); +} + +static int +saa7111_detach_client (struct i2c_client *client) +{ + struct saa7111 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7111 = { + .owner = THIS_MODULE, + .name = "saa7111", + + .id = I2C_DRIVERID_SAA7111A, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = saa7111_attach_adapter, + .detach_client = saa7111_detach_client, + .command = saa7111_command, +}; + +static int __init +saa7111_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7111); +} + +static void __exit +saa7111_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7111); +} + +module_init(saa7111_init); +module_exit(saa7111_exit); diff --git a/drivers/media/video/saa7114.c b/drivers/media/video/saa7114.c new file mode 100644 index 000000000000..e73023695e58 --- /dev/null +++ b/drivers/media/video/saa7114.c @@ -0,0 +1,1241 @@ +/* + * saa7114 - Philips SAA7114H video decoder driver version 0.0.1 + * + * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Based on saa7111 driver by Dave Perks + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> + +#include <linux/slab.h> + +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7114H video decoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(x) (x)->name + +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct saa7114 { + unsigned char reg[0xf0 * 2]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; + int playback; +}; + +#define I2C_SAA7114 0x42 +#define I2C_SAA7114A 0x40 + +#define I2C_DELAY 10 + + +//#define SAA_7114_NTSC_HSYNC_START (-3) +//#define SAA_7114_NTSC_HSYNC_STOP (-18) + +#define SAA_7114_NTSC_HSYNC_START (-17) +#define SAA_7114_NTSC_HSYNC_STOP (-32) + +//#define SAA_7114_NTSC_HOFFSET (5) +#define SAA_7114_NTSC_HOFFSET (6) +#define SAA_7114_NTSC_VOFFSET (10) +#define SAA_7114_NTSC_WIDTH (720) +#define SAA_7114_NTSC_HEIGHT (250) + +#define SAA_7114_SECAM_HSYNC_START (-17) +#define SAA_7114_SECAM_HSYNC_STOP (-32) + +#define SAA_7114_SECAM_HOFFSET (2) +#define SAA_7114_SECAM_VOFFSET (10) +#define SAA_7114_SECAM_WIDTH (720) +#define SAA_7114_SECAM_HEIGHT (300) + +#define SAA_7114_PAL_HSYNC_START (-17) +#define SAA_7114_PAL_HSYNC_STOP (-32) + +#define SAA_7114_PAL_HOFFSET (2) +#define SAA_7114_PAL_VOFFSET (10) +#define SAA_7114_PAL_WIDTH (720) +#define SAA_7114_PAL_HEIGHT (300) + + + +#define SAA_7114_VERTICAL_CHROMA_OFFSET 0 //0x50504040 +#define SAA_7114_VERTICAL_LUMA_OFFSET 0 + +#define REG_ADDR(x) (((x) << 1) + 1) +#define LOBYTE(x) ((unsigned char)((x) & 0xff)) +#define HIBYTE(x) ((unsigned char)(((x) >> 8) & 0xff)) +#define LOWORD(x) ((unsigned short int)((x) & 0xffff)) +#define HIWORD(x) ((unsigned short int)(((x) >> 16) & 0xffff)) + + +/* ----------------------------------------------------------------------- */ + +static inline int +saa7114_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + /*struct saa7114 *decoder = i2c_get_clientdata(client);*/ + + /*decoder->reg[reg] = value;*/ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7114_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the saa7114 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + /*struct saa7114 *decoder = i2c_get_clientdata(client);*/ + struct i2c_msg msg; + u8 block_data[32]; + + msg.addr = client->addr; + msg.flags = 0; + while (len >= 2) { + msg.buf = (char *) block_data; + msg.len = 0; + block_data[msg.len++] = reg = data[0]; + do { + block_data[msg.len++] = + /*decoder->reg[reg++] =*/ data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + msg.len < 32); + if ((ret = i2c_transfer(client->adapter, + &msg, 1)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = saa7114_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static inline int +saa7114_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +// initially set NTSC, composite + + +static const unsigned char init[] = { + 0x00, 0x00, /* 00 - ID byte , chip version, + * read only */ + 0x01, 0x08, /* 01 - X,X,X,X, IDEL3 to IDEL0 - + * horizontal increment delay, + * recommended position */ + 0x02, 0x00, /* 02 - FUSE=3, GUDL=2, MODE=0 ; + * input control */ + 0x03, 0x10, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, + * HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x04, 0x90, /* 04 - GAI1=256 */ + 0x05, 0x90, /* 05 - GAI2=256 */ + 0x06, SAA_7114_NTSC_HSYNC_START, /* 06 - HSB: hsync start, + * depends on the video standard */ + 0x07, SAA_7114_NTSC_HSYNC_STOP, /* 07 - HSS: hsync stop, depends + *on the video standard */ + 0x08, 0xb8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, + * HPLL: free running in playback, locked + * in capture, VNOI=0 */ + 0x09, 0x80, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, + * UPTCV=0, APER=1; depends from input */ + 0x0a, 0x80, /* 0a - BRIG=128 */ + 0x0b, 0x44, /* 0b - CONT=1.109 */ + 0x0c, 0x40, /* 0c - SATN=1.0 */ + 0x0d, 0x00, /* 0d - HUE=0 */ + 0x0e, 0x84, /* 0e - CDTO, CSTD2 to 0, DCVF, FCTC, + * CCOMB; depends from video standard */ + 0x0f, 0x24, /* 0f - ACGC,CGAIN6 to CGAIN0; depends + * from video standard */ + 0x10, 0x03, /* 10 - OFFU1 to 0, OFFV1 to 0, CHBW, + * LCBW2 to 0 */ + 0x11, 0x59, /* 11 - COLO, RTP1, HEDL1 to 0, RTP0, + * YDEL2 to 0 */ + 0x12, 0xc9, /* 12 - RT signal control RTSE13 to 10 + * and 03 to 00 */ + 0x13, 0x80, /* 13 - RT/X port output control */ + 0x14, 0x00, /* 14 - analog, ADC, compatibility control */ + 0x15, 0x00, /* 15 - VGATE start FID change */ + 0x16, 0xfe, /* 16 - VGATE stop */ + 0x17, 0x00, /* 17 - Misc., VGATE MSBs */ + 0x18, 0x40, /* RAWG */ + 0x19, 0x80, /* RAWO */ + 0x1a, 0x00, + 0x1b, 0x00, + 0x1c, 0x00, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x00, /* status byte, read only */ + 0x20, 0x00, /* video decoder reserved part */ + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x24, 0x00, + 0x25, 0x00, + 0x26, 0x00, + 0x27, 0x00, + 0x28, 0x00, + 0x29, 0x00, + 0x2a, 0x00, + 0x2b, 0x00, + 0x2c, 0x00, + 0x2d, 0x00, + 0x2e, 0x00, + 0x2f, 0x00, + 0x30, 0xbc, /* audio clock generator */ + 0x31, 0xdf, + 0x32, 0x02, + 0x33, 0x00, + 0x34, 0xcd, + 0x35, 0xcc, + 0x36, 0x3a, + 0x37, 0x00, + 0x38, 0x03, + 0x39, 0x10, + 0x3a, 0x00, + 0x3b, 0x00, + 0x3c, 0x00, + 0x3d, 0x00, + 0x3e, 0x00, + 0x3f, 0x00, + 0x40, 0x00, /* VBI data slicer */ + 0x41, 0xff, + 0x42, 0xff, + 0x43, 0xff, + 0x44, 0xff, + 0x45, 0xff, + 0x46, 0xff, + 0x47, 0xff, + 0x48, 0xff, + 0x49, 0xff, + 0x4a, 0xff, + 0x4b, 0xff, + 0x4c, 0xff, + 0x4d, 0xff, + 0x4e, 0xff, + 0x4f, 0xff, + 0x50, 0xff, + 0x51, 0xff, + 0x52, 0xff, + 0x53, 0xff, + 0x54, 0xff, + 0x55, 0xff, + 0x56, 0xff, + 0x57, 0xff, + 0x58, 0x40, // framing code + 0x59, 0x47, // horizontal offset + 0x5a, 0x06, // vertical offset + 0x5b, 0x83, // field offset + 0x5c, 0x00, // reserved + 0x5d, 0x3e, // header and data + 0x5e, 0x00, // sliced data + 0x5f, 0x00, // reserved + 0x60, 0x00, /* video decoder reserved part */ + 0x61, 0x00, + 0x62, 0x00, + 0x63, 0x00, + 0x64, 0x00, + 0x65, 0x00, + 0x66, 0x00, + 0x67, 0x00, + 0x68, 0x00, + 0x69, 0x00, + 0x6a, 0x00, + 0x6b, 0x00, + 0x6c, 0x00, + 0x6d, 0x00, + 0x6e, 0x00, + 0x6f, 0x00, + 0x70, 0x00, /* video decoder reserved part */ + 0x71, 0x00, + 0x72, 0x00, + 0x73, 0x00, + 0x74, 0x00, + 0x75, 0x00, + 0x76, 0x00, + 0x77, 0x00, + 0x78, 0x00, + 0x79, 0x00, + 0x7a, 0x00, + 0x7b, 0x00, + 0x7c, 0x00, + 0x7d, 0x00, + 0x7e, 0x00, + 0x7f, 0x00, + 0x80, 0x00, /* X-port, I-port and scaler */ + 0x81, 0x00, + 0x82, 0x00, + 0x83, 0x00, + 0x84, 0xc5, + 0x85, 0x0d, // hsync and vsync ? + 0x86, 0x40, + 0x87, 0x01, + 0x88, 0x00, + 0x89, 0x00, + 0x8a, 0x00, + 0x8b, 0x00, + 0x8c, 0x00, + 0x8d, 0x00, + 0x8e, 0x00, + 0x8f, 0x00, + 0x90, 0x03, /* Task A definition */ + 0x91, 0x08, + 0x92, 0x00, + 0x93, 0x40, + 0x94, 0x00, // window settings + 0x95, 0x00, + 0x96, 0x00, + 0x97, 0x00, + 0x98, 0x00, + 0x99, 0x00, + 0x9a, 0x00, + 0x9b, 0x00, + 0x9c, 0x00, + 0x9d, 0x00, + 0x9e, 0x00, + 0x9f, 0x00, + 0xa0, 0x01, /* horizontal integer prescaling ratio */ + 0xa1, 0x00, /* horizontal prescaler accumulation + * sequence length */ + 0xa2, 0x00, /* UV FIR filter, Y FIR filter, prescaler + * DC gain */ + 0xa3, 0x00, + 0xa4, 0x80, // luminance brightness + 0xa5, 0x40, // luminance gain + 0xa6, 0x40, // chrominance saturation + 0xa7, 0x00, + 0xa8, 0x00, // horizontal luminance scaling increment + 0xa9, 0x04, + 0xaa, 0x00, // horizontal luminance phase offset + 0xab, 0x00, + 0xac, 0x00, // horizontal chrominance scaling increment + 0xad, 0x02, + 0xae, 0x00, // horizontal chrominance phase offset + 0xaf, 0x00, + 0xb0, 0x00, // vertical luminance scaling increment + 0xb1, 0x04, + 0xb2, 0x00, // vertical chrominance scaling increment + 0xb3, 0x04, + 0xb4, 0x00, + 0xb5, 0x00, + 0xb6, 0x00, + 0xb7, 0x00, + 0xb8, 0x00, + 0xb9, 0x00, + 0xba, 0x00, + 0xbb, 0x00, + 0xbc, 0x00, + 0xbd, 0x00, + 0xbe, 0x00, + 0xbf, 0x00, + 0xc0, 0x02, // Task B definition + 0xc1, 0x08, + 0xc2, 0x00, + 0xc3, 0x40, + 0xc4, 0x00, // window settings + 0xc5, 0x00, + 0xc6, 0x00, + 0xc7, 0x00, + 0xc8, 0x00, + 0xc9, 0x00, + 0xca, 0x00, + 0xcb, 0x00, + 0xcc, 0x00, + 0xcd, 0x00, + 0xce, 0x00, + 0xcf, 0x00, + 0xd0, 0x01, // horizontal integer prescaling ratio + 0xd1, 0x00, // horizontal prescaler accumulation sequence length + 0xd2, 0x00, // UV FIR filter, Y FIR filter, prescaler DC gain + 0xd3, 0x00, + 0xd4, 0x80, // luminance brightness + 0xd5, 0x40, // luminance gain + 0xd6, 0x40, // chrominance saturation + 0xd7, 0x00, + 0xd8, 0x00, // horizontal luminance scaling increment + 0xd9, 0x04, + 0xda, 0x00, // horizontal luminance phase offset + 0xdb, 0x00, + 0xdc, 0x00, // horizontal chrominance scaling increment + 0xdd, 0x02, + 0xde, 0x00, // horizontal chrominance phase offset + 0xdf, 0x00, + 0xe0, 0x00, // vertical luminance scaling increment + 0xe1, 0x04, + 0xe2, 0x00, // vertical chrominance scaling increment + 0xe3, 0x04, + 0xe4, 0x00, + 0xe5, 0x00, + 0xe6, 0x00, + 0xe7, 0x00, + 0xe8, 0x00, + 0xe9, 0x00, + 0xea, 0x00, + 0xeb, 0x00, + 0xec, 0x00, + 0xed, 0x00, + 0xee, 0x00, + 0xef, 0x00 +}; + +static int +saa7114_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7114 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + //dprintk(1, KERN_INFO "%s: writing init\n", I2C_NAME(client)); + //saa7114_write_block(client, init, sizeof(init)); + break; + + case DECODER_DUMP: + { + int i; + + dprintk(1, KERN_INFO "%s: decoder dump\n", I2C_NAME(client)); + + for (i = 0; i < 32; i += 16) { + int j; + + printk(KERN_DEBUG "%s: %03x", I2C_NAME(client), i); + for (j = 0; j < 16; ++j) { + printk(" %02x", + saa7114_read(client, i + j)); + } + printk("\n"); + } + } + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + dprintk(1, KERN_DEBUG "%s: decoder get capabilities\n", + I2C_NAME(client)); + + cap->flags = VIDEO_DECODER_PAL | + VIDEO_DECODER_NTSC | + VIDEO_DECODER_AUTO | + VIDEO_DECODER_CCIR; + cap->inputs = 8; + cap->outputs = 1; + } + break; + + case DECODER_GET_STATUS: + { + int *iarg = arg; + int status; + int res; + + status = saa7114_read(client, 0x1f); + + dprintk(1, KERN_DEBUG "%s status: 0x%02x\n", I2C_NAME(client), + status); + res = 0; + if ((status & (1 << 6)) == 0) { + res |= DECODER_STATUS_GOOD; + } + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + case VIDEO_MODE_SECAM: + res |= DECODER_STATUS_SECAM; + break; + default: + case VIDEO_MODE_AUTO: + if ((status & (1 << 5)) != 0) { + res |= DECODER_STATUS_NTSC; + } else { + res |= DECODER_STATUS_PAL; + } + break; + } + if ((status & (1 << 0)) != 0) { + res |= DECODER_STATUS_COLOR; + } + *iarg = res; + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg; + + short int hoff = 0, voff = 0, w = 0, h = 0; + + dprintk(1, KERN_DEBUG "%s: decoder set norm ", + I2C_NAME(client)); + switch (*iarg) { + + case VIDEO_MODE_NTSC: + dprintk(1, "NTSC\n"); + decoder->reg[REG_ADDR(0x06)] = + SAA_7114_NTSC_HSYNC_START; + decoder->reg[REG_ADDR(0x07)] = + SAA_7114_NTSC_HSYNC_STOP; + + decoder->reg[REG_ADDR(0x08)] = decoder->playback ? 0x7c : 0xb8; // PLL free when playback, PLL close when capture + + decoder->reg[REG_ADDR(0x0e)] = 0x85; + decoder->reg[REG_ADDR(0x0f)] = 0x24; + + hoff = SAA_7114_NTSC_HOFFSET; + voff = SAA_7114_NTSC_VOFFSET; + w = SAA_7114_NTSC_WIDTH; + h = SAA_7114_NTSC_HEIGHT; + + break; + + case VIDEO_MODE_PAL: + dprintk(1, "PAL\n"); + decoder->reg[REG_ADDR(0x06)] = + SAA_7114_PAL_HSYNC_START; + decoder->reg[REG_ADDR(0x07)] = + SAA_7114_PAL_HSYNC_STOP; + + decoder->reg[REG_ADDR(0x08)] = decoder->playback ? 0x7c : 0xb8; // PLL free when playback, PLL close when capture + + decoder->reg[REG_ADDR(0x0e)] = 0x81; + decoder->reg[REG_ADDR(0x0f)] = 0x24; + + hoff = SAA_7114_PAL_HOFFSET; + voff = SAA_7114_PAL_VOFFSET; + w = SAA_7114_PAL_WIDTH; + h = SAA_7114_PAL_HEIGHT; + + break; + + default: + dprintk(1, " Unknown video mode!!!\n"); + return -EINVAL; + + } + + + decoder->reg[REG_ADDR(0x94)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0x95)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0x96)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0x97)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0x98)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0x99)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0x9a)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0x9b)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0x9c)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0x9d)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0x9e)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0x9f)] = HIBYTE(h) & 0x0f; // out height high + + decoder->reg[REG_ADDR(0xc4)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0xc5)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0xc6)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0xc7)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0xc8)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0xc9)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0xca)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0xcb)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0xcc)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0xcd)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0xce)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0xcf)] = HIBYTE(h) & 0x0f; // out height high + + + saa7114_write(client, 0x80, 0x06); // i-port and scaler back end clock selection, task A&B off + saa7114_write(client, 0x88, 0xd8); // sw reset scaler + saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + + saa7114_write_block(client, decoder->reg + (0x06 << 1), + 3 << 1); + saa7114_write_block(client, decoder->reg + (0x0e << 1), + 2 << 1); + saa7114_write_block(client, decoder->reg + (0x5a << 1), + 2 << 1); + + saa7114_write_block(client, decoder->reg + (0x94 << 1), + (0x9f + 1 - 0x94) << 1); + saa7114_write_block(client, decoder->reg + (0xc4 << 1), + (0xcf + 1 - 0xc4) << 1); + + saa7114_write(client, 0x88, 0xd8); // sw reset scaler + saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + saa7114_write(client, 0x80, 0x36); // i-port and scaler back end clock selection + + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + dprintk(1, KERN_DEBUG "%s: decoder set input (%d)\n", + I2C_NAME(client), *iarg); + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + + if (decoder->input != *iarg) { + dprintk(1, KERN_DEBUG "%s: now setting %s input\n", + I2C_NAME(client), + *iarg >= 6 ? "S-Video" : "Composite"); + decoder->input = *iarg; + + /* select mode */ + decoder->reg[REG_ADDR(0x02)] = + (decoder-> + reg[REG_ADDR(0x02)] & 0xf0) | (decoder-> + input < + 6 ? 0x0 : 0x9); + saa7114_write(client, 0x02, + decoder->reg[REG_ADDR(0x02)]); + + /* bypass chrominance trap for modes 6..9 */ + decoder->reg[REG_ADDR(0x09)] = + (decoder-> + reg[REG_ADDR(0x09)] & 0x7f) | (decoder-> + input < + 6 ? 0x0 : + 0x80); + saa7114_write(client, 0x09, + decoder->reg[REG_ADDR(0x09)]); + + decoder->reg[REG_ADDR(0x0e)] = + decoder->input < + 6 ? decoder-> + reg[REG_ADDR(0x0e)] | 1 : decoder-> + reg[REG_ADDR(0x0e)] & ~1; + saa7114_write(client, 0x0e, + decoder->reg[REG_ADDR(0x0e)]); + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_DEBUG "%s: decoder set output\n", + I2C_NAME(client)); + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + dprintk(1, KERN_DEBUG "%s: decoder %s output\n", + I2C_NAME(client), enable ? "enable" : "disable"); + + decoder->playback = !enable; + + if (decoder->enable != enable) { + decoder->enable = enable; + + /* RJ: If output should be disabled (for + * playing videos), we also need a open PLL. + * The input is set to 0 (where no input + * source is connected), although this + * is not necessary. + * + * If output should be enabled, we have to + * reverse the above. + */ + + if (decoder->enable) { + decoder->reg[REG_ADDR(0x08)] = 0xb8; + decoder->reg[REG_ADDR(0x12)] = 0xc9; + decoder->reg[REG_ADDR(0x13)] = 0x80; + decoder->reg[REG_ADDR(0x87)] = 0x01; + } else { + decoder->reg[REG_ADDR(0x08)] = 0x7c; + decoder->reg[REG_ADDR(0x12)] = 0x00; + decoder->reg[REG_ADDR(0x13)] = 0x00; + decoder->reg[REG_ADDR(0x87)] = 0x00; + } + + saa7114_write_block(client, + decoder->reg + (0x12 << 1), + 2 << 1); + saa7114_write(client, 0x08, + decoder->reg[REG_ADDR(0x08)]); + saa7114_write(client, 0x87, + decoder->reg[REG_ADDR(0x87)]); + saa7114_write(client, 0x88, 0xd8); // sw reset scaler + saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + saa7114_write(client, 0x80, 0x36); + + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + dprintk(1, + KERN_DEBUG + "%s: decoder set picture bright=%d contrast=%d saturation=%d hue=%d\n", + I2C_NAME(client), pic->brightness, pic->contrast, + pic->colour, pic->hue); + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + saa7114_write(client, 0x0a, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7114_write(client, 0x0b, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7114_write(client, 0x0c, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7114_write(client, 0x0d, + (decoder->hue - 32768) >> 8); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_SAA7114 >> 1, I2C_SAA7114A >> 1, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_saa7114; + +static int +saa7114_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i, err[30]; + short int hoff = SAA_7114_NTSC_HOFFSET; + short int voff = SAA_7114_NTSC_VOFFSET; + short int w = SAA_7114_NTSC_WIDTH; + short int h = SAA_7114_NTSC_HEIGHT; + struct i2c_client *client; + struct saa7114 *decoder; + + dprintk(1, + KERN_INFO + "saa7114.c: detecting saa7114 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7114; + client->flags = I2C_CLIENT_ALLOW_USE; + strlcpy(I2C_NAME(client), "saa7114", sizeof(I2C_NAME(client))); + + decoder = kmalloc(sizeof(struct saa7114), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(decoder, 0, sizeof(struct saa7114)); + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = -1; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + decoder->playback = 0; // initially capture mode useda + i2c_set_clientdata(client, decoder); + + memcpy(decoder->reg, init, sizeof(init)); + + decoder->reg[REG_ADDR(0x94)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0x95)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0x96)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0x97)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0x98)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0x99)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0x9a)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0x9b)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0x9c)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0x9d)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0x9e)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0x9f)] = HIBYTE(h) & 0x0f; // out height high + + decoder->reg[REG_ADDR(0xc4)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0xc5)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0xc6)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0xc7)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0xc8)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0xc9)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0xca)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0xcb)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0xcc)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0xcd)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0xce)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0xcf)] = HIBYTE(h) & 0x0f; // out height high + + decoder->reg[REG_ADDR(0xb8)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xb9)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xba)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xbb)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + + decoder->reg[REG_ADDR(0xbc)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xbd)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xbe)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xbf)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + + decoder->reg[REG_ADDR(0xe8)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xe9)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xea)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xeb)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + + decoder->reg[REG_ADDR(0xec)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xed)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xee)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xef)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + + + decoder->reg[REG_ADDR(0x13)] = 0x80; // RTC0 on + decoder->reg[REG_ADDR(0x87)] = 0x01; // I-Port + decoder->reg[REG_ADDR(0x12)] = 0xc9; // RTS0 + + decoder->reg[REG_ADDR(0x02)] = 0xc0; // set composite1 input, aveasy + decoder->reg[REG_ADDR(0x09)] = 0x00; // chrominance trap + decoder->reg[REG_ADDR(0x0e)] |= 1; // combfilter on + + + dprintk(1, KERN_DEBUG "%s_attach: starting decoder init\n", + I2C_NAME(client)); + + err[0] = + saa7114_write_block(client, decoder->reg + (0x20 << 1), + 0x10 << 1); + err[1] = + saa7114_write_block(client, decoder->reg + (0x30 << 1), + 0x10 << 1); + err[2] = + saa7114_write_block(client, decoder->reg + (0x63 << 1), + (0x7f + 1 - 0x63) << 1); + err[3] = + saa7114_write_block(client, decoder->reg + (0x89 << 1), + 6 << 1); + err[4] = + saa7114_write_block(client, decoder->reg + (0xb8 << 1), + 8 << 1); + err[5] = + saa7114_write_block(client, decoder->reg + (0xe8 << 1), + 8 << 1); + + + for (i = 0; i <= 5; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + for (i = 6; i < 8; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + dprintk(1, + KERN_DEBUG + "%s_attach: performing decoder reset sequence\n", + I2C_NAME(client)); + + err[6] = saa7114_write(client, 0x80, 0x06); // i-port and scaler backend clock selection, task A&B off + err[7] = saa7114_write(client, 0x88, 0xd8); // sw reset scaler + err[8] = saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + + for (i = 6; i <= 8; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + dprintk(1, KERN_INFO "%s_attach: performing the rest of init\n", + I2C_NAME(client)); + + + err[9] = saa7114_write(client, 0x01, decoder->reg[REG_ADDR(0x01)]); + err[10] = saa7114_write_block(client, decoder->reg + (0x03 << 1), (0x1e + 1 - 0x03) << 1); // big seq + err[11] = saa7114_write_block(client, decoder->reg + (0x40 << 1), (0x5f + 1 - 0x40) << 1); // slicer + err[12] = saa7114_write_block(client, decoder->reg + (0x81 << 1), 2 << 1); // ? + err[13] = saa7114_write_block(client, decoder->reg + (0x83 << 1), 5 << 1); // ? + err[14] = saa7114_write_block(client, decoder->reg + (0x90 << 1), 4 << 1); // Task A + err[15] = + saa7114_write_block(client, decoder->reg + (0x94 << 1), + 12 << 1); + err[16] = + saa7114_write_block(client, decoder->reg + (0xa0 << 1), + 8 << 1); + err[17] = + saa7114_write_block(client, decoder->reg + (0xa8 << 1), + 8 << 1); + err[18] = + saa7114_write_block(client, decoder->reg + (0xb0 << 1), + 8 << 1); + err[19] = saa7114_write_block(client, decoder->reg + (0xc0 << 1), 4 << 1); // Task B + err[15] = + saa7114_write_block(client, decoder->reg + (0xc4 << 1), + 12 << 1); + err[16] = + saa7114_write_block(client, decoder->reg + (0xd0 << 1), + 8 << 1); + err[17] = + saa7114_write_block(client, decoder->reg + (0xd8 << 1), + 8 << 1); + err[18] = + saa7114_write_block(client, decoder->reg + (0xe0 << 1), + 8 << 1); + + for (i = 9; i <= 18; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + + for (i = 6; i < 8; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + + for (i = 0x11; i <= 0x13; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + + dprintk(1, KERN_DEBUG "%s_attach: setting video input\n", + I2C_NAME(client)); + + err[19] = + saa7114_write(client, 0x02, decoder->reg[REG_ADDR(0x02)]); + err[20] = + saa7114_write(client, 0x09, decoder->reg[REG_ADDR(0x09)]); + err[21] = + saa7114_write(client, 0x0e, decoder->reg[REG_ADDR(0x0e)]); + + for (i = 19; i <= 21; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + dprintk(1, + KERN_DEBUG + "%s_attach: performing decoder reset sequence\n", + I2C_NAME(client)); + + err[22] = saa7114_write(client, 0x88, 0xd8); // sw reset scaler + err[23] = saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + err[24] = saa7114_write(client, 0x80, 0x36); // i-port and scaler backend clock selection, task A&B off + + + for (i = 22; i <= 24; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + err[25] = saa7114_write(client, 0x06, init[REG_ADDR(0x06)]); + err[26] = saa7114_write(client, 0x07, init[REG_ADDR(0x07)]); + err[27] = saa7114_write(client, 0x10, init[REG_ADDR(0x10)]); + + dprintk(1, + KERN_INFO + "%s_attach: chip version %x, decoder status 0x%02x\n", + I2C_NAME(client), saa7114_read(client, 0x00) >> 4, + saa7114_read(client, 0x1f)); + dprintk(1, + KERN_DEBUG + "%s_attach: power save control: 0x%02x, scaler status: 0x%02x\n", + I2C_NAME(client), saa7114_read(client, 0x88), + saa7114_read(client, 0x8f)); + + + for (i = 0x94; i < 0x96; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(decoder); + return i; + } + + //i = saa7114_write_block(client, init, sizeof(init)); + i = 0; + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach error: init status %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version %x at address 0x%x\n", + I2C_NAME(client), saa7114_read(client, 0x00) >> 4, + client->addr << 1); + } + + return 0; +} + +static int +saa7114_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7114.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7114_detect_client); +} + +static int +saa7114_detach_client (struct i2c_client *client) +{ + struct saa7114 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7114 = { + .owner = THIS_MODULE, + .name = "saa7114", + + .id = I2C_DRIVERID_SAA7114, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = saa7114_attach_adapter, + .detach_client = saa7114_detach_client, + .command = saa7114_command, +}; + +static int __init +saa7114_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7114); +} + +static void __exit +saa7114_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7114); +} + +module_init(saa7114_init); +module_exit(saa7114_exit); diff --git a/drivers/media/video/saa7121.h b/drivers/media/video/saa7121.h new file mode 100644 index 000000000000..74e37d405208 --- /dev/null +++ b/drivers/media/video/saa7121.h @@ -0,0 +1,132 @@ +/* saa7121.h - saa7121 initializations + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + 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. + + */ +#ifndef __SAA7121_H__ +#define __SAA7121_H__ + +#define NTSC_BURST_START 0x19 /* 28 */ +#define NTSC_BURST_END 0x1d /* 29 */ +#define NTSC_CHROMA_PHASE 0x67 /* 5a */ +#define NTSC_GAINU 0x76 /* 5b */ +#define NTSC_GAINV 0xa5 /* 5c */ +#define NTSC_BLACK_LEVEL 0x2a /* 5d */ +#define NTSC_BLANKING_LEVEL 0x2e /* 5e */ +#define NTSC_VBI_BLANKING 0x2e /* 5f */ +#define NTSC_DAC_CONTROL 0x11 /* 61 */ +#define NTSC_BURST_AMP 0x3f /* 62 */ +#define NTSC_SUBC3 0x1f /* 63 */ +#define NTSC_SUBC2 0x7c /* 64 */ +#define NTSC_SUBC1 0xf0 /* 65 */ +#define NTSC_SUBC0 0x21 /* 66 */ +#define NTSC_HTRIG 0x72 /* 6c */ +#define NTSC_VTRIG 0x00 /* 6c */ +#define NTSC_MULTI 0x30 /* 6e */ +#define NTSC_CCTTX 0x11 /* 6f */ +#define NTSC_FIRST_ACTIVE 0x12 /* 7a */ +#define NTSC_LAST_ACTIVE 0x02 /* 7b */ +#define NTSC_MSB_VERTICAL 0x40 /* 7c */ + +#define PAL_BURST_START 0x21 /* 28 */ +#define PAL_BURST_END 0x1d /* 29 */ +#define PAL_CHROMA_PHASE 0x3f /* 5a */ +#define PAL_GAINU 0x7d /* 5b */ +#define PAL_GAINV 0xaf /* 5c */ +#define PAL_BLACK_LEVEL 0x23 /* 5d */ +#define PAL_BLANKING_LEVEL 0x35 /* 5e */ +#define PAL_VBI_BLANKING 0x35 /* 5f */ +#define PAL_DAC_CONTROL 0x02 /* 61 */ +#define PAL_BURST_AMP 0x2f /* 62 */ +#define PAL_SUBC3 0xcb /* 63 */ +#define PAL_SUBC2 0x8a /* 64 */ +#define PAL_SUBC1 0x09 /* 65 */ +#define PAL_SUBC0 0x2a /* 66 */ +#define PAL_HTRIG 0x86 /* 6c */ +#define PAL_VTRIG 0x04 /* 6d */ +#define PAL_MULTI 0x20 /* 6e */ +#define PAL_CCTTX 0x15 /* 6f */ +#define PAL_FIRST_ACTIVE 0x16 /* 7a */ +#define PAL_LAST_ACTIVE 0x36 /* 7b */ +#define PAL_MSB_VERTICAL 0x40 /* 7c */ + +/* Initialization Sequence */ + +static __u8 init7121ntsc[] = { + 0x26, 0x0, 0x27, 0x0, + 0x28, NTSC_BURST_START, 0x29, NTSC_BURST_END, + 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, 0x2d, 0x0, + 0x2e, 0x0, 0x2f, 0x0, 0x30, 0x0, 0x31, 0x0, + 0x32, 0x0, 0x33, 0x0, 0x34, 0x0, 0x35, 0x0, + 0x36, 0x0, 0x37, 0x0, 0x38, 0x0, 0x39, 0x0, + 0x3a, 0x03, 0x3b, 0x0, 0x3c, 0x0, 0x3d, 0x0, + 0x3e, 0x0, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0, + 0x42, 0x0, 0x43, 0x0, 0x44, 0x0, 0x45, 0x0, + 0x46, 0x0, 0x47, 0x0, 0x48, 0x0, 0x49, 0x0, + 0x4a, 0x0, 0x4b, 0x0, 0x4c, 0x0, 0x4d, 0x0, + 0x4e, 0x0, 0x4f, 0x0, 0x50, 0x0, 0x51, 0x0, + 0x52, 0x0, 0x53, 0x0, 0x54, 0x0, 0x55, 0x0, + 0x56, 0x0, 0x57, 0x0, 0x58, 0x0, 0x59, 0x0, + 0x5a, NTSC_CHROMA_PHASE, 0x5b, NTSC_GAINU, + 0x5c, NTSC_GAINV, 0x5d, NTSC_BLACK_LEVEL, + 0x5e, NTSC_BLANKING_LEVEL, 0x5f, NTSC_VBI_BLANKING, + 0x60, 0x0, 0x61, NTSC_DAC_CONTROL, + 0x62, NTSC_BURST_AMP, 0x63, NTSC_SUBC3, + 0x64, NTSC_SUBC2, 0x65, NTSC_SUBC1, + 0x66, NTSC_SUBC0, 0x67, 0x80, 0x68, 0x80, + 0x69, 0x80, 0x6a, 0x80, 0x6b, 0x29, + 0x6c, NTSC_HTRIG, 0x6d, NTSC_VTRIG, + 0x6e, NTSC_MULTI, 0x6f, NTSC_CCTTX, + 0x70, 0xc9, 0x71, 0x68, 0x72, 0x60, 0x73, 0x0, + 0x74, 0x0, 0x75, 0x0, 0x76, 0x0, 0x77, 0x0, + 0x78, 0x0, 0x79, 0x0, 0x7a, NTSC_FIRST_ACTIVE, + 0x7b, NTSC_LAST_ACTIVE, 0x7c, NTSC_MSB_VERTICAL, + 0x7d, 0x0, 0x7e, 0x0, 0x7f, 0x0 +}; +#define INIT7121LEN (sizeof(init7121ntsc)/2) + +static __u8 init7121pal[] = { + 0x26, 0x0, 0x27, 0x0, + 0x28, PAL_BURST_START, 0x29, PAL_BURST_END, + 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, 0x2d, 0x0, + 0x2e, 0x0, 0x2f, 0x0, 0x30, 0x0, 0x31, 0x0, + 0x32, 0x0, 0x33, 0x0, 0x34, 0x0, 0x35, 0x0, + 0x36, 0x0, 0x37, 0x0, 0x38, 0x0, 0x39, 0x0, + 0x3a, 0x03, 0x3b, 0x0, 0x3c, 0x0, 0x3d, 0x0, + 0x3e, 0x0, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0, + 0x42, 0x0, 0x43, 0x0, 0x44, 0x0, 0x45, 0x0, + 0x46, 0x0, 0x47, 0x0, 0x48, 0x0, 0x49, 0x0, + 0x4a, 0x0, 0x4b, 0x0, 0x4c, 0x0, 0x4d, 0x0, + 0x4e, 0x0, 0x4f, 0x0, 0x50, 0x0, 0x51, 0x0, + 0x52, 0x0, 0x53, 0x0, 0x54, 0x0, 0x55, 0x0, + 0x56, 0x0, 0x57, 0x0, 0x58, 0x0, 0x59, 0x0, + 0x5a, PAL_CHROMA_PHASE, 0x5b, PAL_GAINU, + 0x5c, PAL_GAINV, 0x5d, PAL_BLACK_LEVEL, + 0x5e, PAL_BLANKING_LEVEL, 0x5f, PAL_VBI_BLANKING, + 0x60, 0x0, 0x61, PAL_DAC_CONTROL, + 0x62, PAL_BURST_AMP, 0x63, PAL_SUBC3, + 0x64, PAL_SUBC2, 0x65, PAL_SUBC1, + 0x66, PAL_SUBC0, 0x67, 0x80, 0x68, 0x80, + 0x69, 0x80, 0x6a, 0x80, 0x6b, 0x29, + 0x6c, PAL_HTRIG, 0x6d, PAL_VTRIG, + 0x6e, PAL_MULTI, 0x6f, PAL_CCTTX, + 0x70, 0xc9, 0x71, 0x68, 0x72, 0x60, 0x73, 0x0, + 0x74, 0x0, 0x75, 0x0, 0x76, 0x0, 0x77, 0x0, + 0x78, 0x0, 0x79, 0x0, 0x7a, PAL_FIRST_ACTIVE, + 0x7b, PAL_LAST_ACTIVE, 0x7c, PAL_MSB_VERTICAL, + 0x7d, 0x0, 0x7e, 0x0, 0x7f, 0x0 +}; +#endif diff --git a/drivers/media/video/saa7134/Makefile b/drivers/media/video/saa7134/Makefile new file mode 100644 index 000000000000..e577a06b136b --- /dev/null +++ b/drivers/media/video/saa7134/Makefile @@ -0,0 +1,11 @@ + +saa7134-objs := saa7134-cards.o saa7134-core.o saa7134-i2c.o \ + saa7134-oss.o saa7134-ts.o saa7134-tvaudio.o \ + saa7134-vbi.o saa7134-video.o saa7134-input.o + +obj-$(CONFIG_VIDEO_SAA7134) += saa7134.o saa7134-empress.o saa6752hs.o +obj-$(CONFIG_VIDEO_SAA7134_DVB) += saa7134-dvb.o + +EXTRA_CFLAGS += -I$(src)/.. +EXTRA_CFLAGS += -I$(srctree)/drivers/media/dvb/dvb-core +EXTRA_CFLAGS += -I$(srctree)/drivers/media/dvb/frontends diff --git a/drivers/media/video/saa7134/saa6752hs.c b/drivers/media/video/saa7134/saa6752hs.c new file mode 100644 index 000000000000..cee13584c9cf --- /dev/null +++ b/drivers/media/video/saa7134/saa6752hs.c @@ -0,0 +1,543 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/crc32.h> + +#include <media/id.h> + +#define MPEG_VIDEO_TARGET_BITRATE_MAX 27000 +#define MPEG_VIDEO_MAX_BITRATE_MAX 27000 +#define MPEG_TOTAL_TARGET_BITRATE_MAX 27000 +#define MPEG_PID_MAX ((1 << 14) - 1) + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {0x20, I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +MODULE_DESCRIPTION("device driver for saa6752hs MPEG2 encoder"); +MODULE_AUTHOR("Andrew de Quincey"); +MODULE_LICENSE("GPL"); + +static struct i2c_driver driver; +static struct i2c_client client_template; + +struct saa6752hs_state { + struct i2c_client client; + struct v4l2_mpeg_compression params; +}; + +enum saa6752hs_command { + SAA6752HS_COMMAND_RESET = 0, + SAA6752HS_COMMAND_STOP = 1, + SAA6752HS_COMMAND_START = 2, + SAA6752HS_COMMAND_PAUSE = 3, + SAA6752HS_COMMAND_RECONFIGURE = 4, + SAA6752HS_COMMAND_SLEEP = 5, + SAA6752HS_COMMAND_RECONFIGURE_FORCE = 6, + + SAA6752HS_COMMAND_MAX +}; + +/* ---------------------------------------------------------------------- */ + +static u8 PAT[] = { + 0xc2, // i2c register + 0x00, // table number for encoder + + 0x47, // sync + 0x40, 0x00, // transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid(0) + 0x10, // transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) + + 0x00, // PSI pointer to start of table + + 0x00, // tid(0) + 0xb0, 0x0d, // section_syntax_indicator(1), section_length(13) + + 0x00, 0x01, // transport_stream_id(1) + + 0xc1, // version_number(0), current_next_indicator(1) + + 0x00, 0x00, // section_number(0), last_section_number(0) + + 0x00, 0x01, // program_number(1) + + 0xe0, 0x00, // PMT PID + + 0x00, 0x00, 0x00, 0x00 // CRC32 +}; + +static u8 PMT[] = { + 0xc2, // i2c register + 0x01, // table number for encoder + + 0x47, // sync + 0x40, 0x00, // transport_error_indicator(0), payload_unit_start(1), transport_priority(0), pid + 0x10, // transport_scrambling_control(00), adaptation_field_control(01), continuity_counter(0) + + 0x00, // PSI pointer to start of table + + 0x02, // tid(2) + 0xb0, 0x17, // section_syntax_indicator(1), section_length(23) + + 0x00, 0x01, // program_number(1) + + 0xc1, // version_number(0), current_next_indicator(1) + + 0x00, 0x00, // section_number(0), last_section_number(0) + + 0xe0, 0x00, // PCR_PID + + 0xf0, 0x00, // program_info_length(0) + + 0x02, 0xe0, 0x00, 0xf0, 0x00, // video stream type(2), pid + 0x04, 0xe0, 0x00, 0xf0, 0x00, // audio stream type(4), pid + + 0x00, 0x00, 0x00, 0x00 // CRC32 +}; + +static struct v4l2_mpeg_compression param_defaults = +{ + .st_type = V4L2_MPEG_TS_2, + .st_bitrate = { + .mode = V4L2_BITRATE_CBR, + .target = 7000, + }, + + .ts_pid_pmt = 16, + .ts_pid_video = 260, + .ts_pid_audio = 256, + .ts_pid_pcr = 259, + + .vi_type = V4L2_MPEG_VI_2, + .vi_aspect_ratio = V4L2_MPEG_ASPECT_4_3, + .vi_bitrate = { + .mode = V4L2_BITRATE_VBR, + .target = 4000, + .max = 6000, + }, + + .au_type = V4L2_MPEG_AU_2_II, + .au_bitrate = { + .mode = V4L2_BITRATE_CBR, + .target = 256, + }, + +#if 0 + /* FIXME: size? via S_FMT? */ + .video_format = MPEG_VIDEO_FORMAT_D1, +#endif +}; + +/* ---------------------------------------------------------------------- */ + +static int saa6752hs_chip_command(struct i2c_client* client, + enum saa6752hs_command command) +{ + unsigned char buf[3]; + unsigned long timeout; + int status = 0; + + // execute the command + switch(command) { + case SAA6752HS_COMMAND_RESET: + buf[0] = 0x00; + break; + + case SAA6752HS_COMMAND_STOP: + buf[0] = 0x03; + break; + + case SAA6752HS_COMMAND_START: + buf[0] = 0x02; + break; + + case SAA6752HS_COMMAND_PAUSE: + buf[0] = 0x04; + break; + + case SAA6752HS_COMMAND_RECONFIGURE: + buf[0] = 0x05; + break; + + case SAA6752HS_COMMAND_SLEEP: + buf[0] = 0x06; + break; + + case SAA6752HS_COMMAND_RECONFIGURE_FORCE: + buf[0] = 0x07; + break; + + default: + return -EINVAL; + } + + // set it and wait for it to be so + i2c_master_send(client, buf, 1); + timeout = jiffies + HZ * 3; + for (;;) { + // get the current status + buf[0] = 0x10; + i2c_master_send(client, buf, 1); + i2c_master_recv(client, buf, 1); + + if (!(buf[0] & 0x20)) + break; + if (time_after(jiffies,timeout)) { + status = -ETIMEDOUT; + break; + } + + // wait a bit + msleep(10); + } + + // delay a bit to let encoder settle + msleep(50); + + // done + return status; +} + + +static int saa6752hs_set_bitrate(struct i2c_client* client, + struct v4l2_mpeg_compression* params) +{ + u8 buf[3]; + + // set the bitrate mode + buf[0] = 0x71; + buf[1] = (params->vi_bitrate.mode == V4L2_BITRATE_VBR) ? 0 : 1; + i2c_master_send(client, buf, 2); + + // set the video bitrate + if (params->vi_bitrate.mode == V4L2_BITRATE_VBR) { + // set the target bitrate + buf[0] = 0x80; + buf[1] = params->vi_bitrate.target >> 8; + buf[2] = params->vi_bitrate.target & 0xff; + i2c_master_send(client, buf, 3); + + // set the max bitrate + buf[0] = 0x81; + buf[1] = params->vi_bitrate.max >> 8; + buf[2] = params->vi_bitrate.max & 0xff; + i2c_master_send(client, buf, 3); + } else { + // set the target bitrate (no max bitrate for CBR) + buf[0] = 0x81; + buf[1] = params->vi_bitrate.target >> 8; + buf[2] = params->vi_bitrate.target & 0xff; + i2c_master_send(client, buf, 3); + } + + // set the audio bitrate + buf[0] = 0x94; + buf[1] = (256 == params->au_bitrate.target) ? 0 : 1; + i2c_master_send(client, buf, 2); + + // set the total bitrate + buf[0] = 0xb1; + buf[1] = params->st_bitrate.target >> 8; + buf[2] = params->st_bitrate.target & 0xff; + i2c_master_send(client, buf, 3); + + // return success + return 0; +} + + +static void saa6752hs_set_params(struct i2c_client* client, + struct v4l2_mpeg_compression* params) +{ + struct saa6752hs_state *h = i2c_get_clientdata(client); + + /* check PIDs */ + if (params->ts_pid_pmt <= MPEG_PID_MAX) + h->params.ts_pid_pmt = params->ts_pid_pmt; + if (params->ts_pid_pcr <= MPEG_PID_MAX) + h->params.ts_pid_pcr = params->ts_pid_pcr; + if (params->ts_pid_video <= MPEG_PID_MAX) + h->params.ts_pid_video = params->ts_pid_video; + if (params->ts_pid_audio <= MPEG_PID_MAX) + h->params.ts_pid_audio = params->ts_pid_audio; + + /* check bitrate parameters */ + if ((params->vi_bitrate.mode == V4L2_BITRATE_CBR) || + (params->vi_bitrate.mode == V4L2_BITRATE_VBR)) + h->params.vi_bitrate.mode = params->vi_bitrate.mode; + if (params->vi_bitrate.mode != V4L2_BITRATE_NONE) + h->params.st_bitrate.target = params->st_bitrate.target; + if (params->vi_bitrate.mode != V4L2_BITRATE_NONE) + h->params.vi_bitrate.target = params->vi_bitrate.target; + if (params->vi_bitrate.mode == V4L2_BITRATE_VBR) + h->params.vi_bitrate.max = params->vi_bitrate.max; + if (params->au_bitrate.mode != V4L2_BITRATE_NONE) + h->params.au_bitrate.target = params->au_bitrate.target; + + /* aspect ratio */ + if (params->vi_aspect_ratio == V4L2_MPEG_ASPECT_4_3 || + params->vi_aspect_ratio == V4L2_MPEG_ASPECT_16_9) + h->params.vi_aspect_ratio = params->vi_aspect_ratio; + + /* range checks */ + if (h->params.st_bitrate.target > MPEG_TOTAL_TARGET_BITRATE_MAX) + h->params.st_bitrate.target = MPEG_TOTAL_TARGET_BITRATE_MAX; + if (h->params.vi_bitrate.target > MPEG_VIDEO_TARGET_BITRATE_MAX) + h->params.vi_bitrate.target = MPEG_VIDEO_TARGET_BITRATE_MAX; + if (h->params.vi_bitrate.max > MPEG_VIDEO_MAX_BITRATE_MAX) + h->params.vi_bitrate.max = MPEG_VIDEO_MAX_BITRATE_MAX; + if (h->params.au_bitrate.target <= 256) + h->params.au_bitrate.target = 256; + else + h->params.au_bitrate.target = 384; +} + +static int saa6752hs_init(struct i2c_client* client) +{ + unsigned char buf[9], buf2[4]; + struct saa6752hs_state *h; + u32 crc; + unsigned char localPAT[256]; + unsigned char localPMT[256]; + + h = i2c_get_clientdata(client); + + // Set video format - must be done first as it resets other settings + buf[0] = 0x41; + buf[1] = 0 /* MPEG_VIDEO_FORMAT_D1 */; + i2c_master_send(client, buf, 2); + + // set bitrate + saa6752hs_set_bitrate(client, &h->params); + + // Set GOP structure {3, 13} + buf[0] = 0x72; + buf[1] = 0x03; + buf[2] = 0x0D; + i2c_master_send(client,buf,3); + + // Set minimum Q-scale {4} + buf[0] = 0x82; + buf[1] = 0x04; + i2c_master_send(client,buf,2); + + // Set maximum Q-scale {12} + buf[0] = 0x83; + buf[1] = 0x0C; + i2c_master_send(client,buf,2); + + // Set Output Protocol + buf[0] = 0xD0; + buf[1] = 0x81; + i2c_master_send(client,buf,2); + + // Set video output stream format {TS} + buf[0] = 0xB0; + buf[1] = 0x05; + i2c_master_send(client,buf,2); + + /* compute PAT */ + memcpy(localPAT, PAT, sizeof(PAT)); + localPAT[17] = 0xe0 | ((h->params.ts_pid_pmt >> 8) & 0x0f); + localPAT[18] = h->params.ts_pid_pmt & 0xff; + crc = crc32_be(~0, &localPAT[7], sizeof(PAT) - 7 - 4); + localPAT[sizeof(PAT) - 4] = (crc >> 24) & 0xFF; + localPAT[sizeof(PAT) - 3] = (crc >> 16) & 0xFF; + localPAT[sizeof(PAT) - 2] = (crc >> 8) & 0xFF; + localPAT[sizeof(PAT) - 1] = crc & 0xFF; + + /* compute PMT */ + memcpy(localPMT, PMT, sizeof(PMT)); + localPMT[3] = 0x40 | ((h->params.ts_pid_pmt >> 8) & 0x0f); + localPMT[4] = h->params.ts_pid_pmt & 0xff; + localPMT[15] = 0xE0 | ((h->params.ts_pid_pcr >> 8) & 0x0F); + localPMT[16] = h->params.ts_pid_pcr & 0xFF; + localPMT[20] = 0xE0 | ((h->params.ts_pid_video >> 8) & 0x0F); + localPMT[21] = h->params.ts_pid_video & 0xFF; + localPMT[25] = 0xE0 | ((h->params.ts_pid_audio >> 8) & 0x0F); + localPMT[26] = h->params.ts_pid_audio & 0xFF; + crc = crc32_be(~0, &localPMT[7], sizeof(PMT) - 7 - 4); + localPMT[sizeof(PMT) - 4] = (crc >> 24) & 0xFF; + localPMT[sizeof(PMT) - 3] = (crc >> 16) & 0xFF; + localPMT[sizeof(PMT) - 2] = (crc >> 8) & 0xFF; + localPMT[sizeof(PMT) - 1] = crc & 0xFF; + + // Set Audio PID + buf[0] = 0xC1; + buf[1] = (h->params.ts_pid_audio >> 8) & 0xFF; + buf[2] = h->params.ts_pid_audio & 0xFF; + i2c_master_send(client,buf,3); + + // Set Video PID + buf[0] = 0xC0; + buf[1] = (h->params.ts_pid_video >> 8) & 0xFF; + buf[2] = h->params.ts_pid_video & 0xFF; + i2c_master_send(client,buf,3); + + // Set PCR PID + buf[0] = 0xC4; + buf[1] = (h->params.ts_pid_pcr >> 8) & 0xFF; + buf[2] = h->params.ts_pid_pcr & 0xFF; + i2c_master_send(client,buf,3); + + // Send SI tables + i2c_master_send(client,localPAT,sizeof(PAT)); + i2c_master_send(client,localPMT,sizeof(PMT)); + + // mute then unmute audio. This removes buzzing artefacts + buf[0] = 0xa4; + buf[1] = 1; + i2c_master_send(client, buf, 2); + buf[1] = 0; + i2c_master_send(client, buf, 2); + + // start it going + saa6752hs_chip_command(client, SAA6752HS_COMMAND_START); + + // readout current state + buf[0] = 0xE1; + buf[1] = 0xA7; + buf[2] = 0xFE; + buf[3] = 0x82; + buf[4] = 0xB0; + i2c_master_send(client, buf, 5); + i2c_master_recv(client, buf2, 4); + + // change aspect ratio + buf[0] = 0xE0; + buf[1] = 0xA7; + buf[2] = 0xFE; + buf[3] = 0x82; + buf[4] = 0xB0; + buf[5] = buf2[0]; + switch(h->params.vi_aspect_ratio) { + case V4L2_MPEG_ASPECT_16_9: + buf[6] = buf2[1] | 0x40; + break; + case V4L2_MPEG_ASPECT_4_3: + default: + buf[6] = buf2[1] & 0xBF; + break; + break; + } + buf[7] = buf2[2]; + buf[8] = buf2[3]; + i2c_master_send(client, buf, 9); + + // return success + return 0; +} + +static int saa6752hs_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct saa6752hs_state *h; + + printk("saa6752hs: chip found @ 0x%x\n", addr<<1); + + if (NULL == (h = kmalloc(sizeof(*h), GFP_KERNEL))) + return -ENOMEM; + memset(h,0,sizeof(*h)); + h->client = client_template; + h->params = param_defaults; + h->client.adapter = adap; + h->client.addr = addr; + + i2c_set_clientdata(&h->client, h); + i2c_attach_client(&h->client); + return 0; +} + +static int saa6752hs_probe(struct i2c_adapter *adap) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa6752hs_attach); + return 0; +} + +static int saa6752hs_detach(struct i2c_client *client) +{ + struct saa6752hs_state *h; + + h = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(h); + return 0; +} + +static int +saa6752hs_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct saa6752hs_state *h = i2c_get_clientdata(client); + struct v4l2_mpeg_compression *params = arg; + int err = 0; + + switch (cmd) { + case VIDIOC_S_MPEGCOMP: + if (NULL == params) { + /* apply settings and start encoder */ + saa6752hs_init(client); + break; + } + saa6752hs_set_params(client, params); + /* fall through */ + case VIDIOC_G_MPEGCOMP: + *params = h->params; + break; + default: + /* nothing */ + break; + } + + return err; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "i2c saa6752hs MPEG encoder", + .id = I2C_DRIVERID_SAA6752HS, + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa6752hs_probe, + .detach_client = saa6752hs_detach, + .command = saa6752hs_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("saa6752hs"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int __init saa6752hs_init_module(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit saa6752hs_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(saa6752hs_init_module); +module_exit(saa6752hs_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/video/saa7134/saa7134-cards.c new file mode 100644 index 000000000000..180d3175ea5b --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-cards.c @@ -0,0 +1,2018 @@ + +/* + * $Id: saa7134-cards.c,v 1.54 2005/03/07 12:01:51 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * card-specific stuff. + * + * (c) 2001-04 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* commly used strings */ +static char name_mute[] = "mute"; +static char name_radio[] = "Radio"; +static char name_tv[] = "Television"; +static char name_tv_mono[] = "TV (mono only)"; +static char name_comp1[] = "Composite1"; +static char name_comp2[] = "Composite2"; +static char name_comp3[] = "Composite3"; +static char name_comp4[] = "Composite4"; +static char name_svideo[] = "S-Video"; + +/* ------------------------------------------------------------------ */ +/* board config info */ + +struct saa7134_board saa7134_boards[] = { + [SAA7134_BOARD_UNKNOWN] = { + .name = "UNKNOWN/GENERIC", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .inputs = {{ + .name = "default", + .vmux = 0, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_PROTEUS_PRO] = { + /* /me */ + .name = "Proteus Pro [philips reference design]", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_FLYVIDEO3000] = { + /* "Marco d'Itri" <md@Linux.IT> */ + .name = "LifeView FlyVIDEO3000", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .gpio = 0x8000, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, + }, + [SAA7134_BOARD_FLYVIDEO2000] = { + /* "TC Wan" <tcwan@cs.usm.my> */ + .name = "LifeView FlyVIDEO2000", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .name = name_mute, + .amux = LINE2, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_FLYTVPLATINUM_MINI] = { + /* "Arnaud Quette" <aquette@free.fr> */ + .name = "LifeView FlyTV Platinum Mini", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_FLYTVPLATINUM_FM] = { + /* LifeView FlyTV Platinum FM (LR214WF) */ + /* "Peter Missel <peter.missel@onlinehome.de> */ + .name = "LifeView FlyTV Platinum FM", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, +// .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, +// .gpio = 0x0000, + .tv = 1, + },{ +/* .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ +*/ .name = name_comp1, /* Composite signal on S-Video input */ + .vmux = 0, + .amux = LINE2, +// .gpio = 0x4000, + },{ + .name = name_comp2, /* Composite input */ + .vmux = 3, + .amux = LINE2, +// .gpio = 0x4000, + },{ + .name = name_svideo, /* S-Video signal on S-Video input */ + .vmux = 8, + .amux = LINE2, +// .gpio = 0x4000, + }}, +/* .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, +*/ }, + [SAA7134_BOARD_EMPRESS] = { + /* "Gert Vervoort" <gert.vervoort@philips.com> */ + .name = "EMPRESS", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_MONSTERTV] = { + /* "K.Ohta" <alpha292@bremen.or.jp> */ + .name = "SKNet Monster TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MD9717] = { + .name = "Tevion MD 9717", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + /* workaround for problems with normal TV sound */ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 2, + .amux = LINE1, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TVSTATION_RDS] = { + /* Typhoon TV Tuner RDS: Art.Nr. 50694 */ + .name = "KNC One TV-Station RDS / Typhoon TV Tuner RDS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + + .name = "CVid over SVid", + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TVSTATION_DVR] = { + .name = "KNC One TV-Station DVR", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x820000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + .gpio = 0x20000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + .gpio = 0x20000, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x20000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x20000, + }, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_CINERGY400] = { + .name = "Terratec Cinergy 400 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp2, // CVideo over SVideo Connector + .vmux = 0, + .amux = LINE1, + }} + }, + [SAA7134_BOARD_MD5044] = { + .name = "Medion 5044", + .audio_clock = 0x00187de7, // was: 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + /* workaround for problems with normal TV sound */ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_KWORLD] = { + .name = "Kworld/KuroutoShikou SAA7130-TVPCI", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_CINERGY600] = { + .name = "Terratec Cinergy 600 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp2, // CVideo over SVideo Connector + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_MD7134] = { + .name = "Medion 7134", + //.audio_clock = 0x00200000, + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_TYPHOON_90031] = { + /* aka Typhoon "TV+Radio", Art.Nr 90031 */ + /* Tom Zoerner <tomzo at users sourceforge net> */ + .name = "Typhoon TV+Radio 90031", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .tda9887_conf = TDA9887_PRESENT, + .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 = LINE2, + }, + }, + [SAA7134_BOARD_ELSA] = { + .name = "ELSA EX-VISION 300TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 4, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_ELSA_500TV] = { + .name = "ELSA EX-VISION 500TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_HITACHI_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 7, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 8, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 8, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_ASUSTeK_TVFM7134] = { + .name = "ASUS TV-FM 7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 6, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + }, + }, + [SAA7135_BOARD_ASUSTeK_TVFM7135] = { + .name = "ASUS TV-FM 7135", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .gpiomask = 0x200000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE2, + .gpio = 0x0000, + },{ + .name = name_svideo, + .vmux = 6, + .amux = LINE2, + .gpio = 0x0000, + }}, + .radio = { + .name = name_radio, + .amux = TV, + .gpio = 0x200000, + }, + }, + [SAA7134_BOARD_VA1000POWER] = { + .name = "AOPEN VA1000 POWER", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_10MOONSTVMASTER] = { + /* "lilicheng" <llc@linuxfans.org> */ + .name = "10MOONS PCI TV CAPTURE CARD", + .audio_clock = 0x00200000, + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x4000, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x4000, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x2000, + }, + .mute = { + .name = name_mute, + .amux = LINE2, + .gpio = 0x8000, + }, + }, + [SAA7134_BOARD_BMK_MPEX_NOTUNER] = { + /* "Andrew de Quincey" <adq@lidskialf.net> */ + .name = "BMK MPEX No Tuner", + .audio_clock = 0x200000, + .tuner_type = TUNER_ABSENT, + .inputs = {{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_comp3, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_comp4, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_VIDEOMATE_TV] = { + .name = "Compro VideoMate TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS] = { + .name = "Compro VideoMate TV Gold+", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .gpiomask = 0x800c0000, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + .gpio = 0x06c00012, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x0ac20012, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .gpio = 0x08c20012, + .tv = 1, + }}, + }, + [SAA7134_BOARD_CRONOS_PLUS] = { + /* gpio pins: + 0 .. 3 BASE_ID + 4 .. 7 PROTECT_ID + 8 .. 11 USER_OUT + 12 .. 13 USER_IN + 14 .. 15 VIDIN_SEL */ + .name = "Matrox CronosPlus", + .tuner_type = TUNER_ABSENT, + .gpiomask = 0xcf00, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .gpio = 2 << 14, + },{ + .name = name_comp2, + .vmux = 0, + .gpio = 1 << 14, + },{ + .name = name_comp3, + .vmux = 0, + .gpio = 0 << 14, + },{ + .name = name_comp4, + .vmux = 0, + .gpio = 3 << 14, + },{ + .name = name_svideo, + .vmux = 8, + .gpio = 2 << 14, + }}, + }, + [SAA7134_BOARD_MD2819] = { + .name = "AverMedia M156 / Medion 2819", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_BMK_MPEX_TUNER] = { + /* "Greg Wickham <greg.wickham@grangenet.net> */ + .name = "BMK MPEX Tuner", + .audio_clock = 0x200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 3, + .amux = TV, + .tv = 1, + }}, + .mpeg = SAA7134_MPEG_EMPRESS, + .video_out = CCIR656, + }, + [SAA7134_BOARD_ASUSTEK_TVFM7133] = { + .name = "ASUS TV-FM 7133", + .audio_clock = 0x00187de7, + // probably wrong, the 7133 one is the NTSC version ... + // .tuner_type = TUNER_PHILIPS_FM1236_MK3 + .tuner_type = TUNER_LG_NTSC_NEW_TAPC, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 6, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_PINNACLE_PCTV_STEREO] = { + .name = "Pinnacle PCTV Stereo (saa7134)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_MT2032, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER, + .inputs = {{ + .name = name_tv, + .vmux = 3, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 1, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_MANLI_MTV002] = { + /* Ognjen Nastic <ognjen@logosoft.ba> */ + .name = "Manli MuchTV M-TV002", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + .mute = { + .name = name_mute, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_MANLI_MTV001] = { + /* Ognjen Nastic <ognjen@logosoft.ba> UNTESTED */ + .name = "Manli MuchTV M-TV001", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_TG3000TV] = { + /* TransGear 3000TV */ + .name = "Nagase Sangyo TransGear 3000TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_ECS_TVP3XP] = { + .name = "Elitegroup ECS TVP3XP FM1216 Tuner Card(PAL-BG,FM) ", + .audio_clock = 0x187de7, // xtal 32.1 MHz + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = "CVid over SVid", + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ECS_TVP3XP_4CB5] = { + .name = "Elitegroup ECS TVP3XP FM1236 Tuner Card (NTSC,FM)", + .audio_clock = 0x187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = "CVid over SVid", + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_AVACSSMARTTV] = { + /* Roman Pszonczenko <romka@kolos.math.uni.lodz.pl> */ + .name = "AVACS SmartTV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x200000, + }, + }, + [SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER] = { + /* Michael Smith <msmith@cbnco.com> */ + .name = "AVerMedia DVD EZMaker", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_ABSENT, + .inputs = {{ + .name = name_comp1, + .vmux = 3, + },{ + .name = name_svideo, + .vmux = 8, + }}, + }, + [SAA7134_BOARD_NOVAC_PRIMETV7133] = { + /* toshii@netbsd.org */ + .name = "Noval Prime TV 7133", + .audio_clock = 0x00200000, + .tuner_type = TUNER_ALPS_TSBH1_NTSC, + .inputs = {{ + .name = name_comp1, + .vmux = 3, + },{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_svideo, + .vmux = 8, + }}, + }, + [SAA7134_BOARD_AVERMEDIA_STUDIO_305] = { + .name = "AverMedia AverTV Studio 305", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1256_IH3, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x3, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + .mute = { + .name = name_mute, + .amux = LINE1, + }, + }, + [SAA7133_BOARD_UPMOST_PURPLE_TV] = { + .name = "UPMOST PURPLE TV", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1236_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 7, + .amux = TV, + .tv = 1, + },{ + .name = name_svideo, + .vmux = 7, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_ITEMS_MTV005] = { + /* Norman Jonas <normanjonas@arcor.de> */ + .name = "Items MuchTV Plus / IT-005", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 3, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_CINERGY200] = { + .name = "Terratec Cinergy 200 TV", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp2, // CVideo over SVideo Connector + .vmux = 0, + .amux = LINE1, + }}, + .mute = { + .name = name_mute, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_VIDEOMATE_TV_PVR] = { + /* Alain St-Denis <alain@topaze.homeip.net> */ + .name = "Compro VideoMate TV PVR/FM", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .gpiomask = 0x808c0080, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + .gpio = 0x00080, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x00080, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2_LEFT, + .tv = 1, + .gpio = 0x00080, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x80000, + }, + .mute = { + .name = name_mute, + .amux = LINE2, + .gpio = 0x40000, + }, + }, + [SAA7134_BOARD_SABRENT_SBTTVFM] = { + /* Michael Rodriguez-Torrent <mrtorrent@asu.edu> */ + .name = "Sabrent SBT-TVFM (saa7130)", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC_M, + .inputs = {{ + .name = name_comp1, + .vmux = 1, + .amux = LINE2, + },{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_ZOLID_XPERT_TV7134] = { + /* Helge Jensen <helge.jensen@slog.dk> */ + .name = ":Zolid Xpert TV7134", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_NTSC, + .inputs = {{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + }, + [SAA7134_BOARD_EMPIRE_PCI_TV_RADIO_LE] = { + /* "Matteo Az" <matte.az@nospam.libero.it> ;-) */ + .name = "Empire PCI TV-Radio LE", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .gpiomask = 0x4000, + .inputs = {{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .gpio = 0x8000, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x8000, + },{ + .name = name_svideo, + .vmux = 6, + .amux = LINE1, + .gpio = 0x8000, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + .gpio = 0x8000, + }, + .mute = { + .name = name_mute, + .amux = TV, + .gpio =0x8000, + } + }, + [SAA7134_BOARD_AVERMEDIA_307] = { + /* + Nickolay V. Shmyrev <nshmyrev@yandex.ru> + Lots of thanks to Andrey Zolotarev <zolotarev_andrey@mail.ru> + */ + .name = "Avermedia AVerTV Studio 307", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1256_IH3, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x03, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + .gpio = 0x00, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + .gpio = 0x00, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + .gpio = 0x00, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + .gpio = 0x00, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + .gpio = 0x01, + }, + }, + [SAA7134_BOARD_AVERMEDIA_CARDBUS] = { + /* Jon Westgate <oryn@oryn.fsck.tv> */ + .name = "AVerMedia Cardbus TV/Radio", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_PAL, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_CINERGY400_CARDBUS] = { + .name = "Terratec Cinergy 400 mobile", + .audio_clock = 0x187de7, + .tuner_type = TUNER_ALPS_TSBE5_PAL, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + }}, + }, + [SAA7134_BOARD_CINERGY600_MK3] = { + .name = "Terratec Cinergy 600 TV MK3", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 4, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + },{ + .name = name_comp2, // CVideo over SVideo Connector + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_VIDEOMATE_GOLD_PLUS] = { + /* Dylan Walkden <dylan_walkden@hotmail.com> */ + .name = "Compro VideoMate Gold+ Pal", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_PAL, + .gpiomask = 0x1ce780, + .inputs = {{ + .name = name_svideo, + .vmux = 0, // CVideo over SVideo Connector - ok? + .amux = LINE1, + .gpio = 0x008080, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + .gpio = 0x008080, + },{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + .gpio = 0x008080, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + .gpio = 0x80000, + }, + .mute = { + .name = name_mute, + .amux = LINE2, + .gpio = 0x0c8000, + }, + }, + [SAA7134_BOARD_PINNACLE_300I_DVBT_PAL] = { + .name = "Pinnacle PCTV 300i DVB-T + PAL", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_MT2032, + .tda9887_conf = TDA9887_PRESENT | TDA9887_INTERCARRIER, + .mpeg = SAA7134_MPEG_DVB, + .inputs = {{ + .name = name_tv, + .vmux = 3, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 1, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + }, + [SAA7134_BOARD_PROVIDEO_PV952] = { + /* andreas.kretschmer@web.de */ + .name = "ProVideo PV952", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .tda9887_conf = TDA9887_PRESENT, + .inputs = {{ + .name = name_comp1, + .vmux = 0, + .amux = LINE1, + },{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_tv_mono, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }}, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, + [SAA7134_BOARD_AVERMEDIA_305] = { + /* much like the "studio" version but without radio + * and another tuner (sirspiritus@yandex.ru) */ + .name = "AverMedia AverTV/305", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_FQ1216ME, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x3, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 0, + .amux = LINE2, + },{ + .name = name_comp2, + .vmux = 3, + .amux = LINE2, + },{ + .name = name_svideo, + .vmux = 8, + .amux = LINE2, + }}, + .mute = { + .name = name_mute, + .amux = LINE1, + }, + }, + [SAA7134_BOARD_FLYDVBTDUO] = { + /* LifeView FlyDVB-T DUO */ + /* "Nico Sabbi <nsabbi@tiscali.it> */ + .name = "LifeView FlyDVB-T DUO", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_TDA8290, +// .gpiomask = 0xe000, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, +// .gpio = 0x0000, + .tv = 1, + },{ + .name = name_comp1, /* Composite signal on S-Video input */ + .vmux = 0, + .amux = LINE2, +// .gpio = 0x4000, + },{ + .name = name_comp2, /* Composite input */ + .vmux = 3, + .amux = LINE2, +// .gpio = 0x4000, + },{ + .name = name_svideo, /* S-Video signal on S-Video input */ + .vmux = 8, + .amux = LINE2, +// .gpio = 0x4000, + }}, + }, +}; +const unsigned int saa7134_bcount = ARRAY_SIZE(saa7134_boards); + +/* ------------------------------------------------------------------ */ +/* PCI ids + subsystem IDs */ + +struct pci_device_id saa7134_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x2001, + .driver_data = SAA7134_BOARD_PROTEUS_PRO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x2001, + .driver_data = SAA7134_BOARD_PROTEUS_PRO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x6752, + .driver_data = SAA7134_BOARD_EMPRESS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1131, + .subdevice = 0x4e85, + .driver_data = SAA7134_BOARD_MONSTERTV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x153B, + .subdevice = 0x1142, + .driver_data = SAA7134_BOARD_CINERGY400, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x153B, + .subdevice = 0x1143, + .driver_data = SAA7134_BOARD_CINERGY600, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x153B, + .subdevice = 0x1158, + .driver_data = SAA7134_BOARD_CINERGY600_MK3, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x153b, + .subdevice = 0x1162, + .driver_data = SAA7134_BOARD_CINERGY400_CARDBUS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x5168, + .subdevice = 0x0138, + .driver_data = SAA7134_BOARD_FLYVIDEO3000, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x4e42, //"Typhoon PCI Capture TV Card" Art.No. 50673 + .subdevice = 0x0138, + .driver_data = SAA7134_BOARD_FLYVIDEO3000, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x5168, + .subdevice = 0x0138, + .driver_data = SAA7134_BOARD_FLYVIDEO2000, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7135, + .subvendor = 0x5168, + .subdevice = 0x0212, /* minipci, LR212 */ + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_MINI, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x5168, + .subdevice = 0x0214, /* Standard PCI, LR214WF */ + .driver_data = SAA7134_BOARD_FLYTVPLATINUM_FM, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x16be, + .subdevice = 0x0003, + .driver_data = SAA7134_BOARD_MD7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1048, + .subdevice = 0x226b, + .driver_data = SAA7134_BOARD_ELSA, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1048, + .subdevice = 0x226b, + .driver_data = SAA7134_BOARD_ELSA_500TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4842, + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4845, + .driver_data = SAA7135_BOARD_ASUSTeK_TVFM7135, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4830, + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4843, + .driver_data = SAA7134_BOARD_ASUSTEK_TVFM7133, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_ASUSTEK, + .subdevice = 0x4840, + .driver_data = SAA7134_BOARD_ASUSTeK_TVFM7134, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0xfe01, + .driver_data = SAA7134_BOARD_TVSTATION_RDS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1894, + .subdevice = 0xfe01, + .driver_data = SAA7134_BOARD_TVSTATION_RDS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1894, + .subdevice = 0xa006, + .driver_data = SAA7134_BOARD_TVSTATION_DVR, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1131, + .subdevice = 0x7133, + .driver_data = SAA7134_BOARD_VA1000POWER, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0x2001, + .driver_data = SAA7134_BOARD_10MOONSTVMASTER, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x185b, + .subdevice = 0xc100, + .driver_data = SAA7134_BOARD_VIDEOMATE_TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x185b, + .subdevice = 0xc100, + .driver_data = SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_VENDOR_ID_MATROX, + .subdevice = 0x48d0, + .driver_data = SAA7134_BOARD_CRONOS_PLUS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0xa70b, + .driver_data = SAA7134_BOARD_MD2819, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x2115, + .driver_data = SAA7134_BOARD_AVERMEDIA_STUDIO_305, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x2108, + .driver_data = SAA7134_BOARD_AVERMEDIA_305, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x10ff, + .driver_data = SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER, + },{ + /* AVerMedia CardBus */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0xd6ee, + .driver_data = SAA7134_BOARD_AVERMEDIA_CARDBUS, + },{ + /* TransGear 3000TV */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x050c, + .driver_data = SAA7134_BOARD_TG3000TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x11bd, + .subdevice = 0x002b, + .driver_data = SAA7134_BOARD_PINNACLE_PCTV_STEREO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x11bd, + .subdevice = 0x002d, + .driver_data = SAA7134_BOARD_PINNACLE_300I_DVBT_PAL, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1019, + .subdevice = 0x4cb4, + .driver_data = SAA7134_BOARD_ECS_TVP3XP, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x1019, + .subdevice = 0x4cb5, + .driver_data = SAA7134_BOARD_ECS_TVP3XP_4CB5, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x12ab, + .subdevice = 0x0800, + .driver_data = SAA7133_BOARD_UPMOST_PURPLE_TV, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x153B, + .subdevice = 0x1152, + .driver_data = SAA7134_BOARD_CINERGY200, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x185b, + .subdevice = 0xc100, + .driver_data = SAA7134_BOARD_VIDEOMATE_TV_PVR, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x1131, + .subdevice = 0, + .driver_data = SAA7134_BOARD_SABRENT_SBTTVFM, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1461, /* Avermedia Technologies Inc */ + .subdevice = 0x9715, + .driver_data = SAA7134_BOARD_AVERMEDIA_307, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x185b, + .subdevice = 0xc200, + .driver_data = SAA7134_BOARD_VIDEOMATE_GOLD_PLUS, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1540, + .subdevice = 0x9524, + .driver_data = SAA7134_BOARD_PROVIDEO_PV952, + + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x5168, + .subdevice = 0x0306, + .driver_data = SAA7134_BOARD_FLYDVBTDUO, + + },{ + /* --- boards without eeprom + subsystem ID --- */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0, + .driver_data = SAA7134_BOARD_NOAUTO, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_VENDOR_ID_PHILIPS, + .subdevice = 0, + .driver_data = SAA7134_BOARD_NOAUTO, + },{ + + /* --- default catch --- */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7135, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = SAA7134_BOARD_UNKNOWN, + },{ + /* --- end of list --- */ + } +}; +MODULE_DEVICE_TABLE(pci, saa7134_pci_tbl); + +/* ----------------------------------------------------------- */ +/* flyvideo tweaks */ + +#if 0 +static struct { + char *model; + int tuner_type; +} fly_list[0x20] = { + /* default catch ... */ + [ 0 ... 0x1f ] = { + .model = "UNKNOWN", + .tuner_type = TUNER_ABSENT, + }, + /* ... the ones known so far */ + [ 0x05 ] = { + .model = "PAL-BG", + .tuner_type = TUNER_LG_PAL_NEW_TAPC, + }, + [ 0x10 ] = { + .model = "PAL-BG / PAL-DK", + .tuner_type = TUNER_PHILIPS_PAL, + }, + [ 0x15 ] = { + .model = "NTSC", + .tuner_type = TUNER_ABSENT /* FIXME */, + }, +}; +#endif + +static void board_flyvideo(struct saa7134_dev *dev) +{ +#if 0 + /* non-working attempt to detect the correct tuner type ... */ + u32 value; + int index; + + value = dev->gpio_value; + index = (value & 0x1f00) >> 8; + printk(KERN_INFO "%s: flyvideo: gpio is 0x%x [model=%s,tuner=%d]\n", + dev->name, value, fly_list[index].model, + fly_list[index].tuner_type); + dev->tuner_type = fly_list[index].tuner_type; +#endif + printk("%s: there are different flyvideo cards with different tuners\n" + "%s: out there, you might have to use the tuner=<nr> insmod\n" + "%s: option to override the default value.\n", + dev->name, dev->name, dev->name); +} + +/* ----------------------------------------------------------- */ + +int saa7134_board_init1(struct saa7134_dev *dev) +{ + // Always print gpio, often manufacturers encode tuner type and other info. + saa_writel(SAA7134_GPIO_GPMODE0 >> 2, 0); + dev->gpio_value = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + printk(KERN_INFO "%s: board init: gpio is %x\n", dev->name, dev->gpio_value); + + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + dev->has_remote = 1; + board_flyvideo(dev); + break; + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + case SAA7134_BOARD_CINERGY600_MK3: + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_AVERMEDIA_STUDIO_305: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_307: +// case SAA7134_BOARD_SABRENT_SBTTVFM: /* not finished yet */ + case SAA7134_BOARD_VIDEOMATE_TV_PVR: + dev->has_remote = 1; + break; + case SAA7134_BOARD_AVACSSMARTTV: + dev->has_remote = 1; + break; + case SAA7134_BOARD_MD5044: + printk("%s: seems there are two different versions of the MD5044\n" + "%s: (with the same ID) out there. If sound doesn't work for\n" + "%s: you try the audio_clock_override=0x200000 insmod option.\n", + dev->name,dev->name,dev->name); + break; + case SAA7134_BOARD_CINERGY400_CARDBUS: + /* power-up tuner chip */ + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, 0x00040000, 0x00040000); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, 0x00040000, 0x00000000); + msleep(1); + break; + } + if (dev->has_remote) + dev->irq2_mask |= (SAA7134_IRQ2_INTE_GPIO18 | + SAA7134_IRQ2_INTE_GPIO18A | + SAA7134_IRQ2_INTE_GPIO16 ); + return 0; +} + +/* stuff which needs working i2c */ +int saa7134_board_init2(struct saa7134_dev *dev) +{ + unsigned char buf; + int board; + + switch (dev->board) { + case SAA7134_BOARD_BMK_MPEX_NOTUNER: + case SAA7134_BOARD_BMK_MPEX_TUNER: + dev->i2c_client.addr = 0x60; + board = (i2c_master_recv(&dev->i2c_client,&buf,0) < 0) + ? SAA7134_BOARD_BMK_MPEX_NOTUNER + : SAA7134_BOARD_BMK_MPEX_TUNER; + if (board == dev->board) + break; + dev->board = board; + printk("%s: board type fixup: %s\n", dev->name, + saa7134_boards[dev->board].name); + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + if (TUNER_ABSENT != dev->tuner_type) + saa7134_i2c_call_clients(dev,TUNER_SET_TYPE,&dev->tuner_type); + break; + } + return 0; +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-core.c b/drivers/media/video/saa7134/saa7134-core.c new file mode 100644 index 000000000000..d506cafba8ff --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-core.c @@ -0,0 +1,1237 @@ +/* + * $Id: saa7134-core.c,v 1.28 2005/02/22 09:56:29 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * driver core + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/sound.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +MODULE_DESCRIPTION("v4l2 driver module for saa7130/34 based TV cards"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +/* ------------------------------------------------------------------ */ + +static unsigned int irq_debug = 0; +module_param(irq_debug, int, 0644); +MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]"); + +static unsigned int core_debug = 0; +module_param(core_debug, int, 0644); +MODULE_PARM_DESC(core_debug,"enable debug messages [core]"); + +static unsigned int gpio_tracking = 0; +module_param(gpio_tracking, int, 0644); +MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]"); + +static unsigned int oss = 0; +module_param(oss, int, 0444); +MODULE_PARM_DESC(oss,"register oss devices (default: no)"); + +static unsigned int latency = UNSET; +module_param(latency, int, 0444); +MODULE_PARM_DESC(latency,"pci latency timer"); + +static unsigned int video_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int vbi_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int radio_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int dsp_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int mixer_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int tuner[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +static unsigned int card[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; + +module_param_array(video_nr, int, NULL, 0444); +module_param_array(vbi_nr, int, NULL, 0444); +module_param_array(radio_nr, int, NULL, 0444); +module_param_array(dsp_nr, int, NULL, 0444); +module_param_array(mixer_nr, int, NULL, 0444); +module_param_array(tuner, int, NULL, 0444); +module_param_array(card, int, NULL, 0444); + +MODULE_PARM_DESC(video_nr, "video device number"); +MODULE_PARM_DESC(vbi_nr, "vbi device number"); +MODULE_PARM_DESC(radio_nr, "radio device number"); +MODULE_PARM_DESC(dsp_nr, "oss dsp device number"); +MODULE_PARM_DESC(mixer_nr, "oss mixer device number"); +MODULE_PARM_DESC(tuner, "tuner type"); +MODULE_PARM_DESC(card, "card type"); + +static DECLARE_MUTEX(devlist_lock); +LIST_HEAD(saa7134_devlist); +static LIST_HEAD(mops_list); +static unsigned int saa7134_devcount; + +#define dprintk(fmt, arg...) if (core_debug) \ + printk(KERN_DEBUG "%s/core: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* debug help functions */ + +static const char *v4l1_ioctls[] = { + "0", "GCAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", + "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", + "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", + "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", + "SMICROCODE", "GVBIFMT", "SVBIFMT" }; +#define V4L1_IOCTLS ARRAY_SIZE(v4l1_ioctls) + +static const char *v4l2_ioctls[] = { + "QUERYCAP", "1", "ENUM_PIXFMT", "ENUM_FBUFFMT", "G_FMT", "S_FMT", + "G_COMP", "S_COMP", "REQBUFS", "QUERYBUF", "G_FBUF", "S_FBUF", + "G_WIN", "S_WIN", "PREVIEW", "QBUF", "16", "DQBUF", "STREAMON", + "STREAMOFF", "G_PERF", "G_PARM", "S_PARM", "G_STD", "S_STD", + "ENUMSTD", "ENUMINPUT", "G_CTRL", "S_CTRL", "G_TUNER", "S_TUNER", + "G_FREQ", "S_FREQ", "G_AUDIO", "S_AUDIO", "35", "QUERYCTRL", + "QUERYMENU", "G_INPUT", "S_INPUT", "ENUMCVT", "41", "42", "43", + "44", "45", "G_OUTPUT", "S_OUTPUT", "ENUMOUTPUT", "G_AUDOUT", + "S_AUDOUT", "ENUMFX", "G_EFFECT", "S_EFFECT", "G_MODULATOR", + "S_MODULATOR" +}; +#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +static const char *osspcm_ioctls[] = { + "RESET", "SYNC", "SPEED", "STEREO", "GETBLKSIZE", "SETFMT", + "CHANNELS", "?", "POST", "SUBDIVIDE", "SETFRAGMENT", "GETFMTS", + "GETOSPACE", "GETISPACE", "NONBLOCK", "GETCAPS", "GET/SETTRIGGER", + "GETIPTR", "GETOPTR", "MAPINBUF", "MAPOUTBUF", "SETSYNCRO", + "SETDUPLEX", "GETODELAY" +}; +#define OSSPCM_IOCTLS ARRAY_SIZE(v4l2_ioctls) + +void saa7134_print_ioctl(char *name, unsigned int cmd) +{ + char *dir; + + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: dir = "--"; break; + case _IOC_READ: dir = "r-"; break; + case _IOC_WRITE: dir = "-w"; break; + case _IOC_READ | _IOC_WRITE: dir = "rw"; break; + default: dir = "??"; break; + } + switch (_IOC_TYPE(cmd)) { + case 'v': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l1, %s, VIDIOC%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L1_IOCTLS) ? + v4l1_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'V': + printk(KERN_DEBUG "%s: ioctl 0x%08x (v4l2, %s, VIDIOC_%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < V4L2_IOCTLS) ? + v4l2_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'P': + printk(KERN_DEBUG "%s: ioctl 0x%08x (oss dsp, %s, SNDCTL_DSP_%s)\n", + name, cmd, dir, (_IOC_NR(cmd) < OSSPCM_IOCTLS) ? + osspcm_ioctls[_IOC_NR(cmd)] : "???"); + break; + case 'M': + printk(KERN_DEBUG "%s: ioctl 0x%08x (oss mixer, %s, #%d)\n", + name, cmd, dir, _IOC_NR(cmd)); + break; + default: + printk(KERN_DEBUG "%s: ioctl 0x%08x (???, %s, #%d)\n", + name, cmd, dir, _IOC_NR(cmd)); + } +} + +void saa7134_track_gpio(struct saa7134_dev *dev, char *msg) +{ + unsigned long mode,status; + + if (!gpio_tracking) + return; + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,0); + saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,SAA7134_GPIO_GPRESCAN); + mode = saa_readl(SAA7134_GPIO_GPMODE0 >> 2) & 0xfffffff; + status = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & 0xfffffff; + printk(KERN_DEBUG + "%s: gpio: mode=0x%07lx in=0x%07lx out=0x%07lx [%s]\n", + dev->name, mode, (~mode) & status, mode & status, msg); +} + +/* ------------------------------------------------------------------ */ + +#if 0 +static char *dec1_bits[8] = { + "DCSTD0", "DCSCT1", "WIPA", "GLIMB", + "GLIMT", "SLTCA", "HLCK" +}; +static char *dec2_bits[8] = { + "RDCAP", "COPRO", "COLSTR", "TYPE3", + NULL, "FIDT", "HLVLN", "INTL" +}; +static char *scale1_bits[8] = { + "VID_A", "VBI_A", NULL, NULL, "VID_B", "VBI_B" +}; +static char *scale2_bits[8] = { + "TRERR", "CFERR", "LDERR", "WASRST", + "FIDSCI", "FIDSCO", "D6^D5", "TASK" +}; + +static void dump_statusreg(struct saa7134_dev *dev, int reg, + char *regname, char **bits) +{ + int value,i; + + value = saa_readb(reg); + printk(KERN_DEBUG "%s: %s:", dev->name, regname); + for (i = 7; i >= 0; i--) { + if (NULL == bits[i]) + continue; + printk(" %s=%d", bits[i], (value & (1 << i)) ? 1 : 0); + } + printk("\n"); +} + +static void dump_statusregs(struct saa7134_dev *dev) +{ + dump_statusreg(dev,SAA7134_STATUS_VIDEO1,"dec1",dec1_bits); + dump_statusreg(dev,SAA7134_STATUS_VIDEO2,"dec2",dec2_bits); + dump_statusreg(dev,SAA7134_SCALER_STATUS0,"scale0",scale1_bits); + dump_statusreg(dev,SAA7134_SCALER_STATUS1,"scale1",scale2_bits); +} +#endif + +/* ----------------------------------------------------------- */ +/* delayed request_module */ + +#ifdef CONFIG_MODULES + +static int need_empress; +static int need_dvb; + +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) + request_module("saa7134-empress"); + if (need_dvb) + request_module("saa7134-dvb"); + 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) +{ + switch (THIS_MODULE->state) { + case MODULE_STATE_COMING: + if (!pending_registered) { + register_module_notifier(&pending_notifier); + pending_registered = 1; + } + *flag = 1; + break; + case MODULE_STATE_LIVE: + request_module(name); + break; + default: + /* nothing */; + break; + } +} + +#else + +#define request_module_depend(name,flag) + +#endif /* CONFIG_MODULES */ + +/* ------------------------------------------------------------------ */ + +/* nr of (saa7134-)pages for the given buffer size */ +static int saa7134_buffer_pages(int size) +{ + size = PAGE_ALIGN(size); + size += PAGE_SIZE; /* for non-page-aligned buffers */ + size /= 4096; + return size; +} + +/* calc max # of buffers from size (must not exceed the 4MB virtual + * address space per DMA channel) */ +int saa7134_buffer_count(unsigned int size, unsigned int count) +{ + unsigned int maxcount; + + maxcount = 1024 / saa7134_buffer_pages(size); + if (count > maxcount) + count = maxcount; + return count; +} + +int saa7134_buffer_startpage(struct saa7134_buf *buf) +{ + return saa7134_buffer_pages(buf->vb.bsize) * buf->vb.i; +} + +unsigned long saa7134_buffer_base(struct saa7134_buf *buf) +{ + unsigned long base; + + base = saa7134_buffer_startpage(buf) * 4096; + base += buf->vb.dma.sglist[0].offset; + return base; +} + +/* ------------------------------------------------------------------ */ + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt) +{ + u32 *cpu; + dma_addr_t dma_addr; + + cpu = pci_alloc_consistent(pci, SAA7134_PGTABLE_SIZE, &dma_addr); + if (NULL == cpu) + return -ENOMEM; + pt->size = SAA7134_PGTABLE_SIZE; + pt->cpu = cpu; + pt->dma = dma_addr; + return 0; +} + +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage) +{ + u32 *ptr; + unsigned int i,p; + + BUG_ON(NULL == pt || NULL == pt->cpu); + + ptr = pt->cpu + startpage; + for (i = 0; i < length; i++, list++) + for (p = 0; p * 4096 < list->length; p++, ptr++) + *ptr = sg_dma_address(list) - list->offset; + return 0; +} + +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt) +{ + if (NULL == pt->cpu) + return; + pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); + pt->cpu = NULL; +} + +/* ------------------------------------------------------------------ */ + +void saa7134_dma_free(struct saa7134_dev *dev,struct saa7134_buf *buf) +{ + if (in_interrupt()) + BUG(); + + videobuf_waiton(&buf->vb,0,0); + videobuf_dma_pci_unmap(dev->pci, &buf->vb.dma); + videobuf_dma_free(&buf->vb.dma); + buf->vb.state = STATE_NEEDS_INIT; +} + +/* ------------------------------------------------------------------ */ + +int saa7134_buffer_queue(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q, + struct saa7134_buf *buf) +{ + struct saa7134_buf *next = NULL; + + assert_spin_locked(&dev->slock); + dprintk("buffer_queue %p\n",buf); + if (NULL == q->curr) { + if (!q->need_two) { + q->curr = buf; + buf->activate(dev,buf,NULL); + } else if (list_empty(&q->queue)) { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = STATE_QUEUED; + } else { + next = list_entry(q->queue.next,struct saa7134_buf, + vb.queue); + q->curr = buf; + buf->activate(dev,buf,next); + } + } else { + list_add_tail(&buf->vb.queue,&q->queue); + buf->vb.state = STATE_QUEUED; + } + return 0; +} + +void saa7134_buffer_finish(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q, + unsigned int state) +{ + assert_spin_locked(&dev->slock); + dprintk("buffer_finish %p\n",q->curr); + + /* finish current buffer */ + q->curr->vb.state = state; + do_gettimeofday(&q->curr->vb.ts); + wake_up(&q->curr->vb.done); + q->curr = NULL; +} + +void saa7134_buffer_next(struct saa7134_dev *dev, + struct saa7134_dmaqueue *q) +{ + struct saa7134_buf *buf,*next = NULL; + + assert_spin_locked(&dev->slock); + BUG_ON(NULL != q->curr); + + if (!list_empty(&q->queue)) { + /* activate next one from queue */ + buf = list_entry(q->queue.next,struct saa7134_buf,vb.queue); + dprintk("buffer_next %p [prev=%p/next=%p]\n", + buf,q->queue.prev,q->queue.next); + list_del(&buf->vb.queue); + if (!list_empty(&q->queue)) + next = list_entry(q->queue.next,struct saa7134_buf, + vb.queue); + q->curr = buf; + buf->activate(dev,buf,next); + dprintk("buffer_next #2 prev=%p/next=%p\n", + q->queue.prev,q->queue.next); + } else { + /* nothing to do -- just stop DMA */ + dprintk("buffer_next %p\n",NULL); + saa7134_set_dmabits(dev); + del_timer(&q->timeout); + } +} + +void saa7134_buffer_timeout(unsigned long data) +{ + struct saa7134_dmaqueue *q = (struct saa7134_dmaqueue*)data; + struct saa7134_dev *dev = q->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->slock,flags); + + /* try to reset the hardware (SWRST) */ + saa_writeb(SAA7134_REGION_ENABLE, 0x00); + saa_writeb(SAA7134_REGION_ENABLE, 0x80); + saa_writeb(SAA7134_REGION_ENABLE, 0x00); + + /* flag current buffer as failed, + try to start over with the next one. */ + if (q->curr) { + dprintk("timeout on %p\n",q->curr); + saa7134_buffer_finish(dev,q,STATE_ERROR); + } + saa7134_buffer_next(dev,q); + spin_unlock_irqrestore(&dev->slock,flags); +} + +/* ------------------------------------------------------------------ */ + +int saa7134_set_dmabits(struct saa7134_dev *dev) +{ + u32 split, task=0, ctrl=0, irq=0; + enum v4l2_field cap = V4L2_FIELD_ANY; + enum v4l2_field ov = V4L2_FIELD_ANY; + + assert_spin_locked(&dev->slock); + + /* video capture -- dma 0 + video task A */ + if (dev->video_q.curr) { + task |= 0x01; + ctrl |= SAA7134_MAIN_CTRL_TE0; + irq |= SAA7134_IRQ1_INTE_RA0_1 | + SAA7134_IRQ1_INTE_RA0_0; + cap = dev->video_q.curr->vb.field; + } + + /* video capture -- dma 1+2 (planar modes) */ + if (dev->video_q.curr && + dev->video_q.curr->fmt->planar) { + ctrl |= SAA7134_MAIN_CTRL_TE4 | + SAA7134_MAIN_CTRL_TE5; + } + + /* screen overlay -- dma 0 + video task B */ + if (dev->ovenable) { + task |= 0x10; + ctrl |= SAA7134_MAIN_CTRL_TE1; + ov = dev->ovfield; + } + + /* vbi capture -- dma 0 + vbi task A+B */ + if (dev->vbi_q.curr) { + task |= 0x22; + ctrl |= SAA7134_MAIN_CTRL_TE2 | + SAA7134_MAIN_CTRL_TE3; + irq |= SAA7134_IRQ1_INTE_RA0_7 | + SAA7134_IRQ1_INTE_RA0_6 | + SAA7134_IRQ1_INTE_RA0_5 | + SAA7134_IRQ1_INTE_RA0_4; + } + + /* audio capture -- dma 3 */ + if (dev->oss.dma_running) { + ctrl |= SAA7134_MAIN_CTRL_TE6; + irq |= SAA7134_IRQ1_INTE_RA3_1 | + SAA7134_IRQ1_INTE_RA3_0; + } + + /* TS capture -- dma 5 */ + if (dev->ts_q.curr) { + ctrl |= SAA7134_MAIN_CTRL_TE5; + irq |= SAA7134_IRQ1_INTE_RA2_3 | + SAA7134_IRQ1_INTE_RA2_2 | + SAA7134_IRQ1_INTE_RA2_1 | + SAA7134_IRQ1_INTE_RA2_0; + } + + /* set task conditions + field handling */ + if (V4L2_FIELD_HAS_BOTH(cap) || V4L2_FIELD_HAS_BOTH(ov) || cap == ov) { + /* default config -- use full frames */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_A), 0x02); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_B), 0x02); + split = 0; + } else { + /* split fields between tasks */ + if (V4L2_FIELD_TOP == cap) { + /* odd A, even B, repeat */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0e); + } else { + /* odd B, even A, repeat */ + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0e); + saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d); + } + saa_writeb(SAA7134_FIELD_HANDLING(TASK_A), 0x01); + saa_writeb(SAA7134_FIELD_HANDLING(TASK_B), 0x01); + split = 1; + } + + /* irqs */ + saa_writeb(SAA7134_REGION_ENABLE, task); + saa_writel(SAA7134_IRQ1, irq); + saa_andorl(SAA7134_MAIN_CTRL, + SAA7134_MAIN_CTRL_TE0 | + SAA7134_MAIN_CTRL_TE1 | + SAA7134_MAIN_CTRL_TE2 | + SAA7134_MAIN_CTRL_TE3 | + SAA7134_MAIN_CTRL_TE4 | + SAA7134_MAIN_CTRL_TE5 | + SAA7134_MAIN_CTRL_TE6, + ctrl); + dprintk("dmabits: task=0x%02x ctrl=0x%02x irq=0x%x split=%s\n", + task, ctrl, irq, split ? "no" : "yes"); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* IRQ handler + helpers */ + +static char *irqbits[] = { + "DONE_RA0", "DONE_RA1", "DONE_RA2", "DONE_RA3", + "AR", "PE", "PWR_ON", "RDCAP", "INTL", "FIDT", "MMC", + "TRIG_ERR", "CONF_ERR", "LOAD_ERR", + "GPIO16?", "GPIO18", "GPIO22", "GPIO23" +}; +#define IRQBITS ARRAY_SIZE(irqbits) + +static void print_irqstatus(struct saa7134_dev *dev, int loop, + unsigned long report, unsigned long status) +{ + unsigned int i; + + printk(KERN_DEBUG "%s/irq[%d,%ld]: r=0x%lx s=0x%02lx", + dev->name,loop,jiffies,report,status); + for (i = 0; i < IRQBITS; i++) { + if (!(report & (1 << i))) + continue; + printk(" %s",irqbits[i]); + } + if (report & SAA7134_IRQ_REPORT_DONE_RA0) { + printk(" | RA0=%s,%s,%s,%ld", + (status & 0x40) ? "vbi" : "video", + (status & 0x20) ? "b" : "a", + (status & 0x10) ? "odd" : "even", + (status & 0x0f)); + } + printk("\n"); +} + +static irqreturn_t saa7134_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7134_dev *dev = (struct saa7134_dev*) dev_id; + unsigned long report,status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + report = saa_readl(SAA7134_IRQ_REPORT); + status = saa_readl(SAA7134_IRQ_STATUS); + if (0 == report) { + if (irq_debug > 1) + printk(KERN_DEBUG "%s/irq: no (more) work\n", + dev->name); + goto out; + } + handled = 1; + saa_writel(SAA7134_IRQ_REPORT,report); + if (irq_debug) + print_irqstatus(dev,loop,report,status); + +#if 0 + if (report & SAA7134_IRQ_REPORT_CONF_ERR) + dump_statusregs(dev); +#endif + + if (report & SAA7134_IRQ_REPORT_RDCAP /* _INTL */) + saa7134_irq_video_intl(dev); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA0) && + (status & 0x60) == 0) + saa7134_irq_video_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA0) && + (status & 0x40) == 0x40) + saa7134_irq_vbi_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA2) && + card_has_mpeg(dev)) + saa7134_irq_ts_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA3)) + saa7134_irq_oss_done(dev,status); + + if ((report & (SAA7134_IRQ_REPORT_GPIO16 | + SAA7134_IRQ_REPORT_GPIO18)) && + dev->remote) + saa7134_input_irq(dev); + } + + if (10 == loop) { + print_irqstatus(dev,loop,report,status); + if (report & SAA7134_IRQ_REPORT_PE) { + /* disable all parity error */ + printk(KERN_WARNING "%s/irq: looping -- " + "clearing PE (parity error!) enable bit\n",dev->name); + saa_clearl(SAA7134_IRQ2,SAA7134_IRQ2_INTE_PE); + } else if (report & (SAA7134_IRQ_REPORT_GPIO16 | + SAA7134_IRQ_REPORT_GPIO18)) { + /* disable gpio IRQs */ + printk(KERN_WARNING "%s/irq: looping -- " + "clearing GPIO enable bits\n",dev->name); + saa_clearl(SAA7134_IRQ2, (SAA7134_IRQ2_INTE_GPIO16 | + SAA7134_IRQ2_INTE_GPIO18)); + } else { + /* disable all irqs */ + printk(KERN_WARNING "%s/irq: looping -- " + "clearing all enable bits\n",dev->name); + saa_writel(SAA7134_IRQ1,0); + saa_writel(SAA7134_IRQ2,0); + } + } + + out: + return IRQ_RETVAL(handled); +} + +/* ------------------------------------------------------------------ */ + +/* early init (no i2c, no irq) */ +static int saa7134_hwinit1(struct saa7134_dev *dev) +{ + dprintk("hwinit1\n"); + + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, 0); + init_MUTEX(&dev->lock); + spin_lock_init(&dev->slock); + + saa7134_track_gpio(dev,"pre-init"); + saa7134_video_init1(dev); + saa7134_vbi_init1(dev); + if (card_has_mpeg(dev)) + saa7134_ts_init1(dev); + saa7134_input_init1(dev); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + saa7134_oss_init1(dev); + break; + } + + /* RAM FIFO config */ + saa_writel(SAA7134_FIFO_SIZE, 0x08070503); + saa_writel(SAA7134_THRESHOULD,0x02020202); + + /* enable audio + video processing */ + saa_writel(SAA7134_MAIN_CTRL, + SAA7134_MAIN_CTRL_VPLLE | + SAA7134_MAIN_CTRL_APLLE | + SAA7134_MAIN_CTRL_EXOSC | + SAA7134_MAIN_CTRL_EVFE1 | + SAA7134_MAIN_CTRL_EVFE2 | + SAA7134_MAIN_CTRL_ESFE | + SAA7134_MAIN_CTRL_EBADC | + SAA7134_MAIN_CTRL_EBDAC); + + /* enable peripheral devices */ + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + + /* set vertical line numbering start (vbi needs this) */ + saa_writeb(SAA7134_SOURCE_TIMING2, 0x20); + + return 0; +} + +/* late init (with i2c + irq) */ +static int saa7134_hwinit2(struct saa7134_dev *dev) +{ + dprintk("hwinit2\n"); + + saa7134_video_init2(dev); + saa7134_tvaudio_init2(dev); + + /* enable IRQ's */ + saa_writel(SAA7134_IRQ1, 0); + saa_writel(SAA7134_IRQ2, dev->irq2_mask); + + return 0; +} + +/* shutdown */ +static int saa7134_hwfini(struct saa7134_dev *dev) +{ + dprintk("hwfini\n"); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + saa7134_oss_fini(dev); + break; + } + if (card_has_mpeg(dev)) + saa7134_ts_fini(dev); + saa7134_input_fini(dev); + saa7134_vbi_fini(dev); + saa7134_video_fini(dev); + saa7134_tvaudio_fini(dev); + return 0; +} + +static void __devinit must_configure_manually(void) +{ + unsigned int i,p; + + printk(KERN_WARNING + "saa7134: <rant>\n" + "saa7134: Congratulations! Your TV card vendor saved a few\n" + "saa7134: cents for a eeprom, thus your pci board has no\n" + "saa7134: subsystem ID and I can't identify it automatically\n" + "saa7134: </rant>\n" + "saa7134: I feel better now. Ok, here are the good news:\n" + "saa7134: You can use the card=<nr> insmod option to specify\n" + "saa7134: which board do you have. The list:\n"); + for (i = 0; i < saa7134_bcount; i++) { + printk(KERN_WARNING "saa7134: card=%d -> %-40.40s", + i,saa7134_boards[i].name); + for (p = 0; saa7134_pci_tbl[p].driver_data; p++) { + if (saa7134_pci_tbl[p].driver_data != i) + continue; + printk(" %04x:%04x", + saa7134_pci_tbl[p].subvendor, + saa7134_pci_tbl[p].subdevice); + } + printk("\n"); + } +} + +static struct video_device *vdev_init(struct saa7134_dev *dev, + struct video_device *template, + char *type) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (NULL == vfd) + return NULL; + *vfd = *template; + vfd->minor = -1; + vfd->dev = &dev->pci->dev; + vfd->release = video_device_release; + snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", + dev->name, type, saa7134_boards[dev->board].name); + return vfd; +} + +static void saa7134_unregister_video(struct saa7134_dev *dev) +{ + if (dev->video_dev) { + if (-1 != dev->video_dev->minor) + video_unregister_device(dev->video_dev); + else + video_device_release(dev->video_dev); + dev->video_dev = NULL; + } + if (dev->vbi_dev) { + if (-1 != dev->vbi_dev->minor) + video_unregister_device(dev->vbi_dev); + else + video_device_release(dev->vbi_dev); + dev->vbi_dev = NULL; + } + if (dev->radio_dev) { + if (-1 != dev->radio_dev->minor) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } +} + +static void mpeg_ops_attach(struct saa7134_mpeg_ops *ops, + struct saa7134_dev *dev) +{ + int err; + + if (NULL != dev->mops) + return; + if (saa7134_boards[dev->board].mpeg != ops->type) + return; + err = ops->init(dev); + if (0 != err) + return; + dev->mops = ops; +} + +static void mpeg_ops_detach(struct saa7134_mpeg_ops *ops, + struct saa7134_dev *dev) +{ + if (NULL == dev->mops) + return; + if (dev->mops != ops) + return; + dev->mops->fini(dev); + dev->mops = NULL; +} + +static int __devinit saa7134_initdev(struct pci_dev *pci_dev, + const struct pci_device_id *pci_id) +{ + struct saa7134_dev *dev; + struct list_head *item; + struct saa7134_mpeg_ops *mops; + int err; + + dev = kmalloc(sizeof(*dev),GFP_KERNEL); + if (NULL == dev) + return -ENOMEM; + memset(dev,0,sizeof(*dev)); + + /* pci init */ + dev->pci = pci_dev; + if (pci_enable_device(pci_dev)) { + err = -EIO; + goto fail1; + } + + dev->nr = saa7134_devcount; + sprintf(dev->name,"saa%x[%d]",pci_dev->device,dev->nr); + + /* pci quirks */ + if (pci_pci_problems) { + if (pci_pci_problems & PCIPCI_TRITON) + printk(KERN_INFO "%s: quirk: PCIPCI_TRITON\n", dev->name); + if (pci_pci_problems & PCIPCI_NATOMA) + printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA\n", dev->name); + if (pci_pci_problems & PCIPCI_VIAETBF) + printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF\n", dev->name); + if (pci_pci_problems & PCIPCI_VSFX) + printk(KERN_INFO "%s: quirk: PCIPCI_VSFX\n",dev->name); +#ifdef PCIPCI_ALIMAGIK + if (pci_pci_problems & PCIPCI_ALIMAGIK) { + printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n", + dev->name); + latency = 0x0A; + } +#endif + } + if (UNSET != latency) { + printk(KERN_INFO "%s: setting pci latency timer to %d\n", + dev->name,latency); + pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency); + } + + /* print pci info */ + pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &dev->pci_rev); + pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &dev->pci_lat); + printk(KERN_INFO "%s: found at %s, rev: %d, irq: %d, " + "latency: %d, mmio: 0x%lx\n", dev->name, + pci_name(pci_dev), dev->pci_rev, pci_dev->irq, + dev->pci_lat,pci_resource_start(pci_dev,0)); + pci_set_master(pci_dev); + if (!pci_dma_supported(pci_dev,0xffffffff)) { + printk("%s: Oops: no 32bit PCI DMA ???\n",dev->name); + err = -EIO; + goto fail1; + } + + /* board config */ + dev->board = pci_id->driver_data; + if (card[dev->nr] >= 0 && + card[dev->nr] < saa7134_bcount) + dev->board = card[dev->nr]; + if (SAA7134_BOARD_NOAUTO == dev->board) { + must_configure_manually(); + dev->board = SAA7134_BOARD_UNKNOWN; + } + dev->tuner_type = saa7134_boards[dev->board].tuner_type; + dev->tda9887_conf = saa7134_boards[dev->board].tda9887_conf; + if (UNSET != tuner[dev->nr]) + dev->tuner_type = tuner[dev->nr]; + printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n", + dev->name,pci_dev->subsystem_vendor, + pci_dev->subsystem_device,saa7134_boards[dev->board].name, + dev->board, card[dev->nr] == dev->board ? + "insmod option" : "autodetected"); + + /* get mmio */ + if (!request_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0), + dev->name)) { + err = -EBUSY; + printk(KERN_ERR "%s: can't get MMIO memory @ 0x%lx\n", + dev->name,pci_resource_start(pci_dev,0)); + goto fail1; + } + dev->lmmio = ioremap(pci_resource_start(pci_dev,0), 0x1000); + dev->bmmio = (__u8 __iomem *)dev->lmmio; + if (NULL == dev->lmmio) { + err = -EIO; + printk(KERN_ERR "%s: can't ioremap() MMIO memory\n", + dev->name); + goto fail2; + } + + /* initialize hardware #1 */ + dev->irq2_mask = + SAA7134_IRQ2_INTE_DEC3 | + SAA7134_IRQ2_INTE_DEC2 | + SAA7134_IRQ2_INTE_DEC1 | + SAA7134_IRQ2_INTE_DEC0 | + SAA7134_IRQ2_INTE_PE | + SAA7134_IRQ2_INTE_AR; + saa7134_board_init1(dev); + saa7134_hwinit1(dev); + + /* get irq */ + err = request_irq(pci_dev->irq, saa7134_irq, + SA_SHIRQ | SA_INTERRUPT, dev->name, dev); + if (err < 0) { + printk(KERN_ERR "%s: can't get IRQ %d\n", + dev->name,pci_dev->irq); + goto fail3; + } + + /* wait a bit, register i2c bus */ + msleep(100); + saa7134_i2c_register(dev); + + /* initialize hardware #2 */ + saa7134_board_init2(dev); + saa7134_hwinit2(dev); + + /* load i2c helpers */ + if (TUNER_ABSENT != dev->tuner_type) + request_module("tuner"); + if (dev->tda9887_conf) + request_module("tda9887"); + 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); + + v4l2_prio_init(&dev->prio); + + /* register v4l devices */ + dev->video_dev = vdev_init(dev,&saa7134_video_template,"video"); + err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER, + video_nr[dev->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + goto fail4; + } + printk(KERN_INFO "%s: registered device video%d [v4l2]\n", + dev->name,dev->video_dev->minor & 0x1f); + + dev->vbi_dev = vdev_init(dev,&saa7134_vbi_template,"vbi"); + err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI, + vbi_nr[dev->nr]); + if (err < 0) + goto fail4; + printk(KERN_INFO "%s: registered device vbi%d\n", + dev->name,dev->vbi_dev->minor & 0x1f); + + if (card_has_radio(dev)) { + dev->radio_dev = vdev_init(dev,&saa7134_radio_template,"radio"); + err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO, + radio_nr[dev->nr]); + if (err < 0) + goto fail4; + printk(KERN_INFO "%s: registered device radio%d\n", + dev->name,dev->radio_dev->minor & 0x1f); + } + + /* register oss devices */ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + if (oss) { + err = dev->oss.minor_dsp = + register_sound_dsp(&saa7134_dsp_fops, + dsp_nr[dev->nr]); + if (err < 0) { + goto fail4; + } + printk(KERN_INFO "%s: registered device dsp%d\n", + dev->name,dev->oss.minor_dsp >> 4); + + err = dev->oss.minor_mixer = + register_sound_mixer(&saa7134_mixer_fops, + mixer_nr[dev->nr]); + if (err < 0) + goto fail5; + printk(KERN_INFO "%s: registered device mixer%d\n", + dev->name,dev->oss.minor_mixer >> 4); + } + break; + } + + /* everything worked */ + pci_set_drvdata(pci_dev,dev); + saa7134_devcount++; + + down(&devlist_lock); + list_for_each(item,&mops_list) { + mops = list_entry(item, struct saa7134_mpeg_ops, next); + mpeg_ops_attach(mops, dev); + } + list_add_tail(&dev->devlist,&saa7134_devlist); + up(&devlist_lock); + + /* check for signal */ + saa7134_irq_video_intl(dev); + return 0; + + fail5: + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + if (oss) + unregister_sound_dsp(dev->oss.minor_dsp); + break; + } + fail4: + saa7134_unregister_video(dev); + saa7134_i2c_unregister(dev); + free_irq(pci_dev->irq, dev); + fail3: + saa7134_hwfini(dev); + iounmap(dev->lmmio); + fail2: + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + fail1: + kfree(dev); + return err; +} + +static void __devexit saa7134_finidev(struct pci_dev *pci_dev) +{ + struct saa7134_dev *dev = pci_get_drvdata(pci_dev); + struct list_head *item; + struct saa7134_mpeg_ops *mops; + + /* debugging ... */ + if (irq_debug) { + u32 report = saa_readl(SAA7134_IRQ_REPORT); + u32 status = saa_readl(SAA7134_IRQ_STATUS); + print_irqstatus(dev,42,report,status); + } + + /* disable peripheral devices */ + saa_writeb(SAA7134_SPECIAL_MODE,0); + + /* shutdown hardware */ + saa_writel(SAA7134_IRQ1,0); + saa_writel(SAA7134_IRQ2,0); + saa_writel(SAA7134_MAIN_CTRL,0); + + /* shutdown subsystems */ + saa7134_hwfini(dev); + + /* unregister */ + down(&devlist_lock); + list_del(&dev->devlist); + list_for_each(item,&mops_list) { + mops = list_entry(item, struct saa7134_mpeg_ops, next); + mpeg_ops_detach(mops, dev); + } + up(&devlist_lock); + saa7134_devcount--; + + saa7134_i2c_unregister(dev); + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + if (oss) { + unregister_sound_mixer(dev->oss.minor_mixer); + unregister_sound_dsp(dev->oss.minor_dsp); + } + break; + } + saa7134_unregister_video(dev); + + /* release ressources */ + free_irq(pci_dev->irq, dev); + iounmap(dev->lmmio); + release_mem_region(pci_resource_start(pci_dev,0), + pci_resource_len(pci_dev,0)); + +#if 0 /* causes some trouble when reinserting the driver ... */ + pci_disable_device(pci_dev); +#endif + pci_set_drvdata(pci_dev, NULL); + + /* free memory */ + kfree(dev); +} + +/* ----------------------------------------------------------- */ + +int saa7134_ts_register(struct saa7134_mpeg_ops *ops) +{ + struct list_head *item; + struct saa7134_dev *dev; + + down(&devlist_lock); + list_for_each(item,&saa7134_devlist) { + dev = list_entry(item, struct saa7134_dev, devlist); + mpeg_ops_attach(ops, dev); + } + list_add_tail(&ops->next,&mops_list); + up(&devlist_lock); + return 0; +} + +void saa7134_ts_unregister(struct saa7134_mpeg_ops *ops) +{ + struct list_head *item; + struct saa7134_dev *dev; + + down(&devlist_lock); + list_del(&ops->next); + list_for_each(item,&saa7134_devlist) { + dev = list_entry(item, struct saa7134_dev, devlist); + mpeg_ops_detach(ops, dev); + } + up(&devlist_lock); +} + +EXPORT_SYMBOL(saa7134_ts_register); +EXPORT_SYMBOL(saa7134_ts_unregister); + +/* ----------------------------------------------------------- */ + +static struct pci_driver saa7134_pci_driver = { + .name = "saa7134", + .id_table = saa7134_pci_tbl, + .probe = saa7134_initdev, + .remove = __devexit_p(saa7134_finidev), +}; + +static int saa7134_init(void) +{ + INIT_LIST_HEAD(&saa7134_devlist); + printk(KERN_INFO "saa7130/34: v4l2 driver version %d.%d.%d loaded\n", + (SAA7134_VERSION_CODE >> 16) & 0xff, + (SAA7134_VERSION_CODE >> 8) & 0xff, + SAA7134_VERSION_CODE & 0xff); +#ifdef SNAPSHOT + printk(KERN_INFO "saa7130/34: snapshot date %04d-%02d-%02d\n", + SNAPSHOT/10000, (SNAPSHOT/100)%100, SNAPSHOT%100); +#endif + return pci_module_init(&saa7134_pci_driver); +} + +static void saa7134_fini(void) +{ +#ifdef CONFIG_MODULES + if (pending_registered) + unregister_module_notifier(&pending_notifier); +#endif + pci_unregister_driver(&saa7134_pci_driver); +} + +module_init(saa7134_init); +module_exit(saa7134_fini); + +/* ----------------------------------------------------------- */ + +EXPORT_SYMBOL(saa7134_print_ioctl); +EXPORT_SYMBOL(saa7134_i2c_call_clients); +EXPORT_SYMBOL(saa7134_devlist); +EXPORT_SYMBOL(saa7134_boards); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-dvb.c b/drivers/media/video/saa7134/saa7134-dvb.c new file mode 100644 index 000000000000..dd4a6c8ee65f --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-dvb.c @@ -0,0 +1,266 @@ +/* + * $Id: saa7134-dvb.c,v 1.12 2005/02/18 12:28:29 kraxel Exp $ + * + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/suspend.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +#include "dvb-pll.h" +#include "mt352.h" +#include "mt352_priv.h" /* FIXME */ +#include "tda1004x.h" + +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int antenna_pwr = 0; +module_param(antenna_pwr, int, 0444); +MODULE_PARM_DESC(antenna_pwr,"enable antenna power (Pinnacle 300i)"); + +/* ------------------------------------------------------------------ */ + +static int pinnacle_antenna_pwr(struct saa7134_dev *dev, int on) +{ + u32 ok; + + if (!on) { + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 26)); + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + return 0; + } + + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 26)); + saa_setl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + udelay(10); + + saa_setl(SAA7134_GPIO_GPMODE0 >> 2, (1 << 28)); + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 28)); + udelay(10); + 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"); + + if (!ok) + saa_clearl(SAA7134_GPIO_GPSTATUS0 >> 2, (1 << 26)); + return ok; +} + +static int mt352_pinnacle_init(struct dvb_frontend* fe) +{ + static u8 clock_config [] = { CLOCK_CTL, 0x3d, 0x28 }; + static u8 reset [] = { RESET, 0x80 }; + static u8 adc_ctl_1_cfg [] = { ADC_CTL_1, 0x40 }; + static u8 agc_cfg [] = { AGC_TARGET, 0x28, 0xa0 }; + static u8 capt_range_cfg[] = { CAPT_RANGE, 0x31 }; + static u8 fsm_ctl_cfg[] = { 0x7b, 0x04 }; + static u8 gpp_ctl_cfg [] = { GPP_CTL, 0x0f }; + static u8 scan_ctl_cfg [] = { SCAN_CTL, 0x0d }; + 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__); + + mt352_write(fe, clock_config, sizeof(clock_config)); + udelay(200); + mt352_write(fe, reset, sizeof(reset)); + mt352_write(fe, adc_ctl_1_cfg, sizeof(adc_ctl_1_cfg)); + mt352_write(fe, agc_cfg, sizeof(agc_cfg)); + mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg)); + mt352_write(fe, gpp_ctl_cfg, sizeof(gpp_ctl_cfg)); + + mt352_write(fe, fsm_ctl_cfg, sizeof(fsm_ctl_cfg)); + mt352_write(fe, scan_ctl_cfg, sizeof(scan_ctl_cfg)); + mt352_write(fe, irq_cfg, sizeof(irq_cfg)); + return 0; +} + +static int mt352_pinnacle_pll_set(struct dvb_frontend* fe, + struct dvb_frontend_parameters* params, + u8* pllbuf) +{ + static int on = TDA9887_PRESENT | TDA9887_PORT2_INACTIVE; + static int off = TDA9887_PRESENT | TDA9887_PORT2_ACTIVE; + struct saa7134_dev *dev = fe->dvb->priv; + struct v4l2_frequency f; + + /* set frequency (mt2050) */ + f.tuner = 0; + f.type = V4L2_TUNER_DIGITAL_TV; + f.frequency = params->frequency / 1000 * 16 / 1000; + saa7134_i2c_call_clients(dev,TDA9887_SET_CONFIG,&on); + saa7134_i2c_call_clients(dev,VIDIOC_S_FREQUENCY,&f); + saa7134_i2c_call_clients(dev,TDA9887_SET_CONFIG,&off); + + pinnacle_antenna_pwr(dev, antenna_pwr); + + /* mt352 setup */ + mt352_pinnacle_init(fe); + pllbuf[0] = 0xc2; + pllbuf[1] = 0x00; + pllbuf[2] = 0x00; + pllbuf[3] = 0x80; + pllbuf[4] = 0x00; + return 0; +} + +static struct mt352_config pinnacle_300i = { + .demod_address = 0x3c >> 1, + .adc_clock = 20333, + .if2 = 36150, + .no_tuner = 1, + .demod_init = mt352_pinnacle_init, + .pll_set = mt352_pinnacle_pll_set, +}; + +/* ------------------------------------------------------------------ */ + +static int medion_cardbus_init(struct dvb_frontend* fe) +{ + /* anything to do here ??? */ + return 0; +} + +static int medion_cardbus_pll_set(struct dvb_frontend* fe, + struct dvb_frontend_parameters* params) +{ + struct saa7134_dev *dev = fe->dvb->priv; + struct v4l2_frequency f; + + /* + * this instructs tuner.o to set the frequency, the call will + * end up in tuner_command(), VIDIOC_S_FREQUENCY switch. + * tda9887.o will see that as well. + */ + f.tuner = 0; + f.type = V4L2_TUNER_DIGITAL_TV; + f.frequency = params->frequency / 1000 * 16 / 1000; + saa7134_i2c_call_clients(dev,VIDIOC_S_FREQUENCY,&f); + return 0; +} + +static int fe_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); +} + +struct tda1004x_config medion_cardbus = { + .demod_address = 0x08, /* not sure this is correct */ + .invert = 0, + .invert_oclk = 0, + .pll_init = medion_cardbus_init, + .pll_set = medion_cardbus_pll_set, + .request_firmware = fe_request_firmware, +}; + +/* ------------------------------------------------------------------ */ + +static int dvb_init(struct saa7134_dev *dev) +{ + /* init struct videobuf_dvb */ + dev->ts.nr_bufs = 32; + dev->ts.nr_packets = 32*4; + dev->dvb.name = dev->name; + videobuf_queue_init(&dev->dvb.dvbq, &saa7134_ts_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_ALTERNATE, + sizeof(struct saa7134_buf), + dev); + + switch (dev->board) { + case SAA7134_BOARD_PINNACLE_300I_DVBT_PAL: + printk("%s: pinnacle 300i dvb setup\n",dev->name); + dev->dvb.frontend = mt352_attach(&pinnacle_300i, + &dev->i2c_adap); + break; + case SAA7134_BOARD_MD7134: + dev->dvb.frontend = tda10046_attach(&medion_cardbus, + &dev->i2c_adap); + if (NULL == dev->dvb.frontend) + printk("%s: Hmm, looks like this is the old MD7134 " + "version without DVB-T support\n",dev->name); + break; + default: + printk("%s: Huh? unknown DVB card?\n",dev->name); + break; + } + + if (NULL == dev->dvb.frontend) { + printk("%s: frontend initialization failed\n",dev->name); + return -1; + } + + /* register everything else */ + return videobuf_dvb_register(&dev->dvb, THIS_MODULE, dev); +} + +static int dvb_fini(struct saa7134_dev *dev) +{ + static int on = TDA9887_PRESENT | TDA9887_PORT2_INACTIVE; + + printk("%s: %s\n",dev->name,__FUNCTION__); + + switch (dev->board) { + case SAA7134_BOARD_PINNACLE_300I_DVBT_PAL: + /* otherwise we don't detect the tuner on next insmod */ + saa7134_i2c_call_clients(dev,TDA9887_SET_CONFIG,&on); + break; + }; + videobuf_dvb_unregister(&dev->dvb); + return 0; +} + +static struct saa7134_mpeg_ops dvb_ops = { + .type = SAA7134_MPEG_DVB, + .init = dvb_init, + .fini = dvb_fini, +}; + +static int __init dvb_register(void) +{ + return saa7134_ts_register(&dvb_ops); +} + +static void __exit dvb_unregister(void) +{ + saa7134_ts_unregister(&dvb_ops); +} + +module_init(dvb_register); +module_exit(dvb_unregister); + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-empress.c b/drivers/media/video/saa7134/saa7134-empress.c new file mode 100644 index 000000000000..2021e099e35a --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-empress.c @@ -0,0 +1,436 @@ +/* + * $Id: saa7134-empress.c,v 1.10 2005/02/03 10:24:33 kraxel Exp $ + * + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +#include <media/saa6752hs.h> + +/* ------------------------------------------------------------------ */ + +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int empress_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +module_param_array(empress_nr, int, NULL, 0444); +MODULE_PARM_DESC(empress_nr,"ts device number"); + +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages"); + +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG "%s/empress: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static void ts_reset_encoder(struct saa7134_dev* dev) +{ + if (!dev->empress_started) + return; + + saa_writeb(SAA7134_SPECIAL_MODE, 0x00); + msleep(10); + saa_writeb(SAA7134_SPECIAL_MODE, 0x01); + msleep(100); + dev->empress_started = 0; +} + +static int ts_init_encoder(struct saa7134_dev* dev) +{ + ts_reset_encoder(dev); + saa7134_i2c_call_clients(dev, VIDIOC_S_MPEGCOMP, NULL); + dev->empress_started = 1; + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int ts_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct list_head *list; + int err; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->empress_dev && h->empress_dev->minor == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + dprintk("open minor=%d\n",minor); + err = -EBUSY; + if (down_trylock(&dev->empress_tsq.lock)) + goto done; + if (dev->empress_users) + goto done_up; + + dev->empress_users++; + file->private_data = dev; + err = 0; + +done_up: + up(&dev->empress_tsq.lock); +done: + return err; +} + +static int ts_release(struct inode *inode, struct file *file) +{ + struct saa7134_dev *dev = file->private_data; + + if (dev->empress_tsq.streaming) + videobuf_streamoff(&dev->empress_tsq); + down(&dev->empress_tsq.lock); + if (dev->empress_tsq.reading) + videobuf_read_stop(&dev->empress_tsq); + videobuf_mmap_free(&dev->empress_tsq); + dev->empress_users--; + + /* stop the encoder */ + ts_reset_encoder(dev); + + up(&dev->empress_tsq.lock); + return 0; +} + +static ssize_t +ts_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct saa7134_dev *dev = file->private_data; + + if (!dev->empress_started) + ts_init_encoder(dev); + + return videobuf_read_stream(&dev->empress_tsq, + data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static unsigned int +ts_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_dev *dev = file->private_data; + + return videobuf_poll_stream(file, &dev->empress_tsq, wait); +} + + +static int +ts_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct saa7134_dev *dev = file->private_data; + + return videobuf_mmap_mapper(&dev->empress_tsq, vma); +} + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel pointer. + */ +static int ts_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7134_dev *dev = file->private_data; + + if (debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = SAA7134_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; + } + + /* --- input switching --------------------------------------- */ + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,"CCIR656"); + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = 0; + return 0; + } + case VIDIOC_S_INPUT: + { + int *i = arg; + + if (*i != 0) + return -EINVAL; + return 0; + } + /* --- capture ioctls ---------------------------------------- */ + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + int index; + + index = f->index; + if (index != 0) + return -EINVAL; + + memset(f,0,sizeof(*f)); + f->index = index; + strlcpy(f->description, "MPEG TS", sizeof(f->description)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->pixelformat = V4L2_PIX_FMT_MPEG; + return 0; + } + + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + + memset(f,0,sizeof(*f)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* FIXME: translate subsampling type EMPRESS into + * width/height: */ + f->fmt.pix.width = 720; /* D1 */ + f->fmt.pix.height = 576; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE * dev->ts.nr_packets; + return 0; + } + + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* + FIXME: translate and round width/height into EMPRESS + subsample type: + + type | PAL | NTSC + --------------------------- + SIF | 352x288 | 352x240 + 1/2 D1 | 352x576 | 352x480 + 2/3 D1 | 480x576 | 480x480 + D1 | 720x576 | 720x480 + */ + + f->fmt.pix.width = 720; /* D1 */ + f->fmt.pix.height = 576; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.sizeimage = TS_PACKET_SIZE* dev->ts.nr_packets; + return 0; + } + + case VIDIOC_REQBUFS: + return videobuf_reqbufs(&dev->empress_tsq,arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(&dev->empress_tsq,arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(&dev->empress_tsq,arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(&dev->empress_tsq,arg, + file->f_flags & O_NONBLOCK); + + case VIDIOC_STREAMON: + return videobuf_streamon(&dev->empress_tsq); + + case VIDIOC_STREAMOFF: + return videobuf_streamoff(&dev->empress_tsq); + + case VIDIOC_QUERYCTRL: + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + return saa7134_common_ioctl(dev, cmd, arg); + + case VIDIOC_S_MPEGCOMP: + saa7134_i2c_call_clients(dev, VIDIOC_S_MPEGCOMP, arg); + ts_init_encoder(dev); + return 0; + case VIDIOC_G_MPEGCOMP: + saa7134_i2c_call_clients(dev, VIDIOC_G_MPEGCOMP, arg); + return 0; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int ts_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, ts_do_ioctl); +} + +static struct file_operations ts_fops = +{ + .owner = THIS_MODULE, + .open = ts_open, + .release = ts_release, + .read = ts_read, + .poll = ts_poll, + .mmap = ts_mmap, + .ioctl = ts_ioctl, + .llseek = no_llseek, +}; + +/* ----------------------------------------------------------- */ + +static struct video_device saa7134_empress_template = +{ + .name = "saa7134-empress", + .type = 0 /* FIXME */, + .type2 = 0 /* FIXME */, + .hardware = 0, + .fops = &ts_fops, + .minor = -1, +}; + +static void empress_signal_update(void* data) +{ + struct saa7134_dev* dev = (struct saa7134_dev*) data; + + if (dev->nosignal) { + dprintk("no video signal\n"); + ts_reset_encoder(dev); + } else { + dprintk("video signal acquired\n"); + if (dev->empress_users) + ts_init_encoder(dev); + } +} + +static void empress_signal_change(struct saa7134_dev *dev) +{ + schedule_work(&dev->empress_workqueue); +} + + +static int empress_init(struct saa7134_dev *dev) +{ + int err; + + dprintk("%s: %s\n",dev->name,__FUNCTION__); + dev->empress_dev = video_device_alloc(); + if (NULL == dev->empress_dev) + return -ENOMEM; + *(dev->empress_dev) = saa7134_empress_template; + dev->empress_dev->dev = &dev->pci->dev; + dev->empress_dev->release = video_device_release; + snprintf(dev->empress_dev->name, sizeof(dev->empress_dev->name), + "%s empress (%s)", dev->name, + saa7134_boards[dev->board].name); + + INIT_WORK(&dev->empress_workqueue, empress_signal_update, (void*) dev); + + err = video_register_device(dev->empress_dev,VFL_TYPE_GRABBER, + empress_nr[dev->nr]); + if (err < 0) { + printk(KERN_INFO "%s: can't register video device\n", + dev->name); + video_device_release(dev->empress_dev); + dev->empress_dev = NULL; + return err; + } + printk(KERN_INFO "%s: registered device video%d [mpeg]\n", + dev->name,dev->empress_dev->minor & 0x1f); + + videobuf_queue_init(&dev->empress_tsq, &saa7134_ts_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_ALTERNATE, + sizeof(struct saa7134_buf), + dev); + + empress_signal_update(dev); + return 0; +} + +static int empress_fini(struct saa7134_dev *dev) +{ + dprintk("%s: %s\n",dev->name,__FUNCTION__); + + if (NULL == dev->empress_dev) + return 0; + flush_scheduled_work(); + video_unregister_device(dev->empress_dev); + dev->empress_dev = NULL; + return 0; +} + +static struct saa7134_mpeg_ops empress_ops = { + .type = SAA7134_MPEG_EMPRESS, + .init = empress_init, + .fini = empress_fini, + .signal_change = empress_signal_change, +}; + +static int __init empress_register(void) +{ + return saa7134_ts_register(&empress_ops); +} + +static void __exit empress_unregister(void) +{ + saa7134_ts_unregister(&empress_ops); +} + +module_init(empress_register); +module_exit(empress_unregister); + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-i2c.c b/drivers/media/video/saa7134/saa7134-i2c.c new file mode 100644 index 000000000000..702bb63d9813 --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-i2c.c @@ -0,0 +1,453 @@ +/* + * $Id: saa7134-i2c.c,v 1.10 2005/01/24 17:37:23 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * i2c interface support + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ----------------------------------------------------------- */ + +static unsigned int i2c_debug = 0; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug,"enable debug messages [i2c]"); + +static unsigned int i2c_scan = 0; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +#define d1printk if (1 == i2c_debug) printk +#define d2printk if (2 == i2c_debug) printk + +#define I2C_WAIT_DELAY 32 +#define I2C_WAIT_RETRY 16 + +/* ----------------------------------------------------------- */ + +static char *str_i2c_status[] = { + "IDLE", "DONE_STOP", "BUSY", "TO_SCL", "TO_ARB", "DONE_WRITE", + "DONE_READ", "DONE_WRITE_TO", "DONE_READ_TO", "NO_DEVICE", + "NO_ACKN", "BUS_ERR", "ARB_LOST", "SEQ_ERR", "ST_ERR", "SW_ERR" +}; + +enum i2c_status { + IDLE = 0, // no I2C command pending + DONE_STOP = 1, // I2C command done and STOP executed + BUSY = 2, // executing I2C command + TO_SCL = 3, // executing I2C command, time out on clock stretching + TO_ARB = 4, // time out on arbitration trial, still trying + DONE_WRITE = 5, // I2C command done and awaiting next write command + DONE_READ = 6, // I2C command done and awaiting next read command + DONE_WRITE_TO = 7, // see 5, and time out on status echo + DONE_READ_TO = 8, // see 6, and time out on status echo + NO_DEVICE = 9, // no acknowledge on device slave address + NO_ACKN = 10, // no acknowledge after data byte transfer + BUS_ERR = 11, // bus error + ARB_LOST = 12, // arbitration lost during transfer + SEQ_ERR = 13, // erroneous programming sequence + ST_ERR = 14, // wrong status echoing + SW_ERR = 15 // software error +}; + +static char *str_i2c_attr[] = { + "NOP", "STOP", "CONTINUE", "START" +}; + +enum i2c_attr { + NOP = 0, // no operation on I2C bus + STOP = 1, // stop condition, no associated byte transfer + CONTINUE = 2, // continue with byte transfer + START = 3 // start condition with byte transfer +}; + +static inline enum i2c_status i2c_get_status(struct saa7134_dev *dev) +{ + enum i2c_status status; + + status = saa_readb(SAA7134_I2C_ATTR_STATUS) & 0x0f; + d2printk(KERN_DEBUG "%s: i2c stat <= %s\n",dev->name, + str_i2c_status[status]); + return status; +} + +static inline void i2c_set_status(struct saa7134_dev *dev, + enum i2c_status status) +{ + d2printk(KERN_DEBUG "%s: i2c stat => %s\n",dev->name, + str_i2c_status[status]); + saa_andorb(SAA7134_I2C_ATTR_STATUS,0x0f,status); +} + +static inline void i2c_set_attr(struct saa7134_dev *dev, enum i2c_attr attr) +{ + d2printk(KERN_DEBUG "%s: i2c attr => %s\n",dev->name, + str_i2c_attr[attr]); + saa_andorb(SAA7134_I2C_ATTR_STATUS,0xc0,attr << 6); +} + +static inline int i2c_is_error(enum i2c_status status) +{ + switch (status) { + case NO_DEVICE: + case NO_ACKN: + case BUS_ERR: + case ARB_LOST: + case SEQ_ERR: + case ST_ERR: + return TRUE; + default: + return FALSE; + } +} + +static inline int i2c_is_idle(enum i2c_status status) +{ + switch (status) { + case IDLE: + case DONE_STOP: + return TRUE; + default: + return FALSE; + } +} + +static inline int i2c_is_busy(enum i2c_status status) +{ + switch (status) { + case BUSY: + return TRUE; + default: + return FALSE; + } +} + +static int i2c_is_busy_wait(struct saa7134_dev *dev) +{ + enum i2c_status status; + int count; + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + status = i2c_get_status(dev); + if (!i2c_is_busy(status)) + break; + saa_wait(I2C_WAIT_DELAY); + } + if (I2C_WAIT_RETRY == count) + return FALSE; + return TRUE; +} + +static int i2c_reset(struct saa7134_dev *dev) +{ + enum i2c_status status; + int count; + + d2printk(KERN_DEBUG "%s: i2c reset\n",dev->name); + status = i2c_get_status(dev); + if (!i2c_is_error(status)) + return TRUE; + i2c_set_status(dev,status); + + for (count = 0; count < I2C_WAIT_RETRY; count++) { + status = i2c_get_status(dev); + if (!i2c_is_error(status)) + break; + udelay(I2C_WAIT_DELAY); + } + if (I2C_WAIT_RETRY == count) + return FALSE; + + if (!i2c_is_idle(status)) + return FALSE; + + i2c_set_attr(dev,NOP); + return TRUE; +} + +static inline int i2c_send_byte(struct saa7134_dev *dev, + enum i2c_attr attr, + unsigned char data) +{ + enum i2c_status status; + __u32 dword; + +#if 0 + i2c_set_attr(dev,attr); + saa_writeb(SAA7134_I2C_DATA, data); +#else + /* have to write both attr + data in one 32bit word */ + dword = saa_readl(SAA7134_I2C_ATTR_STATUS >> 2); + dword &= 0x0f; + dword |= (attr << 6); + dword |= ((__u32)data << 8); + dword |= 0x00 << 16; /* 100 kHz */ +// dword |= 0x40 << 16; /* 400 kHz */ + dword |= 0xf0 << 24; + saa_writel(SAA7134_I2C_ATTR_STATUS >> 2, dword); +#endif + d2printk(KERN_DEBUG "%s: i2c data => 0x%x\n",dev->name,data); + + if (!i2c_is_busy_wait(dev)) + return -EIO; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + return -EIO; + return 0; +} + +static inline int i2c_recv_byte(struct saa7134_dev *dev) +{ + enum i2c_status status; + unsigned char data; + + i2c_set_attr(dev,CONTINUE); + if (!i2c_is_busy_wait(dev)) + return -EIO; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + return -EIO; + data = saa_readb(SAA7134_I2C_DATA); + d2printk(KERN_DEBUG "%s: i2c data <= 0x%x\n",dev->name,data); + return data; +} + +static int saa7134_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg *msgs, int num) +{ + struct saa7134_dev *dev = i2c_adap->algo_data; + enum i2c_status status; + unsigned char data; + int addr,rc,i,byte; + + status = i2c_get_status(dev); + if (!i2c_is_idle(status)) + if (!i2c_reset(dev)) + return -EIO; + + d2printk("start xfer\n"); + d1printk(KERN_DEBUG "%s: i2c xfer:",dev->name); + for (i = 0; i < num; i++) { + if (!(msgs[i].flags & I2C_M_NOSTART) || 0 == i) { + /* send address */ + d2printk("send address\n"); + addr = msgs[i].addr << 1; + if (msgs[i].flags & I2C_M_RD) + addr |= 1; + if (i > 0 && msgs[i].flags & I2C_M_RD) { + /* workaround for a saa7134 i2c bug + * needed to talk to the mt352 demux + * thanks to pinnacle for the hint */ + int quirk = 0xfd; + d1printk(" [%02x quirk]",quirk); + i2c_send_byte(dev,START,quirk); + i2c_recv_byte(dev); + } + d1printk(" < %02x", addr); + rc = i2c_send_byte(dev,START,addr); + if (rc < 0) + goto err; + } + if (msgs[i].flags & I2C_M_RD) { + /* read bytes */ + d2printk("read bytes\n"); + for (byte = 0; byte < msgs[i].len; byte++) { + d1printk(" ="); + rc = i2c_recv_byte(dev); + if (rc < 0) + goto err; + d1printk("%02x", rc); + msgs[i].buf[byte] = rc; + } + } else { + /* write bytes */ + d2printk("write bytes\n"); + for (byte = 0; byte < msgs[i].len; byte++) { + data = msgs[i].buf[byte]; + d1printk(" %02x", data); + rc = i2c_send_byte(dev,CONTINUE,data); + if (rc < 0) + goto err; + } + } + } + d2printk("xfer done\n"); + d1printk(" >"); + i2c_set_attr(dev,STOP); + rc = -EIO; + if (!i2c_is_busy_wait(dev)) + goto err; + status = i2c_get_status(dev); + if (i2c_is_error(status)) + goto err; + + d1printk("\n"); + return num; + err: + if (1 == i2c_debug) { + status = i2c_get_status(dev); + printk(" ERROR: %s\n",str_i2c_status[status]); + } + return rc; +} + +/* ----------------------------------------------------------- */ + +static int algo_control(struct i2c_adapter *adapter, + unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +static int attach_inform(struct i2c_client *client) +{ + struct saa7134_dev *dev = client->adapter->algo_data; + int tuner = dev->tuner_type; + int conf = dev->tda9887_conf; + + saa7134_i2c_call_clients(dev,TUNER_SET_TYPE,&tuner); + saa7134_i2c_call_clients(dev,TDA9887_SET_CONFIG,&conf); + return 0; +} + +static struct i2c_algorithm saa7134_algo = { + .name = "saa7134", + .id = I2C_ALGO_SAA7134, + .master_xfer = saa7134_i2c_xfer, + .algo_control = algo_control, + .functionality = functionality, +}; + +static struct i2c_adapter saa7134_adap_template = { + .owner = THIS_MODULE, +#ifdef I2C_CLASS_TV_ANALOG + .class = I2C_CLASS_TV_ANALOG, +#endif + I2C_DEVNAME("saa7134"), + .id = I2C_ALGO_SAA7134, + .algo = &saa7134_algo, + .client_register = attach_inform, +}; + +static struct i2c_client saa7134_client_template = { + I2C_DEVNAME("saa7134 internal"), +}; + +/* ----------------------------------------------------------- */ + +static int +saa7134_i2c_eeprom(struct saa7134_dev *dev, unsigned char *eedata, int len) +{ + unsigned char buf; + int i,err; + + dev->i2c_client.addr = 0xa0 >> 1; + buf = 0; + if (1 != (err = i2c_master_send(&dev->i2c_client,&buf,1))) { + printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n", + dev->name,err); + return -1; + } + if (len != (err = i2c_master_recv(&dev->i2c_client,eedata,len))) { + printk(KERN_WARNING "%s: i2c eeprom read error (err=%d)\n", + dev->name,err); + return -1; + } + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "%s: i2c eeprom %02x:",dev->name,i); + printk(" %02x",eedata[i]); + if (15 == (i % 16)) + printk("\n"); + } + return 0; +} + +static char *i2c_devs[128] = { + [ 0x20 ] = "mpeg encoder (saa6752hs)", + [ 0xa0 >> 1 ] = "eeprom", + [ 0xc0 >> 1 ] = "tuner (analog)", + [ 0x86 >> 1 ] = "tda9887", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i,rc; + + for (i = 0; i < 128; i++) { + c->addr = i; + rc = i2c_master_recv(c,&buf,0); + if (rc < 0) + continue; + printk("%s: i2c scan: found device @ 0x%x [%s]\n", + name, i << 1, i2c_devs[i] ? i2c_devs[i] : "???"); + } +} + +void saa7134_i2c_call_clients(struct saa7134_dev *dev, + unsigned int cmd, void *arg) +{ + BUG_ON(NULL == dev->i2c_adap.algo_data); + i2c_clients_command(&dev->i2c_adap, cmd, arg); +} + +int saa7134_i2c_register(struct saa7134_dev *dev) +{ + dev->i2c_adap = saa7134_adap_template; + dev->i2c_adap.dev.parent = &dev->pci->dev; + strcpy(dev->i2c_adap.name,dev->name); + dev->i2c_adap.algo_data = dev; + i2c_add_adapter(&dev->i2c_adap); + + dev->i2c_client = saa7134_client_template; + dev->i2c_client.adapter = &dev->i2c_adap; + + saa7134_i2c_eeprom(dev,dev->eedata,sizeof(dev->eedata)); + if (i2c_scan) + do_i2c_scan(dev->name,&dev->i2c_client); + return 0; +} + +int saa7134_i2c_unregister(struct saa7134_dev *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-input.c b/drivers/media/video/saa7134/saa7134-input.c new file mode 100644 index 000000000000..727d437e07df --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-input.c @@ -0,0 +1,491 @@ +/* + * $Id: saa7134-input.c,v 1.16 2004/12/10 12:33:39 kraxel Exp $ + * + * handle saa7134 IR remotes via linux kernel input layer. + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/input.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +static unsigned int disable_ir = 0; +module_param(disable_ir, int, 0444); +MODULE_PARM_DESC(disable_ir,"disable infrared remote support"); + +static unsigned int ir_debug = 0; +module_param(ir_debug, int, 0644); +MODULE_PARM_DESC(ir_debug,"enable debug messages [IR]"); + +#define dprintk(fmt, arg...) if (ir_debug) \ + printk(KERN_DEBUG "%s/ir: " fmt, dev->name , ## arg) + +/* ---------------------------------------------------------------------- */ + +static IR_KEYTAB_TYPE flyvideo_codes[IR_KEYTAB_SIZE] = { + [ 15 ] = KEY_KP0, + [ 3 ] = KEY_KP1, + [ 4 ] = KEY_KP2, + [ 5 ] = KEY_KP3, + [ 7 ] = KEY_KP4, + [ 8 ] = KEY_KP5, + [ 9 ] = KEY_KP6, + [ 11 ] = KEY_KP7, + [ 12 ] = KEY_KP8, + [ 13 ] = KEY_KP9, + + [ 14 ] = KEY_TUNER, // Air/Cable + [ 17 ] = KEY_VIDEO, // Video + [ 21 ] = KEY_AUDIO, // Audio + [ 0 ] = KEY_POWER, // Pover + [ 2 ] = KEY_ZOOM, // Fullscreen + [ 27 ] = KEY_MUTE, // Mute + [ 20 ] = KEY_VOLUMEUP, + [ 23 ] = KEY_VOLUMEDOWN, + [ 18 ] = KEY_CHANNELUP, // Channel + + [ 19 ] = KEY_CHANNELDOWN, // Channel - + [ 6 ] = KEY_AGAIN, // Recal + [ 16 ] = KEY_KPENTER, // Enter + +#if 1 /* FIXME */ + [ 26 ] = KEY_F22, // Stereo + [ 24 ] = KEY_EDIT, // AV Source +#endif +}; + +static IR_KEYTAB_TYPE cinergy_codes[IR_KEYTAB_SIZE] = { + [ 0 ] = KEY_KP0, + [ 1 ] = KEY_KP1, + [ 2 ] = KEY_KP2, + [ 3 ] = KEY_KP3, + [ 4 ] = KEY_KP4, + [ 5 ] = KEY_KP5, + [ 6 ] = KEY_KP6, + [ 7 ] = KEY_KP7, + [ 8 ] = KEY_KP8, + [ 9 ] = KEY_KP9, + + [ 0x0a ] = KEY_POWER, + [ 0x0b ] = KEY_PROG1, // app + [ 0x0c ] = KEY_ZOOM, // zoom/fullscreen + [ 0x0d ] = KEY_CHANNELUP, // channel + [ 0x0e ] = KEY_CHANNELDOWN, // channel- + [ 0x0f ] = KEY_VOLUMEUP, + [ 0x10 ] = KEY_VOLUMEDOWN, + [ 0x11 ] = KEY_TUNER, // AV + [ 0x12 ] = KEY_NUMLOCK, // -/-- + [ 0x13 ] = KEY_AUDIO, // audio + [ 0x14 ] = KEY_MUTE, + [ 0x15 ] = KEY_UP, + [ 0x16 ] = KEY_DOWN, + [ 0x17 ] = KEY_LEFT, + [ 0x18 ] = KEY_RIGHT, + [ 0x19 ] = BTN_LEFT, + [ 0x1a ] = BTN_RIGHT, + [ 0x1b ] = KEY_WWW, // text + [ 0x1c ] = KEY_REWIND, + [ 0x1d ] = KEY_FORWARD, + [ 0x1e ] = KEY_RECORD, + [ 0x1f ] = KEY_PLAY, + [ 0x20 ] = KEY_PREVIOUSSONG, + [ 0x21 ] = KEY_NEXTSONG, + [ 0x22 ] = KEY_PAUSE, + [ 0x23 ] = KEY_STOP, +}; + +/* Alfons Geser <a.geser@cox.net> + * updates from Job D. R. Borges <jobdrb@ig.com.br> */ +static IR_KEYTAB_TYPE eztv_codes[IR_KEYTAB_SIZE] = { + [ 18 ] = KEY_POWER, + [ 1 ] = KEY_TV, // DVR + [ 21 ] = KEY_DVD, // DVD + [ 23 ] = KEY_AUDIO, // music + // DVR mode / DVD mode / music mode + + [ 27 ] = KEY_MUTE, // mute + [ 2 ] = KEY_LANGUAGE, // MTS/SAP / audio / autoseek + [ 30 ] = KEY_SUBTITLE, // closed captioning / subtitle / seek + [ 22 ] = KEY_ZOOM, // full screen + [ 28 ] = KEY_VIDEO, // video source / eject / delall + [ 29 ] = KEY_RESTART, // playback / angle / del + [ 47 ] = KEY_SEARCH, // scan / menu / playlist + [ 48 ] = KEY_CHANNEL, // CH surfing / bookmark / memo + + [ 49 ] = KEY_HELP, // help + [ 50 ] = KEY_MODE, // num/memo + [ 51 ] = KEY_ESC, // cancel + + [ 12 ] = KEY_UP, // up + [ 16 ] = KEY_DOWN, // down + [ 8 ] = KEY_LEFT, // left + [ 4 ] = KEY_RIGHT, // right + [ 3 ] = KEY_SELECT, // select + + [ 31 ] = KEY_REWIND, // rewind + [ 32 ] = KEY_PLAYPAUSE, // play/pause + [ 41 ] = KEY_FORWARD, // forward + [ 20 ] = KEY_AGAIN, // repeat + [ 43 ] = KEY_RECORD, // recording + [ 44 ] = KEY_STOP, // stop + [ 45 ] = KEY_PLAY, // play + [ 46 ] = KEY_SHUFFLE, // snapshot / shuffle + + [ 0 ] = KEY_KP0, + [ 5 ] = KEY_KP1, + [ 6 ] = KEY_KP2, + [ 7 ] = KEY_KP3, + [ 9 ] = KEY_KP4, + [ 10 ] = KEY_KP5, + [ 11 ] = KEY_KP6, + [ 13 ] = KEY_KP7, + [ 14 ] = KEY_KP8, + [ 15 ] = KEY_KP9, + + [ 42 ] = KEY_VOLUMEUP, + [ 17 ] = KEY_VOLUMEDOWN, + [ 24 ] = KEY_CHANNELUP, // CH.tracking up + [ 25 ] = KEY_CHANNELDOWN, // CH.tracking down + + [ 19 ] = KEY_KPENTER, // enter + [ 33 ] = KEY_KPDOT, // . (decimal dot) +}; + +static IR_KEYTAB_TYPE avacssmart_codes[IR_KEYTAB_SIZE] = { + [ 30 ] = KEY_POWER, // power + [ 28 ] = KEY_SEARCH, // scan + [ 7 ] = KEY_SELECT, // source + + [ 22 ] = KEY_VOLUMEUP, + [ 20 ] = KEY_VOLUMEDOWN, + [ 31 ] = KEY_CHANNELUP, + [ 23 ] = KEY_CHANNELDOWN, + [ 24 ] = KEY_MUTE, + + [ 2 ] = KEY_KP0, + [ 1 ] = KEY_KP1, + [ 11 ] = KEY_KP2, + [ 27 ] = KEY_KP3, + [ 5 ] = KEY_KP4, + [ 9 ] = KEY_KP5, + [ 21 ] = KEY_KP6, + [ 6 ] = KEY_KP7, + [ 10 ] = KEY_KP8, + [ 18 ] = KEY_KP9, + [ 16 ] = KEY_KPDOT, + + [ 3 ] = KEY_TUNER, // tv/fm + [ 4 ] = KEY_REWIND, // fm tuning left or function left + [ 12 ] = KEY_FORWARD, // fm tuning right or function right + + [ 0 ] = KEY_RECORD, + [ 8 ] = KEY_STOP, + [ 17 ] = KEY_PLAY, + + [ 25 ] = KEY_ZOOM, + [ 14 ] = KEY_MENU, // function + [ 19 ] = KEY_AGAIN, // recall + [ 29 ] = KEY_RESTART, // reset + +// FIXME + [ 13 ] = KEY_F21, // mts + [ 15 ] = KEY_F22, // min + [ 26 ] = KEY_F23, // freeze +}; + +/* Alex Hermann <gaaf@gmx.net> */ +static IR_KEYTAB_TYPE md2819_codes[IR_KEYTAB_SIZE] = { + [ 40 ] = KEY_KP1, + [ 24 ] = KEY_KP2, + [ 56 ] = KEY_KP3, + [ 36 ] = KEY_KP4, + [ 20 ] = KEY_KP5, + [ 52 ] = KEY_KP6, + [ 44 ] = KEY_KP7, + [ 28 ] = KEY_KP8, + [ 60 ] = KEY_KP9, + [ 34 ] = KEY_KP0, + + [ 32 ] = KEY_TV, // TV/FM + [ 16 ] = KEY_CD, // CD + [ 48 ] = KEY_TEXT, // TELETEXT + [ 0 ] = KEY_POWER, // POWER + + [ 8 ] = KEY_VIDEO, // VIDEO + [ 4 ] = KEY_AUDIO, // AUDIO + [ 12 ] = KEY_ZOOM, // FULL SCREEN + + [ 18 ] = KEY_SUBTITLE, // DISPLAY - ??? + [ 50 ] = KEY_REWIND, // LOOP - ??? + [ 2 ] = KEY_PRINT, // PREVIEW - ??? + + [ 42 ] = KEY_SEARCH, // AUTOSCAN + [ 26 ] = KEY_SLEEP, // FREEZE - ??? + [ 58 ] = KEY_SHUFFLE, // SNAPSHOT - ??? + [ 10 ] = KEY_MUTE, // MUTE + + [ 38 ] = KEY_RECORD, // RECORD + [ 22 ] = KEY_PAUSE, // PAUSE + [ 54 ] = KEY_STOP, // STOP + [ 6 ] = KEY_PLAY, // PLAY + + [ 46 ] = KEY_RED, // <RED> + [ 33 ] = KEY_GREEN, // <GREEN> + [ 14 ] = KEY_YELLOW, // <YELLOW> + [ 1 ] = KEY_BLUE, // <BLUE> + + [ 30 ] = KEY_VOLUMEDOWN, // VOLUME- + [ 62 ] = KEY_VOLUMEUP, // VOLUME+ + [ 17 ] = KEY_CHANNELDOWN, // CHANNEL/PAGE- + [ 49 ] = KEY_CHANNELUP // CHANNEL/PAGE+ +}; + +static IR_KEYTAB_TYPE videomate_tv_pvr_codes[IR_KEYTAB_SIZE] = { + [ 20 ] = KEY_MUTE, + [ 36 ] = KEY_ZOOM, + + [ 1 ] = KEY_DVD, + [ 35 ] = KEY_RADIO, + [ 0 ] = KEY_TV, + + [ 10 ] = KEY_REWIND, + [ 8 ] = KEY_PLAYPAUSE, + [ 15 ] = KEY_FORWARD, + + [ 2 ] = KEY_PREVIOUS, + [ 7 ] = KEY_STOP, + [ 6 ] = KEY_NEXT, + + [ 12 ] = KEY_UP, + [ 14 ] = KEY_DOWN, + [ 11 ] = KEY_LEFT, + [ 13 ] = KEY_RIGHT, + [ 17 ] = KEY_OK, + + [ 3 ] = KEY_MENU, + [ 9 ] = KEY_SETUP, + [ 5 ] = KEY_VIDEO, + [ 34 ] = KEY_CHANNEL, + + [ 18 ] = KEY_VOLUMEUP, + [ 21 ] = KEY_VOLUMEDOWN, + [ 16 ] = KEY_CHANNELUP, + [ 19 ] = KEY_CHANNELDOWN, + + [ 4 ] = KEY_RECORD, + + [ 22 ] = KEY_KP1, + [ 23 ] = KEY_KP2, + [ 24 ] = KEY_KP3, + [ 25 ] = KEY_KP4, + [ 26 ] = KEY_KP5, + [ 27 ] = KEY_KP6, + [ 28 ] = KEY_KP7, + [ 29 ] = KEY_KP8, + [ 30 ] = KEY_KP9, + [ 31 ] = KEY_KP0, + + [ 32 ] = KEY_LANGUAGE, + [ 33 ] = KEY_SLEEP, +}; +/* ---------------------------------------------------------------------- */ + +static int build_key(struct saa7134_dev *dev) +{ + struct saa7134_ir *ir = dev->remote; + u32 gpio, data; + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + if (ir->polling) { + if (ir->last_gpio == gpio) + return 0; + ir->last_gpio = gpio; + } + + data = ir_extract_bits(gpio, ir->mask_keycode); + dprintk("build_key gpio=0x%x mask=0x%x data=%d\n", + gpio, ir->mask_keycode, data); + + if ((ir->mask_keydown && (0 != (gpio & ir->mask_keydown))) || + (ir->mask_keyup && (0 == (gpio & ir->mask_keyup)))) { + ir_input_keydown(&ir->dev,&ir->ir,data,data); + } else { + ir_input_nokey(&ir->dev,&ir->ir); + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +void saa7134_input_irq(struct saa7134_dev *dev) +{ + struct saa7134_ir *ir = dev->remote; + + if (!ir->polling) + build_key(dev); +} + +static void saa7134_input_timer(unsigned long data) +{ + struct saa7134_dev *dev = (struct saa7134_dev*)data; + struct saa7134_ir *ir = dev->remote; + unsigned long timeout; + + build_key(dev); + timeout = jiffies + (ir->polling * HZ / 1000); + mod_timer(&ir->timer, timeout); +} + +int saa7134_input_init1(struct saa7134_dev *dev) +{ + struct saa7134_ir *ir; + IR_KEYTAB_TYPE *ir_codes = NULL; + u32 mask_keycode = 0; + u32 mask_keydown = 0; + u32 mask_keyup = 0; + int polling = 0; + int ir_type = IR_TYPE_OTHER; + + if (!dev->has_remote) + return -ENODEV; + if (disable_ir) + return -ENODEV; + + /* detect & configure */ + switch (dev->board) { + case SAA7134_BOARD_FLYVIDEO2000: + case SAA7134_BOARD_FLYVIDEO3000: + ir_codes = flyvideo_codes; + mask_keycode = 0xEC00000; + mask_keydown = 0x0040000; + break; + case SAA7134_BOARD_CINERGY400: + case SAA7134_BOARD_CINERGY600: + case SAA7134_BOARD_CINERGY600_MK3: + ir_codes = cinergy_codes; + mask_keycode = 0x00003f; + mask_keyup = 0x040000; + break; + case SAA7134_BOARD_ECS_TVP3XP: + case SAA7134_BOARD_ECS_TVP3XP_4CB5: + ir_codes = eztv_codes; + mask_keycode = 0x00017c; + mask_keyup = 0x000002; + polling = 50; // ms + break; + case SAA7134_BOARD_AVACSSMARTTV: + ir_codes = avacssmart_codes; + mask_keycode = 0x00001F; + mask_keyup = 0x000020; + polling = 50; // ms + break; + case SAA7134_BOARD_MD2819: + case SAA7134_BOARD_AVERMEDIA_305: + case SAA7134_BOARD_AVERMEDIA_307: + ir_codes = md2819_codes; + mask_keycode = 0x0007C8; + mask_keydown = 0x000010; + polling = 50; // ms + /* Set GPIO pin2 to high to enable the IR controller */ + saa_setb(SAA7134_GPIO_GPMODE0, 0x4); + saa_setb(SAA7134_GPIO_GPSTATUS0, 0x4); + break; + case SAA7134_BOARD_VIDEOMATE_TV_PVR: + ir_codes = videomate_tv_pvr_codes; + mask_keycode = 0x00003F; + mask_keyup = 0x400000; + polling = 50; // ms + break; + } + if (NULL == ir_codes) { + printk("%s: Oops: IR config error [card=%d]\n", + dev->name, dev->board); + return -ENODEV; + } + + ir = kmalloc(sizeof(*ir),GFP_KERNEL); + if (NULL == ir) + return -ENOMEM; + memset(ir,0,sizeof(*ir)); + + /* init hardware-specific stuff */ + ir->mask_keycode = mask_keycode; + ir->mask_keydown = mask_keydown; + ir->mask_keyup = mask_keyup; + ir->polling = polling; + + /* init input device */ + snprintf(ir->name, sizeof(ir->name), "saa7134 IR (%s)", + saa7134_boards[dev->board].name); + snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", + pci_name(dev->pci)); + + ir_input_init(&ir->dev, &ir->ir, ir_type, ir_codes); + ir->dev.name = ir->name; + ir->dev.phys = ir->phys; + ir->dev.id.bustype = BUS_PCI; + ir->dev.id.version = 1; + if (dev->pci->subsystem_vendor) { + ir->dev.id.vendor = dev->pci->subsystem_vendor; + ir->dev.id.product = dev->pci->subsystem_device; + } else { + ir->dev.id.vendor = dev->pci->vendor; + ir->dev.id.product = dev->pci->device; + } + + /* all done */ + dev->remote = ir; + if (ir->polling) { + init_timer(&ir->timer); + ir->timer.function = saa7134_input_timer; + ir->timer.data = (unsigned long)dev; + ir->timer.expires = jiffies + HZ; + add_timer(&ir->timer); + } + + input_register_device(&dev->remote->dev); + printk("%s: registered input device for IR\n",dev->name); + return 0; +} + +void saa7134_input_fini(struct saa7134_dev *dev) +{ + if (NULL == dev->remote) + return; + + input_unregister_device(&dev->remote->dev); + if (dev->remote->polling) + del_timer_sync(&dev->remote->timer); + kfree(dev->remote); + dev->remote = NULL; +} + +/* ---------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-oss.c b/drivers/media/video/saa7134/saa7134-oss.c new file mode 100644 index 000000000000..6b6a643bf1cd --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-oss.c @@ -0,0 +1,857 @@ +/* + * $Id: saa7134-oss.c,v 1.13 2004/12/10 12:33:39 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * oss dsp interface + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/soundcard.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int oss_debug = 0; +module_param(oss_debug, int, 0644); +MODULE_PARM_DESC(oss_debug,"enable debug messages [oss]"); + +static unsigned int oss_rate = 0; +module_param(oss_rate, int, 0444); +MODULE_PARM_DESC(oss_rate,"sample rate (valid are: 32000,48000)"); + +#define dprintk(fmt, arg...) if (oss_debug) \ + printk(KERN_DEBUG "%s/oss: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int dsp_buffer_conf(struct saa7134_dev *dev, int blksize, int blocks) +{ + blksize &= ~0xff; + if (blksize < 0x100) + blksize = 0x100; + if (blksize > 0x10000) + blksize = 0x10000; + + if (blocks < 2) + blocks = 2; + while ((blksize * blocks) & ~PAGE_MASK) + blocks++; + if ((blksize * blocks) > 1024*1024) + blocks = 1024*1024 / blksize; + + dev->oss.blocks = blocks; + dev->oss.blksize = blksize; + dev->oss.bufsize = blksize * blocks; + + dprintk("buffer config: %d blocks / %d bytes, %d kB total\n", + blocks,blksize,blksize * blocks / 1024); + return 0; +} + +static int dsp_buffer_init(struct saa7134_dev *dev) +{ + int err; + + if (!dev->oss.bufsize) + BUG(); + videobuf_dma_init(&dev->oss.dma); + err = videobuf_dma_init_kernel(&dev->oss.dma, PCI_DMA_FROMDEVICE, + dev->oss.bufsize >> PAGE_SHIFT); + if (0 != err) + return err; + return 0; +} + +static int dsp_buffer_free(struct saa7134_dev *dev) +{ + if (!dev->oss.blksize) + BUG(); + videobuf_dma_free(&dev->oss.dma); + dev->oss.blocks = 0; + dev->oss.blksize = 0; + dev->oss.bufsize = 0; + return 0; +} + +static void dsp_dma_start(struct saa7134_dev *dev) +{ + dev->oss.dma_blk = 0; + dev->oss.dma_running = 1; + saa7134_set_dmabits(dev); +} + +static void dsp_dma_stop(struct saa7134_dev *dev) +{ + dev->oss.dma_blk = -1; + dev->oss.dma_running = 0; + saa7134_set_dmabits(dev); +} + +static int dsp_rec_start(struct saa7134_dev *dev) +{ + int err, bswap, sign; + u32 fmt, control; + unsigned long flags; + + /* prepare buffer */ + if (0 != (err = videobuf_dma_pci_map(dev->pci,&dev->oss.dma))) + return err; + if (0 != (err = saa7134_pgtable_alloc(dev->pci,&dev->oss.pt))) + goto fail1; + if (0 != (err = saa7134_pgtable_build(dev->pci,&dev->oss.pt, + dev->oss.dma.sglist, + dev->oss.dma.sglen, + 0))) + goto fail2; + + /* sample format */ + switch (dev->oss.afmt) { + case AFMT_U8: + case AFMT_S8: fmt = 0x00; break; + case AFMT_U16_LE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_S16_BE: fmt = 0x01; break; + default: + err = -EINVAL; + goto fail2; + } + + switch (dev->oss.afmt) { + case AFMT_S8: + case AFMT_S16_LE: + case AFMT_S16_BE: sign = 1; break; + default: sign = 0; break; + } + + switch (dev->oss.afmt) { + case AFMT_U16_BE: + case AFMT_S16_BE: bswap = 1; break; + default: bswap = 0; break; + } + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + if (1 == dev->oss.channels) + fmt |= (1 << 3); + if (2 == dev->oss.channels) + fmt |= (3 << 3); + if (sign) + fmt |= 0x04; + fmt |= (TV == dev->oss.input) ? 0xc0 : 0x80; + + saa_writeb(SAA7134_NUM_SAMPLES0, (dev->oss.blksize & 0x0000ff)); + saa_writeb(SAA7134_NUM_SAMPLES1, (dev->oss.blksize & 0x00ff00) >> 8); + saa_writeb(SAA7134_NUM_SAMPLES2, (dev->oss.blksize & 0xff0000) >> 16); + saa_writeb(SAA7134_AUDIO_FORMAT_CTRL, fmt); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + if (1 == dev->oss.channels) + fmt |= (1 << 4); + if (2 == dev->oss.channels) + fmt |= (2 << 4); + if (!sign) + fmt |= 0x04; + saa_writel(0x588 >> 2, dev->oss.blksize -4); + saa_writel(0x58c >> 2, 0x543210 | (fmt << 24)); + break; + } + dprintk("rec_start: afmt=%d ch=%d => fmt=0x%x swap=%c\n", + dev->oss.afmt, dev->oss.channels, fmt, + bswap ? 'b' : '-'); + + /* dma: setup channel 6 (= AUDIO) */ + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (dev->oss.pt.dma >> 12); + if (bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + saa_writel(SAA7134_RS_BA1(6),0); + saa_writel(SAA7134_RS_BA2(6),dev->oss.blksize); + saa_writel(SAA7134_RS_PITCH(6),0); + saa_writel(SAA7134_RS_CONTROL(6),control); + + /* start dma */ + dev->oss.recording_on = 1; + spin_lock_irqsave(&dev->slock,flags); + dsp_dma_start(dev); + spin_unlock_irqrestore(&dev->slock,flags); + return 0; + + fail2: + saa7134_pgtable_free(dev->pci,&dev->oss.pt); + fail1: + videobuf_dma_pci_unmap(dev->pci,&dev->oss.dma); + return err; +} + +static int dsp_rec_stop(struct saa7134_dev *dev) +{ + unsigned long flags; + + dprintk("rec_stop dma_blk=%d\n",dev->oss.dma_blk); + + /* stop dma */ + dev->oss.recording_on = 0; + spin_lock_irqsave(&dev->slock,flags); + dsp_dma_stop(dev); + spin_unlock_irqrestore(&dev->slock,flags); + + /* unlock buffer */ + saa7134_pgtable_free(dev->pci,&dev->oss.pt); + videobuf_dma_pci_unmap(dev->pci,&dev->oss.dma); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int dsp_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct list_head *list; + int err; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->oss.minor_dsp == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + down(&dev->oss.lock); + err = -EBUSY; + if (dev->oss.users_dsp) + goto fail1; + dev->oss.users_dsp++; + file->private_data = dev; + + dev->oss.afmt = AFMT_U8; + dev->oss.channels = 1; + dev->oss.read_count = 0; + dev->oss.read_offset = 0; + dsp_buffer_conf(dev,PAGE_SIZE,64); + err = dsp_buffer_init(dev); + if (0 != err) + goto fail2; + + up(&dev->oss.lock); + return 0; + + fail2: + dev->oss.users_dsp--; + fail1: + up(&dev->oss.lock); + return err; +} + +static int dsp_release(struct inode *inode, struct file *file) +{ + struct saa7134_dev *dev = file->private_data; + + down(&dev->oss.lock); + if (dev->oss.recording_on) + dsp_rec_stop(dev); + dsp_buffer_free(dev); + dev->oss.users_dsp--; + file->private_data = NULL; + up(&dev->oss.lock); + return 0; +} + +static ssize_t dsp_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct saa7134_dev *dev = file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned int bytes; + unsigned long flags; + int err,ret = 0; + + add_wait_queue(&dev->oss.wq, &wait); + down(&dev->oss.lock); + while (count > 0) { + /* wait for data if needed */ + if (0 == dev->oss.read_count) { + if (!dev->oss.recording_on) { + err = dsp_rec_start(dev); + if (err < 0) { + if (0 == ret) + ret = err; + break; + } + } + if (dev->oss.recording_on && + !dev->oss.dma_running) { + /* recover from overruns */ + spin_lock_irqsave(&dev->slock,flags); + dsp_dma_start(dev); + spin_unlock_irqrestore(&dev->slock,flags); + } + if (file->f_flags & O_NONBLOCK) { + if (0 == ret) + ret = -EAGAIN; + break; + } + up(&dev->oss.lock); + set_current_state(TASK_INTERRUPTIBLE); + if (0 == dev->oss.read_count) + schedule(); + set_current_state(TASK_RUNNING); + down(&dev->oss.lock); + if (signal_pending(current)) { + if (0 == ret) + ret = -EINTR; + break; + } + } + + /* copy data to userspace */ + bytes = count; + if (bytes > dev->oss.read_count) + bytes = dev->oss.read_count; + if (bytes > dev->oss.bufsize - dev->oss.read_offset) + bytes = dev->oss.bufsize - dev->oss.read_offset; + if (copy_to_user(buffer + ret, + dev->oss.dma.vmalloc + dev->oss.read_offset, + bytes)) { + if (0 == ret) + ret = -EFAULT; + break; + } + + ret += bytes; + count -= bytes; + dev->oss.read_count -= bytes; + dev->oss.read_offset += bytes; + if (dev->oss.read_offset == dev->oss.bufsize) + dev->oss.read_offset = 0; + } + up(&dev->oss.lock); + remove_wait_queue(&dev->oss.wq, &wait); + return ret; +} + +static ssize_t dsp_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static int dsp_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct saa7134_dev *dev = file->private_data; + void __user *argp = (void __user *) arg; + int __user *p = argp; + int val = 0; + + if (oss_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + case SNDCTL_DSP_GETCAPS: + return 0; + + case SNDCTL_DSP_SPEED: + if (get_user(val, p)) + return -EFAULT; + /* fall through */ + case SOUND_PCM_READ_RATE: + return put_user(dev->oss.rate, p); + + case SNDCTL_DSP_STEREO: + if (get_user(val, p)) + return -EFAULT; + down(&dev->oss.lock); + dev->oss.channels = val ? 2 : 1; + if (dev->oss.recording_on) { + dsp_rec_stop(dev); + dsp_rec_start(dev); + } + up(&dev->oss.lock); + return put_user(dev->oss.channels-1, p); + + case SNDCTL_DSP_CHANNELS: + if (get_user(val, p)) + return -EFAULT; + if (val != 1 && val != 2) + return -EINVAL; + down(&dev->oss.lock); + dev->oss.channels = val; + if (dev->oss.recording_on) { + dsp_rec_stop(dev); + dsp_rec_start(dev); + } + up(&dev->oss.lock); + /* fall through */ + case SOUND_PCM_READ_CHANNELS: + return put_user(dev->oss.channels, p); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_U8 | AFMT_S8 | + AFMT_U16_LE | AFMT_U16_BE | + AFMT_S16_LE | AFMT_S16_BE, p); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt */ + if (get_user(val, p)) + return -EFAULT; + switch (val) { + case AFMT_QUERY: + /* nothing to do */ + break; + case AFMT_U8: + case AFMT_S8: + case AFMT_U16_LE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_S16_BE: + down(&dev->oss.lock); + dev->oss.afmt = val; + if (dev->oss.recording_on) { + dsp_rec_stop(dev); + dsp_rec_start(dev); + } + up(&dev->oss.lock); + return put_user(dev->oss.afmt, p); + default: + return -EINVAL; + } + + case SOUND_PCM_READ_BITS: + switch (dev->oss.afmt) { + case AFMT_U8: + case AFMT_S8: + return put_user(8, p); + case AFMT_U16_LE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_S16_BE: + return put_user(16, p); + default: + return -EINVAL; + } + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_RESET: + down(&dev->oss.lock); + if (dev->oss.recording_on) + dsp_rec_stop(dev); + up(&dev->oss.lock); + return 0; + case SNDCTL_DSP_GETBLKSIZE: + return put_user(dev->oss.blksize, p); + + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(val, p)) + return -EFAULT; + if (dev->oss.recording_on) + return -EBUSY; + dsp_buffer_free(dev); + /* used to be arg >> 16 instead of val >> 16; fixed */ + dsp_buffer_conf(dev,1 << (val & 0xffff), (val >> 16) & 0xffff); + dsp_buffer_init(dev); + return 0; + + case SNDCTL_DSP_SYNC: + /* NOP */ + return 0; + + case SNDCTL_DSP_GETISPACE: + { + audio_buf_info info; + info.fragsize = dev->oss.blksize; + info.fragstotal = dev->oss.blocks; + info.bytes = dev->oss.read_count; + info.fragments = info.bytes / info.fragsize; + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + default: + return -EINVAL; + } +} + +static unsigned int dsp_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_dev *dev = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &dev->oss.wq, wait); + + if (0 == dev->oss.read_count) { + down(&dev->oss.lock); + if (!dev->oss.recording_on) + dsp_rec_start(dev); + up(&dev->oss.lock); + } else + mask |= (POLLIN | POLLRDNORM); + return mask; +} + +struct file_operations saa7134_dsp_fops = { + .owner = THIS_MODULE, + .open = dsp_open, + .release = dsp_release, + .read = dsp_read, + .write = dsp_write, + .ioctl = dsp_ioctl, + .poll = dsp_poll, + .llseek = no_llseek, +}; + +/* ------------------------------------------------------------------ */ + +static int +mixer_recsrc_7134(struct saa7134_dev *dev) +{ + int analog_io,rate; + + switch (dev->oss.input) { + case TV: + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, 0xc0); + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, 0x00); + break; + case LINE1: + case LINE2: + case LINE2_LEFT: + analog_io = (LINE1 == dev->oss.input) ? 0x00 : 0x08; + rate = (32000 == dev->oss.rate) ? 0x01 : 0x03; + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x08, analog_io); + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, 0x80); + saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, rate); + break; + } + return 0; +} + +static int +mixer_recsrc_7133(struct saa7134_dev *dev) +{ + u32 value = 0xbbbbbb; + + switch (dev->oss.input) { + case TV: + value = 0xbbbb10; /* MAIN */ + break; + case LINE1: + value = 0xbbbb32; /* AUX1 */ + break; + case LINE2: + case LINE2_LEFT: + value = 0xbbbb54; /* AUX2 */ + break; + } + saa_dsp_writel(dev, 0x46c >> 2, value); + return 0; +} + +static int +mixer_recsrc(struct saa7134_dev *dev, enum saa7134_audio_in src) +{ + static const char *iname[] = { "Oops", "TV", "LINE1", "LINE2" }; + + dev->oss.count++; + dev->oss.input = src; + dprintk("mixer input = %s\n",iname[dev->oss.input]); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mixer_recsrc_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mixer_recsrc_7133(dev); + break; + } + return 0; +} + +static int +mixer_level(struct saa7134_dev *dev, enum saa7134_audio_in src, int level) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + switch (src) { + case TV: + /* nothing */ + break; + case LINE1: + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x10, + (100 == level) ? 0x00 : 0x10); + break; + case LINE2: + case LINE2_LEFT: + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x20, + (100 == level) ? 0x00 : 0x20); + break; + } + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + /* nothing */ + break; + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int mixer_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct list_head *list; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->oss.minor_mixer == minor) + dev = h; + } + if (NULL == dev) + return -ENODEV; + + file->private_data = dev; + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static int mixer_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct saa7134_dev *dev = file->private_data; + enum saa7134_audio_in input; + int val,ret; + void __user *argp = (void __user *) arg; + int __user *p = argp; + + if (oss_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, p); + case SOUND_MIXER_INFO: + { + mixer_info info; + memset(&info,0,sizeof(info)); + strlcpy(info.id, "TV audio", sizeof(info.id)); + strlcpy(info.name, dev->name, sizeof(info.name)); + info.modify_counter = dev->oss.count; + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + case SOUND_OLD_MIXER_INFO: + { + _old_mixer_info info; + memset(&info,0,sizeof(info)); + strlcpy(info.id, "TV audio", sizeof(info.id)); + strlcpy(info.name, dev->name, sizeof(info.name)); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + case MIXER_READ(SOUND_MIXER_CAPS): + return put_user(SOUND_CAP_EXCL_INPUT, p); + case MIXER_READ(SOUND_MIXER_STEREODEVS): + return put_user(0, p); + case MIXER_READ(SOUND_MIXER_RECMASK): + case MIXER_READ(SOUND_MIXER_DEVMASK): + val = SOUND_MASK_LINE1 | SOUND_MASK_LINE2; + if (32000 == dev->oss.rate) + val |= SOUND_MASK_VIDEO; + return put_user(val, p); + + case MIXER_WRITE(SOUND_MIXER_RECSRC): + if (get_user(val, p)) + return -EFAULT; + input = dev->oss.input; + if (32000 == dev->oss.rate && + val & SOUND_MASK_VIDEO && dev->oss.input != TV) + input = TV; + if (val & SOUND_MASK_LINE1 && dev->oss.input != LINE1) + input = LINE1; + if (val & SOUND_MASK_LINE2 && dev->oss.input != LINE2) + input = LINE2; + if (input != dev->oss.input) + mixer_recsrc(dev,input); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_RECSRC): + switch (dev->oss.input) { + case TV: ret = SOUND_MASK_VIDEO; break; + case LINE1: ret = SOUND_MASK_LINE1; break; + case LINE2: ret = SOUND_MASK_LINE2; break; + default: ret = 0; + } + return put_user(ret, p); + + case MIXER_WRITE(SOUND_MIXER_VIDEO): + case MIXER_READ(SOUND_MIXER_VIDEO): + if (32000 != dev->oss.rate) + return -EINVAL; + return put_user(100 | 100 << 8, p); + + case MIXER_WRITE(SOUND_MIXER_LINE1): + if (get_user(val, p)) + return -EFAULT; + val &= 0xff; + val = (val <= 50) ? 50 : 100; + dev->oss.line1 = val; + mixer_level(dev,LINE1,dev->oss.line1); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_LINE1): + return put_user(dev->oss.line1 | dev->oss.line1 << 8, p); + + case MIXER_WRITE(SOUND_MIXER_LINE2): + if (get_user(val, p)) + return -EFAULT; + val &= 0xff; + val = (val <= 50) ? 50 : 100; + dev->oss.line2 = val; + mixer_level(dev,LINE2,dev->oss.line2); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_LINE2): + return put_user(dev->oss.line2 | dev->oss.line2 << 8, p); + + default: + return -EINVAL; + } +} + +struct file_operations saa7134_mixer_fops = { + .owner = THIS_MODULE, + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, + .llseek = no_llseek, +}; + +/* ------------------------------------------------------------------ */ + +int saa7134_oss_init1(struct saa7134_dev *dev) +{ + /* general */ + init_MUTEX(&dev->oss.lock); + init_waitqueue_head(&dev->oss.wq); + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + saa_writel(0x588 >> 2, 0x00000fff); + saa_writel(0x58c >> 2, 0x00543210); + saa_dsp_writel(dev, 0x46c >> 2, 0xbbbbbb); + break; + } + + /* dsp */ + dev->oss.rate = 32000; + if (oss_rate) + dev->oss.rate = oss_rate; + dev->oss.rate = (dev->oss.rate > 40000) ? 48000 : 32000; + + /* mixer */ + dev->oss.line1 = 50; + dev->oss.line2 = 50; + mixer_level(dev,LINE1,dev->oss.line1); + mixer_level(dev,LINE2,dev->oss.line2); + mixer_recsrc(dev, (dev->oss.rate == 32000) ? TV : LINE2); + + return 0; +} + +int saa7134_oss_fini(struct saa7134_dev *dev) +{ + /* nothing */ + return 0; +} + +void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status) +{ + int next_blk, reg = 0; + + spin_lock(&dev->slock); + if (UNSET == dev->oss.dma_blk) { + dprintk("irq: recording stopped\n"); + goto done; + } + if (0 != (status & 0x0f000000)) + dprintk("irq: lost %ld\n", (status >> 24) & 0x0f); + if (0 == (status & 0x10000000)) { + /* odd */ + if (0 == (dev->oss.dma_blk & 0x01)) + reg = SAA7134_RS_BA1(6); + } else { + /* even */ + if (0 == (dev->oss.dma_blk & 0x00)) + reg = SAA7134_RS_BA2(6); + } + if (0 == reg) { + dprintk("irq: field oops [%s]\n", + (status & 0x10000000) ? "even" : "odd"); + goto done; + } + if (dev->oss.read_count >= dev->oss.blksize * (dev->oss.blocks-2)) { + dprintk("irq: overrun [full=%d/%d]\n",dev->oss.read_count, + dev->oss.bufsize); + dsp_dma_stop(dev); + goto done; + } + + /* next block addr */ + next_blk = (dev->oss.dma_blk + 2) % dev->oss.blocks; + saa_writel(reg,next_blk * dev->oss.blksize); + if (oss_debug > 2) + dprintk("irq: ok, %s, next_blk=%d, addr=%x\n", + (status & 0x10000000) ? "even" : "odd ", next_blk, + next_blk * dev->oss.blksize); + + /* update status & wake waiting readers */ + dev->oss.dma_blk = (dev->oss.dma_blk + 1) % dev->oss.blocks; + dev->oss.read_count += dev->oss.blksize; + wake_up(&dev->oss.wq); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-reg.h b/drivers/media/video/saa7134/saa7134-reg.h new file mode 100644 index 000000000000..87734f22af7d --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-reg.h @@ -0,0 +1,366 @@ +/* + * $Id: saa7134-reg.h,v 1.2 2004/09/15 16:15:24 kraxel Exp $ + * + * philips saa7134 registers + */ + +/* ------------------------------------------------------------------ */ +/* + * PCI ID's + */ +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7130 +# define PCI_DEVICE_ID_PHILIPS_SAA7130 0x7130 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7133 +# define PCI_DEVICE_ID_PHILIPS_SAA7133 0x7133 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7134 +# define PCI_DEVICE_ID_PHILIPS_SAA7134 0x7134 +#endif +#ifndef PCI_DEVICE_ID_PHILIPS_SAA7135 +# define PCI_DEVICE_ID_PHILIPS_SAA7135 0x7135 +#endif + +/* ------------------------------------------------------------------ */ +/* + * registers -- 32 bit + */ + +/* DMA channels, n = 0 ... 6 */ +#define SAA7134_RS_BA1(n) ((0x200 >> 2) + 4*n) +#define SAA7134_RS_BA2(n) ((0x204 >> 2) + 4*n) +#define SAA7134_RS_PITCH(n) ((0x208 >> 2) + 4*n) +#define SAA7134_RS_CONTROL(n) ((0x20c >> 2) + 4*n) +#define SAA7134_RS_CONTROL_WSWAP (0x01 << 25) +#define SAA7134_RS_CONTROL_BSWAP (0x01 << 24) +#define SAA7134_RS_CONTROL_BURST_2 (0x01 << 21) +#define SAA7134_RS_CONTROL_BURST_4 (0x02 << 21) +#define SAA7134_RS_CONTROL_BURST_8 (0x03 << 21) +#define SAA7134_RS_CONTROL_BURST_16 (0x04 << 21) +#define SAA7134_RS_CONTROL_BURST_32 (0x05 << 21) +#define SAA7134_RS_CONTROL_BURST_64 (0x06 << 21) +#define SAA7134_RS_CONTROL_BURST_MAX (0x07 << 21) +#define SAA7134_RS_CONTROL_ME (0x01 << 20) +#define SAA7134_FIFO_SIZE (0x2a0 >> 2) +#define SAA7134_THRESHOULD (0x2a4 >> 2) + +/* main control */ +#define SAA7134_MAIN_CTRL (0x2a8 >> 2) +#define SAA7134_MAIN_CTRL_VPLLE (1 << 15) +#define SAA7134_MAIN_CTRL_APLLE (1 << 14) +#define SAA7134_MAIN_CTRL_EXOSC (1 << 13) +#define SAA7134_MAIN_CTRL_EVFE1 (1 << 12) +#define SAA7134_MAIN_CTRL_EVFE2 (1 << 11) +#define SAA7134_MAIN_CTRL_ESFE (1 << 10) +#define SAA7134_MAIN_CTRL_EBADC (1 << 9) +#define SAA7134_MAIN_CTRL_EBDAC (1 << 8) +#define SAA7134_MAIN_CTRL_TE6 (1 << 6) +#define SAA7134_MAIN_CTRL_TE5 (1 << 5) +#define SAA7134_MAIN_CTRL_TE4 (1 << 4) +#define SAA7134_MAIN_CTRL_TE3 (1 << 3) +#define SAA7134_MAIN_CTRL_TE2 (1 << 2) +#define SAA7134_MAIN_CTRL_TE1 (1 << 1) +#define SAA7134_MAIN_CTRL_TE0 (1 << 0) + +/* DMA status */ +#define SAA7134_DMA_STATUS (0x2ac >> 2) + +/* audio / video status */ +#define SAA7134_AV_STATUS (0x2c0 >> 2) +#define SAA7134_AV_STATUS_STEREO (1 << 17) +#define SAA7134_AV_STATUS_DUAL (1 << 16) +#define SAA7134_AV_STATUS_PILOT (1 << 15) +#define SAA7134_AV_STATUS_SMB (1 << 14) +#define SAA7134_AV_STATUS_DMB (1 << 13) +#define SAA7134_AV_STATUS_VDSP (1 << 12) +#define SAA7134_AV_STATUS_IIC_STATUS (3 << 10) +#define SAA7134_AV_STATUS_MVM (7 << 7) +#define SAA7134_AV_STATUS_FIDT (1 << 6) +#define SAA7134_AV_STATUS_INTL (1 << 5) +#define SAA7134_AV_STATUS_RDCAP (1 << 4) +#define SAA7134_AV_STATUS_PWR_ON (1 << 3) +#define SAA7134_AV_STATUS_LOAD_ERR (1 << 2) +#define SAA7134_AV_STATUS_TRIG_ERR (1 << 1) +#define SAA7134_AV_STATUS_CONF_ERR (1 << 0) + +/* interrupt */ +#define SAA7134_IRQ1 (0x2c4 >> 2) +#define SAA7134_IRQ1_INTE_RA3_1 (1 << 25) +#define SAA7134_IRQ1_INTE_RA3_0 (1 << 24) +#define SAA7134_IRQ1_INTE_RA2_3 (1 << 19) +#define SAA7134_IRQ1_INTE_RA2_2 (1 << 18) +#define SAA7134_IRQ1_INTE_RA2_1 (1 << 17) +#define SAA7134_IRQ1_INTE_RA2_0 (1 << 16) +#define SAA7134_IRQ1_INTE_RA1_3 (1 << 11) +#define SAA7134_IRQ1_INTE_RA1_2 (1 << 10) +#define SAA7134_IRQ1_INTE_RA1_1 (1 << 9) +#define SAA7134_IRQ1_INTE_RA1_0 (1 << 8) +#define SAA7134_IRQ1_INTE_RA0_7 (1 << 7) +#define SAA7134_IRQ1_INTE_RA0_6 (1 << 6) +#define SAA7134_IRQ1_INTE_RA0_5 (1 << 5) +#define SAA7134_IRQ1_INTE_RA0_4 (1 << 4) +#define SAA7134_IRQ1_INTE_RA0_3 (1 << 3) +#define SAA7134_IRQ1_INTE_RA0_2 (1 << 2) +#define SAA7134_IRQ1_INTE_RA0_1 (1 << 1) +#define SAA7134_IRQ1_INTE_RA0_0 (1 << 0) + +#define SAA7134_IRQ2 (0x2c8 >> 2) +#define SAA7134_IRQ2_INTE_GPIO23A (1 << 17) +#define SAA7134_IRQ2_INTE_GPIO23 (1 << 16) +#define SAA7134_IRQ2_INTE_GPIO22A (1 << 15) +#define SAA7134_IRQ2_INTE_GPIO22 (1 << 14) +#define SAA7134_IRQ2_INTE_GPIO18A (1 << 13) +#define SAA7134_IRQ2_INTE_GPIO18 (1 << 12) +#define SAA7134_IRQ2_INTE_GPIO16 (1 << 11) /* not certain */ +#define SAA7134_IRQ2_INTE_SC2 (1 << 10) +#define SAA7134_IRQ2_INTE_SC1 (1 << 9) +#define SAA7134_IRQ2_INTE_SC0 (1 << 8) +#define SAA7134_IRQ2_INTE_DEC5 (1 << 7) +#define SAA7134_IRQ2_INTE_DEC4 (1 << 6) +#define SAA7134_IRQ2_INTE_DEC3 (1 << 5) +#define SAA7134_IRQ2_INTE_DEC2 (1 << 4) +#define SAA7134_IRQ2_INTE_DEC1 (1 << 3) +#define SAA7134_IRQ2_INTE_DEC0 (1 << 2) +#define SAA7134_IRQ2_INTE_PE (1 << 1) +#define SAA7134_IRQ2_INTE_AR (1 << 0) + +#define SAA7134_IRQ_REPORT (0x2cc >> 2) +#define SAA7134_IRQ_REPORT_GPIO23 (1 << 17) +#define SAA7134_IRQ_REPORT_GPIO22 (1 << 16) +#define SAA7134_IRQ_REPORT_GPIO18 (1 << 15) +#define SAA7134_IRQ_REPORT_GPIO16 (1 << 14) /* not certain */ +#define SAA7134_IRQ_REPORT_LOAD_ERR (1 << 13) +#define SAA7134_IRQ_REPORT_CONF_ERR (1 << 12) +#define SAA7134_IRQ_REPORT_TRIG_ERR (1 << 11) +#define SAA7134_IRQ_REPORT_MMC (1 << 10) +#define SAA7134_IRQ_REPORT_FIDT (1 << 9) +#define SAA7134_IRQ_REPORT_INTL (1 << 8) +#define SAA7134_IRQ_REPORT_RDCAP (1 << 7) +#define SAA7134_IRQ_REPORT_PWR_ON (1 << 6) +#define SAA7134_IRQ_REPORT_PE (1 << 5) +#define SAA7134_IRQ_REPORT_AR (1 << 4) +#define SAA7134_IRQ_REPORT_DONE_RA3 (1 << 3) +#define SAA7134_IRQ_REPORT_DONE_RA2 (1 << 2) +#define SAA7134_IRQ_REPORT_DONE_RA1 (1 << 1) +#define SAA7134_IRQ_REPORT_DONE_RA0 (1 << 0) +#define SAA7134_IRQ_STATUS (0x2d0 >> 2) + + +/* ------------------------------------------------------------------ */ +/* + * registers -- 8 bit + */ + +/* video decoder */ +#define SAA7134_INCR_DELAY 0x101 +#define SAA7134_ANALOG_IN_CTRL1 0x102 +#define SAA7134_ANALOG_IN_CTRL2 0x103 +#define SAA7134_ANALOG_IN_CTRL3 0x104 +#define SAA7134_ANALOG_IN_CTRL4 0x105 +#define SAA7134_HSYNC_START 0x106 +#define SAA7134_HSYNC_STOP 0x107 +#define SAA7134_SYNC_CTRL 0x108 +#define SAA7134_LUMA_CTRL 0x109 +#define SAA7134_DEC_LUMA_BRIGHT 0x10a +#define SAA7134_DEC_LUMA_CONTRAST 0x10b +#define SAA7134_DEC_CHROMA_SATURATION 0x10c +#define SAA7134_DEC_CHROMA_HUE 0x10d +#define SAA7134_CHROMA_CTRL1 0x10e +#define SAA7134_CHROMA_GAIN 0x10f +#define SAA7134_CHROMA_CTRL2 0x110 +#define SAA7134_MODE_DELAY_CTRL 0x111 + +#define SAA7134_ANALOG_ADC 0x114 +#define SAA7134_VGATE_START 0x115 +#define SAA7134_VGATE_STOP 0x116 +#define SAA7134_MISC_VGATE_MSB 0x117 +#define SAA7134_RAW_DATA_GAIN 0x118 +#define SAA7134_RAW_DATA_OFFSET 0x119 +#define SAA7134_STATUS_VIDEO1 0x11e +#define SAA7134_STATUS_VIDEO2 0x11f + +/* video scaler */ +#define SAA7134_SOURCE_TIMING1 0x000 +#define SAA7134_SOURCE_TIMING2 0x001 +#define SAA7134_REGION_ENABLE 0x004 +#define SAA7134_SCALER_STATUS0 0x006 +#define SAA7134_SCALER_STATUS1 0x007 +#define SAA7134_START_GREEN 0x00c +#define SAA7134_START_BLUE 0x00d +#define SAA7134_START_RED 0x00e +#define SAA7134_GREEN_PATH(x) (0x010 +x) +#define SAA7134_BLUE_PATH(x) (0x020 +x) +#define SAA7134_RED_PATH(x) (0x030 +x) + +#define TASK_A 0x040 +#define TASK_B 0x080 +#define SAA7134_TASK_CONDITIONS(t) (0x000 +t) +#define SAA7134_FIELD_HANDLING(t) (0x001 +t) +#define SAA7134_DATA_PATH(t) (0x002 +t) +#define SAA7134_VBI_H_START1(t) (0x004 +t) +#define SAA7134_VBI_H_START2(t) (0x005 +t) +#define SAA7134_VBI_H_STOP1(t) (0x006 +t) +#define SAA7134_VBI_H_STOP2(t) (0x007 +t) +#define SAA7134_VBI_V_START1(t) (0x008 +t) +#define SAA7134_VBI_V_START2(t) (0x009 +t) +#define SAA7134_VBI_V_STOP1(t) (0x00a +t) +#define SAA7134_VBI_V_STOP2(t) (0x00b +t) +#define SAA7134_VBI_H_LEN1(t) (0x00c +t) +#define SAA7134_VBI_H_LEN2(t) (0x00d +t) +#define SAA7134_VBI_V_LEN1(t) (0x00e +t) +#define SAA7134_VBI_V_LEN2(t) (0x00f +t) + +#define SAA7134_VIDEO_H_START1(t) (0x014 +t) +#define SAA7134_VIDEO_H_START2(t) (0x015 +t) +#define SAA7134_VIDEO_H_STOP1(t) (0x016 +t) +#define SAA7134_VIDEO_H_STOP2(t) (0x017 +t) +#define SAA7134_VIDEO_V_START1(t) (0x018 +t) +#define SAA7134_VIDEO_V_START2(t) (0x019 +t) +#define SAA7134_VIDEO_V_STOP1(t) (0x01a +t) +#define SAA7134_VIDEO_V_STOP2(t) (0x01b +t) +#define SAA7134_VIDEO_PIXELS1(t) (0x01c +t) +#define SAA7134_VIDEO_PIXELS2(t) (0x01d +t) +#define SAA7134_VIDEO_LINES1(t) (0x01e +t) +#define SAA7134_VIDEO_LINES2(t) (0x01f +t) + +#define SAA7134_H_PRESCALE(t) (0x020 +t) +#define SAA7134_ACC_LENGTH(t) (0x021 +t) +#define SAA7134_LEVEL_CTRL(t) (0x022 +t) +#define SAA7134_FIR_PREFILTER_CTRL(t) (0x023 +t) +#define SAA7134_LUMA_BRIGHT(t) (0x024 +t) +#define SAA7134_LUMA_CONTRAST(t) (0x025 +t) +#define SAA7134_CHROMA_SATURATION(t) (0x026 +t) +#define SAA7134_VBI_H_SCALE_INC1(t) (0x028 +t) +#define SAA7134_VBI_H_SCALE_INC2(t) (0x029 +t) +#define SAA7134_VBI_PHASE_OFFSET_LUMA(t) (0x02a +t) +#define SAA7134_VBI_PHASE_OFFSET_CHROMA(t) (0x02b +t) +#define SAA7134_H_SCALE_INC1(t) (0x02c +t) +#define SAA7134_H_SCALE_INC2(t) (0x02d +t) +#define SAA7134_H_PHASE_OFF_LUMA(t) (0x02e +t) +#define SAA7134_H_PHASE_OFF_CHROMA(t) (0x02f +t) +#define SAA7134_V_SCALE_RATIO1(t) (0x030 +t) +#define SAA7134_V_SCALE_RATIO2(t) (0x031 +t) +#define SAA7134_V_FILTER(t) (0x032 +t) +#define SAA7134_V_PHASE_OFFSET0(t) (0x034 +t) +#define SAA7134_V_PHASE_OFFSET1(t) (0x035 +t) +#define SAA7134_V_PHASE_OFFSET2(t) (0x036 +t) +#define SAA7134_V_PHASE_OFFSET3(t) (0x037 +t) + +/* clipping & dma */ +#define SAA7134_OFMT_VIDEO_A 0x300 +#define SAA7134_OFMT_DATA_A 0x301 +#define SAA7134_OFMT_VIDEO_B 0x302 +#define SAA7134_OFMT_DATA_B 0x303 +#define SAA7134_ALPHA_NOCLIP 0x304 +#define SAA7134_ALPHA_CLIP 0x305 +#define SAA7134_UV_PIXEL 0x308 +#define SAA7134_CLIP_RED 0x309 +#define SAA7134_CLIP_GREEN 0x30a +#define SAA7134_CLIP_BLUE 0x30b + +/* i2c bus */ +#define SAA7134_I2C_ATTR_STATUS 0x180 +#define SAA7134_I2C_DATA 0x181 +#define SAA7134_I2C_CLOCK_SELECT 0x182 +#define SAA7134_I2C_TIMER 0x183 + +/* audio */ +#define SAA7134_NICAM_ADD_DATA1 0x140 +#define SAA7134_NICAM_ADD_DATA2 0x141 +#define SAA7134_NICAM_STATUS 0x142 +#define SAA7134_AUDIO_STATUS 0x143 +#define SAA7134_NICAM_ERROR_COUNT 0x144 +#define SAA7134_IDENT_SIF 0x145 +#define SAA7134_LEVEL_READOUT1 0x146 +#define SAA7134_LEVEL_READOUT2 0x147 +#define SAA7134_NICAM_ERROR_LOW 0x148 +#define SAA7134_NICAM_ERROR_HIGH 0x149 +#define SAA7134_DCXO_IDENT_CTRL 0x14a +#define SAA7134_DEMODULATOR 0x14b +#define SAA7134_AGC_GAIN_SELECT 0x14c +#define SAA7134_CARRIER1_FREQ0 0x150 +#define SAA7134_CARRIER1_FREQ1 0x151 +#define SAA7134_CARRIER1_FREQ2 0x152 +#define SAA7134_CARRIER2_FREQ0 0x154 +#define SAA7134_CARRIER2_FREQ1 0x155 +#define SAA7134_CARRIER2_FREQ2 0x156 +#define SAA7134_NUM_SAMPLES0 0x158 +#define SAA7134_NUM_SAMPLES1 0x159 +#define SAA7134_NUM_SAMPLES2 0x15a +#define SAA7134_AUDIO_FORMAT_CTRL 0x15b +#define SAA7134_MONITOR_SELECT 0x160 +#define SAA7134_FM_DEEMPHASIS 0x161 +#define SAA7134_FM_DEMATRIX 0x162 +#define SAA7134_CHANNEL1_LEVEL 0x163 +#define SAA7134_CHANNEL2_LEVEL 0x164 +#define SAA7134_NICAM_CONFIG 0x165 +#define SAA7134_NICAM_LEVEL_ADJUST 0x166 +#define SAA7134_STEREO_DAC_OUTPUT_SELECT 0x167 +#define SAA7134_I2S_OUTPUT_FORMAT 0x168 +#define SAA7134_I2S_OUTPUT_SELECT 0x169 +#define SAA7134_I2S_OUTPUT_LEVEL 0x16a +#define SAA7134_DSP_OUTPUT_SELECT 0x16b +#define SAA7134_AUDIO_MUTE_CTRL 0x16c +#define SAA7134_SIF_SAMPLE_FREQ 0x16d +#define SAA7134_ANALOG_IO_SELECT 0x16e +#define SAA7134_AUDIO_CLOCK0 0x170 +#define SAA7134_AUDIO_CLOCK1 0x171 +#define SAA7134_AUDIO_CLOCK2 0x172 +#define SAA7134_AUDIO_PLL_CTRL 0x173 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD0 0x174 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD1 0x175 +#define SAA7134_AUDIO_CLOCKS_PER_FIELD2 0x176 + +/* video port output */ +#define SAA7134_VIDEO_PORT_CTRL0 0x190 +#define SAA7134_VIDEO_PORT_CTRL1 0x191 +#define SAA7134_VIDEO_PORT_CTRL2 0x192 +#define SAA7134_VIDEO_PORT_CTRL3 0x193 +#define SAA7134_VIDEO_PORT_CTRL4 0x194 +#define SAA7134_VIDEO_PORT_CTRL5 0x195 +#define SAA7134_VIDEO_PORT_CTRL6 0x196 +#define SAA7134_VIDEO_PORT_CTRL7 0x197 +#define SAA7134_VIDEO_PORT_CTRL8 0x198 + +/* transport stream interface */ +#define SAA7134_TS_PARALLEL 0x1a0 +#define SAA7134_TS_PARALLEL_SERIAL 0x1a1 +#define SAA7134_TS_SERIAL0 0x1a2 +#define SAA7134_TS_SERIAL1 0x1a3 +#define SAA7134_TS_DMA0 0x1a4 +#define SAA7134_TS_DMA1 0x1a5 +#define SAA7134_TS_DMA2 0x1a6 + +/* GPIO Controls */ +#define SAA7134_GPIO_GPRESCAN 0x80 +#define SAA7134_GPIO_27_25 0x0E + +#define SAA7134_GPIO_GPMODE0 0x1B0 +#define SAA7134_GPIO_GPMODE1 0x1B1 +#define SAA7134_GPIO_GPMODE2 0x1B2 +#define SAA7134_GPIO_GPMODE3 0x1B3 +#define SAA7134_GPIO_GPSTATUS0 0x1B4 +#define SAA7134_GPIO_GPSTATUS1 0x1B5 +#define SAA7134_GPIO_GPSTATUS2 0x1B6 +#define SAA7134_GPIO_GPSTATUS3 0x1B7 + +/* I2S output */ +#define SAA7134_I2S_AUDIO_OUTPUT 0x1c0 + +/* test modes */ +#define SAA7134_SPECIAL_MODE 0x1d0 + +/* audio -- saa7133 + saa7135 only */ +#define SAA7135_DSP_RWSTATE 0x580 +#define SAA7135_DSP_RWSTATE_ERR (1 << 3) +#define SAA7135_DSP_RWSTATE_IDA (1 << 2) +#define SAA7135_DSP_RWSTATE_RDB (1 << 1) +#define SAA7135_DSP_RWSTATE_WRR (1 << 0) + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/media/video/saa7134/saa7134-ts.c b/drivers/media/video/saa7134/saa7134-ts.c new file mode 100644 index 000000000000..345eb2a8c28d --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-ts.c @@ -0,0 +1,243 @@ +/* + * $Id: saa7134-ts.c,v 1.14 2005/02/03 10:24:33 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int ts_debug = 0; +module_param(ts_debug, int, 0644); +MODULE_PARM_DESC(ts_debug,"enable debug messages [ts]"); + +#define dprintk(fmt, arg...) if (ts_debug) \ + printk(KERN_DEBUG "%s/ts: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + u32 control; + + dprintk("buffer_activate [%p]",buf); + buf->vb.state = STATE_ACTIVE; + buf->top_seen = 0; + + /* dma: setup channel 5 (= TS) */ + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + + if (NULL == next) + next = buf; + if (V4L2_FIELD_TOP == buf->vb.field) { + dprintk("- [top] buf=%p next=%p\n",buf,next); + saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(buf)); + saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(next)); + } else { + dprintk("- [bottom] buf=%p next=%p\n",buf,next); + saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(next)); + saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(buf)); + } + saa_writel(SAA7134_RS_PITCH(5),TS_PACKET_SIZE); + saa_writel(SAA7134_RS_CONTROL(5),control); + + /* start DMA */ + saa7134_set_dmabits(dev); + + mod_timer(&dev->ts_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_dev *dev = q->priv_data; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + unsigned int lines, llength, size; + int err; + + dprintk("buffer_prepare [%p,%s]\n",buf,v4l2_field_names[field]); + + llength = TS_PACKET_SIZE; + lines = dev->ts.nr_packets; + + size = lines * llength; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (buf->vb.size != size) { + saa7134_dma_free(dev,buf); + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = llength; + buf->vb.height = lines; + buf->vb.size = size; + buf->pt = &dev->ts.pt_ts; + + err = videobuf_iolock(dev->pci,&buf->vb,NULL); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + buf->vb.dma.sglist, + buf->vb.dma.sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + buf->vb.field = field; + return 0; + + oops: + saa7134_dma_free(dev,buf); + return err; +} + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct saa7134_dev *dev = q->priv_data; + + *size = TS_PACKET_SIZE * dev->ts.nr_packets; + if (0 == *count) + *count = dev->ts.nr_bufs; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_dev *dev = q->priv_data; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_buffer_queue(dev,&dev->ts_q,buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_dev *dev = q->priv_data; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_dma_free(dev,buf); +} + +struct videobuf_queue_ops saa7134_ts_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; +EXPORT_SYMBOL_GPL(saa7134_ts_qops); + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +static unsigned int tsbufs = 4; +module_param(tsbufs, int, 0444); +MODULE_PARM_DESC(tsbufs,"number of ts buffers, range 2-32"); + +static unsigned int ts_nr_packets = 30; +module_param(ts_nr_packets, int, 0444); +MODULE_PARM_DESC(ts_nr_packets,"size of a ts buffers (in ts packets)"); + +int saa7134_ts_init1(struct saa7134_dev *dev) +{ + /* sanitycheck insmod options */ + if (tsbufs < 2) + tsbufs = 2; + if (tsbufs > VIDEO_MAX_FRAME) + tsbufs = VIDEO_MAX_FRAME; + if (ts_nr_packets < 4) + ts_nr_packets = 4; + if (ts_nr_packets > 312) + ts_nr_packets = 312; + dev->ts.nr_bufs = tsbufs; + dev->ts.nr_packets = ts_nr_packets; + + INIT_LIST_HEAD(&dev->ts_q.queue); + init_timer(&dev->ts_q.timeout); + dev->ts_q.timeout.function = saa7134_buffer_timeout; + dev->ts_q.timeout.data = (unsigned long)(&dev->ts_q); + dev->ts_q.dev = dev; + dev->ts_q.need_two = 1; + saa7134_pgtable_alloc(dev->pci,&dev->ts.pt_ts); + + /* init TS hw */ + saa_writeb(SAA7134_TS_SERIAL1, 0x00); /* deactivate TS softreset */ + saa_writeb(SAA7134_TS_PARALLEL, 0xec); /* TSSOP high active, TSVAL high active, TSLOCK ignored */ + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, (TS_PACKET_SIZE-1)); + saa_writeb(SAA7134_TS_DMA0, ((dev->ts.nr_packets-1)&0xff)); + saa_writeb(SAA7134_TS_DMA1, (((dev->ts.nr_packets-1)>>8)&0xff)); + saa_writeb(SAA7134_TS_DMA2, ((((dev->ts.nr_packets-1)>>16)&0x3f) | 0x00)); /* TSNOPIT=0, TSCOLAP=0 */ + + return 0; +} + +int saa7134_ts_fini(struct saa7134_dev *dev) +{ + saa7134_pgtable_free(dev->pci,&dev->ts.pt_ts); + return 0; +} + + +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->ts_q.curr) { + field = dev->ts_q.curr->vb.field; + if (field == V4L2_FIELD_TOP) { + if ((status & 0x100000) != 0x000000) + goto done; + } else { + if ((status & 0x100000) != 0x100000) + goto done; + } + saa7134_buffer_finish(dev,&dev->ts_q,STATE_DONE); + } + saa7134_buffer_next(dev,&dev->ts_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-tvaudio.c b/drivers/media/video/saa7134/saa7134-tvaudio.c new file mode 100644 index 000000000000..ecac13c006d5 --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-tvaudio.c @@ -0,0 +1,1031 @@ +/* + * $Id: saa7134-tvaudio.c,v 1.22 2005/01/07 13:11:19 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * tv audio decoder (fm stereo, nicam, ...) + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/smp_lock.h> +#include <asm/div64.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int audio_debug = 0; +module_param(audio_debug, int, 0644); +MODULE_PARM_DESC(audio_debug,"enable debug messages [tv audio]"); + +static unsigned int audio_ddep = 0; +module_param(audio_ddep, int, 0644); +MODULE_PARM_DESC(audio_ddep,"audio ddep overwrite"); + +static int audio_clock_override = UNSET; +module_param(audio_clock_override, int, 0644); + +static int audio_clock_tweak = 0; +module_param(audio_clock_tweak, int, 0644); +MODULE_PARM_DESC(audio_clock_tweak, "Audio clock tick fine tuning for cards with audio crystal that's slightly off (range [-1024 .. 1024])"); + +#define dprintk(fmt, arg...) if (audio_debug) \ + printk(KERN_DEBUG "%s/audio: " fmt, dev->name , ## arg) +#define d2printk(fmt, arg...) if (audio_debug > 1) \ + printk(KERN_DEBUG "%s/audio: " fmt, dev->name, ## arg) + +#define print_regb(reg) printk("%s: reg 0x%03x [%-16s]: 0x%02x\n", \ + dev->name,(SAA7134_##reg),(#reg),saa_readb((SAA7134_##reg))) + +/* msecs */ +#define SCAN_INITIAL_DELAY 1000 +#define SCAN_SAMPLE_DELAY 200 +#define SCAN_SUBCARRIER_DELAY 2000 + +/* ------------------------------------------------------------------ */ +/* saa7134 code */ + +static struct mainscan { + char *name; + v4l2_std_id std; + int carr; +} mainscan[] = { + { + .name = "M", + .std = V4L2_STD_NTSC | V4L2_STD_PAL_M, + .carr = 4500, + },{ + .name = "BG", + .std = V4L2_STD_PAL_BG, + .carr = 5500, + },{ + .name = "I", + .std = V4L2_STD_PAL_I, + .carr = 6000, + },{ + .name = "DKL", + .std = V4L2_STD_PAL_DK | V4L2_STD_SECAM, + .carr = 6500, + } +}; + +static struct saa7134_tvaudio tvaudio[] = { + { + .name = "PAL-B/G FM-stereo", + .std = V4L2_STD_PAL, + .mode = TVAUDIO_FM_BG_STEREO, + .carr1 = 5500, + .carr2 = 5742, + },{ + .name = "PAL-D/K1 FM-stereo", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 6258, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-D/K2 FM-stereo", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 6742, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-D/K3 FM-stereo", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 5742, + .mode = TVAUDIO_FM_BG_STEREO, + },{ + .name = "PAL-B/G NICAM", + .std = V4L2_STD_PAL, + .carr1 = 5500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "PAL-I NICAM", + .std = V4L2_STD_PAL, + .carr1 = 6000, + .carr2 = 6552, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "PAL-D/K NICAM", + .std = V4L2_STD_PAL, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_FM, + },{ + .name = "SECAM-L NICAM", + .std = V4L2_STD_SECAM, + .carr1 = 6500, + .carr2 = 5850, + .mode = TVAUDIO_NICAM_AM, + },{ + .name = "SECAM-D/K", + .std = V4L2_STD_SECAM, + .carr1 = 6500, + .carr2 = -1, + .mode = TVAUDIO_FM_MONO, + },{ + .name = "NTSC-M", + .std = V4L2_STD_NTSC, + .carr1 = 4500, + .carr2 = -1, + .mode = TVAUDIO_FM_MONO, + },{ + .name = "NTSC-A2 FM-stereo", + .std = V4L2_STD_NTSC, + .carr1 = 4500, + .carr2 = 4724, + .mode = TVAUDIO_FM_K_STEREO, + } +}; +#define TVAUDIO (sizeof(tvaudio)/sizeof(struct saa7134_tvaudio)) + +/* ------------------------------------------------------------------ */ + +static void tvaudio_init(struct saa7134_dev *dev) +{ + int clock = saa7134_boards[dev->board].audio_clock; + + if (UNSET != audio_clock_override) + clock = audio_clock_override; + + /* init all audio registers */ + saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x00); + if (need_resched()) + schedule(); + else + udelay(10); + + saa_writeb(SAA7134_AUDIO_CLOCK0, clock & 0xff); + saa_writeb(SAA7134_AUDIO_CLOCK1, (clock >> 8) & 0xff); + saa_writeb(SAA7134_AUDIO_CLOCK2, (clock >> 16) & 0xff); + saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x01); + + saa_writeb(SAA7134_NICAM_ERROR_LOW, 0x14); + saa_writeb(SAA7134_NICAM_ERROR_HIGH, 0x50); + saa_writeb(SAA7134_MONITOR_SELECT, 0xa0); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); +} + +static u32 tvaudio_carr2reg(u32 carrier) +{ + u64 a = carrier; + + a <<= 24; + do_div(a,12288); + return a; +} + +static void tvaudio_setcarrier(struct saa7134_dev *dev, + int primary, int secondary) +{ + if (-1 == secondary) + secondary = primary; + saa_writel(SAA7134_CARRIER1_FREQ0 >> 2, tvaudio_carr2reg(primary)); + saa_writel(SAA7134_CARRIER2_FREQ0 >> 2, tvaudio_carr2reg(secondary)); +} + +static void mute_input_7134(struct saa7134_dev *dev) +{ + unsigned int mute; + struct saa7134_input *in; + int ausel=0, ics=0, ocs=0; + int mask; + + /* look what is to do ... */ + in = dev->input; + mute = (dev->ctl_mute || + (dev->automute && (&card(dev).radio) != in)); + if (PCI_DEVICE_ID_PHILIPS_SAA7130 == dev->pci->device && + card(dev).mute.name) { + /* 7130 - we'll mute using some unconnected audio input */ + if (mute) + in = &card(dev).mute; + } + if (dev->hw_mute == mute && + dev->hw_input == in) { + dprintk("mute/input: nothing to do [mute=%d,input=%s]\n", + mute,in->name); + return; + } + + dprintk("ctl_mute=%d automute=%d input=%s => mute=%d input=%s\n", + dev->ctl_mute,dev->automute,dev->input->name,mute,in->name); + dev->hw_mute = mute; + dev->hw_input = in; + + if (PCI_DEVICE_ID_PHILIPS_SAA7134 == dev->pci->device) + /* 7134 mute */ + saa_writeb(SAA7134_AUDIO_MUTE_CTRL, mute ? 0xbf : 0xbb); + + /* switch internal audio mux */ + switch (in->amux) { + case TV: ausel=0xc0; ics=0x00; ocs=0x02; break; + case LINE1: ausel=0x80; ics=0x00; ocs=0x00; break; + case LINE2: ausel=0x80; ics=0x08; ocs=0x01; break; + case LINE2_LEFT: ausel=0x80; ics=0x08; ocs=0x05; break; + } + saa_andorb(SAA7134_AUDIO_FORMAT_CTRL, 0xc0, ausel); + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x08, ics); + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, ocs); + + /* switch gpio-connected external audio mux */ + if (0 == card(dev).gpiomask) + return; + mask = card(dev).gpiomask; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, mask, mask); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, mask, in->gpio); + saa7134_track_gpio(dev,in->name); +} + +static void tvaudio_setmode(struct saa7134_dev *dev, + struct saa7134_tvaudio *audio, + char *note) +{ + int acpf, tweak = 0; + + if (dev->tvnorm->id == V4L2_STD_NTSC) { + acpf = 0x19066; + } else { + acpf = 0x1e000; + } + if (audio_clock_tweak > -1024 && audio_clock_tweak < 1024) + tweak = audio_clock_tweak; + + if (note) + dprintk("tvaudio_setmode: %s %s [%d.%03d/%d.%03d MHz] acpf=%d%+d\n", + note,audio->name, + audio->carr1 / 1000, audio->carr1 % 1000, + audio->carr2 / 1000, audio->carr2 % 1000, + acpf, tweak); + + acpf += tweak; + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD0, (acpf & 0x0000ff) >> 0); + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD1, (acpf & 0x00ff00) >> 8); + saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD2, (acpf & 0x030000) >> 16); + tvaudio_setcarrier(dev,audio->carr1,audio->carr2); + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + case TVAUDIO_FM_BG_STEREO: + saa_writeb(SAA7134_DEMODULATOR, 0x00); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x22); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa0); + break; + case TVAUDIO_FM_K_STEREO: + saa_writeb(SAA7134_DEMODULATOR, 0x00); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x01); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x22); + saa_writeb(SAA7134_FM_DEMATRIX, 0x80); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa0); + break; + case TVAUDIO_NICAM_FM: + saa_writeb(SAA7134_DEMODULATOR, 0x10); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x44); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa1); + saa_writeb(SAA7134_NICAM_CONFIG, 0x00); + break; + case TVAUDIO_NICAM_AM: + saa_writeb(SAA7134_DEMODULATOR, 0x12); + saa_writeb(SAA7134_DCXO_IDENT_CTRL, 0x00); + saa_writeb(SAA7134_FM_DEEMPHASIS, 0x44); + saa_writeb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0xa1); + saa_writeb(SAA7134_NICAM_CONFIG, 0x00); + break; + case TVAUDIO_FM_SAT_STEREO: + /* not implemented (yet) */ + break; + } +} + +static int tvaudio_sleep(struct saa7134_dev *dev, int timeout) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&dev->thread.wq, &wait); + if (dev->thread.scan1 == dev->thread.scan2 && !dev->thread.shutdown) { + if (timeout < 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { +#if 0 + /* hmm, that one doesn't return on wakeup ... */ + msleep_interruptible(timeout); +#else + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(timeout)); +#endif + } + } + remove_wait_queue(&dev->thread.wq, &wait); + return dev->thread.scan1 != dev->thread.scan2; +} + +static int tvaudio_checkcarrier(struct saa7134_dev *dev, struct mainscan *scan) +{ + __s32 left,right,value; + + if (audio_debug > 1) { + int i; + dprintk("debug %d:",scan->carr); + for (i = -150; i <= 150; i += 30) { + tvaudio_setcarrier(dev,scan->carr+i,scan->carr+i); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + value = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (0 == i) + printk(" # %6d # ",value >> 16); + else + printk(" %6d",value >> 16); + } + printk("\n"); + } + + if (dev->tvnorm->id & scan->std) { + tvaudio_setcarrier(dev,scan->carr-90,scan->carr-90); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + left = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + + tvaudio_setcarrier(dev,scan->carr+90,scan->carr+90); + saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + if (tvaudio_sleep(dev,SCAN_SAMPLE_DELAY)) + return -1; + right = saa_readl(SAA7134_LEVEL_READOUT1 >> 2); + + left >>= 16; + right >>= 16; + value = left > right ? left - right : right - left; + dprintk("scanning %d.%03d MHz [%4s] => dc is %5d [%d/%d]\n", + scan->carr / 1000, scan->carr % 1000, + scan->name, value, left, right); + } else { + value = 0; + dprintk("skipping %d.%03d MHz [%4s]\n", + scan->carr / 1000, scan->carr % 1000, scan->name); + } + return value; +} + +#if 0 +static void sifdebug_dump_regs(struct saa7134_dev *dev) +{ + print_regb(AUDIO_STATUS); + print_regb(IDENT_SIF); + print_regb(LEVEL_READOUT1); + print_regb(LEVEL_READOUT2); + print_regb(DCXO_IDENT_CTRL); + print_regb(DEMODULATOR); + print_regb(AGC_GAIN_SELECT); + print_regb(MONITOR_SELECT); + print_regb(FM_DEEMPHASIS); + print_regb(FM_DEMATRIX); + print_regb(SIF_SAMPLE_FREQ); + print_regb(ANALOG_IO_SELECT); +} +#endif + +static int tvaudio_getstereo(struct saa7134_dev *dev, struct saa7134_tvaudio *audio) +{ + __u32 idp,nicam; + int retval = -1; + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + return V4L2_TUNER_SUB_MONO; + case TVAUDIO_FM_K_STEREO: + case TVAUDIO_FM_BG_STEREO: + idp = (saa_readb(SAA7134_IDENT_SIF) & 0xe0) >> 5; + dprintk("getstereo: fm/stereo: idp=0x%x\n",idp); + if (0x03 == (idp & 0x03)) + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + else if (0x05 == (idp & 0x05)) + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + else if (0x01 == (idp & 0x01)) + retval = V4L2_TUNER_SUB_MONO; + break; + case TVAUDIO_FM_SAT_STEREO: + /* not implemented (yet) */ + break; + case TVAUDIO_NICAM_FM: + case TVAUDIO_NICAM_AM: + nicam = saa_readb(SAA7134_NICAM_STATUS); + dprintk("getstereo: nicam=0x%x\n",nicam); + switch (nicam & 0x0b) { + case 0x09: + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + break; + case 0x0a: + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + break; + case 0x08: + default: + retval = V4L2_TUNER_SUB_MONO; + break; + } + break; + } + if (retval != -1) + dprintk("found audio subchannels:%s%s%s%s\n", + (retval & V4L2_TUNER_SUB_MONO) ? " mono" : "", + (retval & V4L2_TUNER_SUB_STEREO) ? " stereo" : "", + (retval & V4L2_TUNER_SUB_LANG1) ? " lang1" : "", + (retval & V4L2_TUNER_SUB_LANG2) ? " lang2" : ""); + return retval; +} + +static int tvaudio_setstereo(struct saa7134_dev *dev, struct saa7134_tvaudio *audio, + u32 mode) +{ + static char *name[] = { + [ V4L2_TUNER_MODE_MONO ] = "mono", + [ V4L2_TUNER_MODE_STEREO ] = "stereo", + [ V4L2_TUNER_MODE_LANG1 ] = "lang1", + [ V4L2_TUNER_MODE_LANG2 ] = "lang2", + }; + static u32 fm[] = { + [ V4L2_TUNER_MODE_MONO ] = 0x00, /* ch1 */ + [ V4L2_TUNER_MODE_STEREO ] = 0x80, /* auto */ + [ V4L2_TUNER_MODE_LANG1 ] = 0x00, /* ch1 */ + [ V4L2_TUNER_MODE_LANG2 ] = 0x01, /* ch2 */ + }; + u32 reg; + + switch (audio->mode) { + case TVAUDIO_FM_MONO: + /* nothing to do ... */ + break; + case TVAUDIO_FM_K_STEREO: + case TVAUDIO_FM_BG_STEREO: + dprintk("setstereo [fm] => %s\n", + name[ mode % ARRAY_SIZE(name) ]); + reg = fm[ mode % ARRAY_SIZE(fm) ]; + saa_writeb(SAA7134_FM_DEMATRIX, reg); + break; + case TVAUDIO_FM_SAT_STEREO: + case TVAUDIO_NICAM_AM: + case TVAUDIO_NICAM_FM: + /* FIXME */ + break; + } + return 0; +} + +static int tvaudio_thread(void *data) +{ + struct saa7134_dev *dev = data; + int carr_vals[ARRAY_SIZE(mainscan)]; + unsigned int i, audio, nscan; + int max1,max2,carrier,rx,mode,lastmode,default_carrier; + + daemonize("%s", dev->name); + allow_signal(SIGTERM); + for (;;) { + tvaudio_sleep(dev,-1); + if (dev->thread.shutdown || signal_pending(current)) + goto done; + + restart: + dev->thread.scan1 = dev->thread.scan2; + dprintk("tvaudio thread scan start [%d]\n",dev->thread.scan1); + dev->tvaudio = NULL; + tvaudio_init(dev); + if (dev->ctl_automute) + dev->automute = 1; + mute_input_7134(dev); + + /* give the tuner some time */ + if (tvaudio_sleep(dev,SCAN_INITIAL_DELAY)) + goto restart; + + max1 = 0; + max2 = 0; + nscan = 0; + carrier = 0; + default_carrier = 0; + for (i = 0; i < ARRAY_SIZE(mainscan); i++) { + if (!(dev->tvnorm->id & mainscan[i].std)) + continue; + if (!default_carrier) + default_carrier = mainscan[i].carr; + nscan++; + } + + if (1 == nscan) { + /* only one candidate -- skip scan ;) */ + max1 = 12345; + carrier = default_carrier; + } else { + /* scan for the main carrier */ + saa_writeb(SAA7134_MONITOR_SELECT,0x00); + tvaudio_setmode(dev,&tvaudio[0],NULL); + for (i = 0; i < ARRAY_SIZE(mainscan); i++) { + carr_vals[i] = tvaudio_checkcarrier(dev, mainscan+i); + if (dev->thread.scan1 != dev->thread.scan2) + goto restart; + } + for (max1 = 0, max2 = 0, i = 0; i < ARRAY_SIZE(mainscan); i++) { + if (max1 < carr_vals[i]) { + max2 = max1; + max1 = carr_vals[i]; + carrier = mainscan[i].carr; + } else if (max2 < carr_vals[i]) { + max2 = carr_vals[i]; + } + } + } + + if (0 != carrier && max1 > 2000 && max1 > max2*3) { + /* found good carrier */ + dprintk("found %s main sound carrier @ %d.%03d MHz [%d/%d]\n", + dev->tvnorm->name, carrier/1000, carrier%1000, + max1, max2); + dev->last_carrier = carrier; + + } else if (0 != dev->last_carrier) { + /* no carrier -- try last detected one as fallback */ + carrier = dev->last_carrier; + printk(KERN_WARNING "%s/audio: audio carrier scan failed, " + "using %d.%03d MHz [last detected]\n", + dev->name, carrier/1000, carrier%1000); + + } else { + /* no carrier + no fallback -- use default */ + carrier = default_carrier; + printk(KERN_WARNING "%s/audio: audio carrier scan failed, " + "using %d.%03d MHz [default]\n", + dev->name, carrier/1000, carrier%1000); + } + tvaudio_setcarrier(dev,carrier,carrier); + dev->automute = 0; + saa_andorb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0x30, 0x00); + saa7134_tvaudio_setmute(dev); + + /* find the exact tv audio norm */ + for (audio = UNSET, i = 0; i < TVAUDIO; i++) { + if (dev->tvnorm->id != UNSET && + !(dev->tvnorm->id & tvaudio[i].std)) + continue; + if (tvaudio[i].carr1 != carrier) + continue; + + if (UNSET == audio) + audio = i; + tvaudio_setmode(dev,&tvaudio[i],"trying"); + if (tvaudio_sleep(dev,SCAN_SUBCARRIER_DELAY)) + goto restart; + if (-1 != tvaudio_getstereo(dev,&tvaudio[i])) { + audio = i; + break; + } + } + saa_andorb(SAA7134_STEREO_DAC_OUTPUT_SELECT, 0x30, 0x30); + if (UNSET == audio) + continue; + tvaudio_setmode(dev,&tvaudio[audio],"using"); + tvaudio_setstereo(dev,&tvaudio[audio],V4L2_TUNER_MODE_MONO); + dev->tvaudio = &tvaudio[audio]; + + lastmode = 42; + for (;;) { + if (tvaudio_sleep(dev,5000)) + goto restart; + if (dev->thread.shutdown || signal_pending(current)) + break; + if (UNSET == dev->thread.mode) { + rx = tvaudio_getstereo(dev,&tvaudio[i]); + mode = saa7134_tvaudio_rx2mode(rx); + } else { + mode = dev->thread.mode; + } + if (lastmode != mode) { + tvaudio_setstereo(dev,&tvaudio[audio],mode); + lastmode = mode; + } + } + } + + done: + complete_and_exit(&dev->thread.exit, 0); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* saa7133 / saa7135 code */ + +static char *stdres[0x20] = { + [0x00] = "no standard detected", + [0x01] = "B/G (in progress)", + [0x02] = "D/K (in progress)", + [0x03] = "M (in progress)", + + [0x04] = "B/G A2", + [0x05] = "B/G NICAM", + [0x06] = "D/K A2 (1)", + [0x07] = "D/K A2 (2)", + [0x08] = "D/K A2 (3)", + [0x09] = "D/K NICAM", + [0x0a] = "L NICAM", + [0x0b] = "I NICAM", + + [0x0c] = "M Korea", + [0x0d] = "M BTSC ", + [0x0e] = "M EIAJ", + + [0x0f] = "FM radio / IF 10.7 / 50 deemp", + [0x10] = "FM radio / IF 10.7 / 75 deemp", + [0x11] = "FM radio / IF sel / 50 deemp", + [0x12] = "FM radio / IF sel / 75 deemp", + + [0x13 ... 0x1e ] = "unknown", + [0x1f] = "??? [in progress]", +}; + +#define DSP_RETRY 32 +#define DSP_DELAY 16 + +static inline int saa_dsp_wait_bit(struct saa7134_dev *dev, int bit) +{ + int state, count = DSP_RETRY; + + state = saa_readb(SAA7135_DSP_RWSTATE); + if (unlikely(state & SAA7135_DSP_RWSTATE_ERR)) { + printk("%s: dsp access error\n",dev->name); + /* FIXME: send ack ... */ + return -EIO; + } + while (0 == (state & bit)) { + if (unlikely(0 == count)) { + printk("%s: dsp access wait timeout [bit=%s]\n", + dev->name, + (bit & SAA7135_DSP_RWSTATE_WRR) ? "WRR" : + (bit & SAA7135_DSP_RWSTATE_RDB) ? "RDB" : + (bit & SAA7135_DSP_RWSTATE_IDA) ? "IDA" : + "???"); + return -EIO; + } + saa_wait(DSP_DELAY); + state = saa_readb(SAA7135_DSP_RWSTATE); + count--; + } + return 0; +} + +#if 0 +static int saa_dsp_readl(struct saa7134_dev *dev, int reg, u32 *value) +{ + int err; + + d2printk("dsp read reg 0x%x\n", reg<<2); + saa_readl(reg); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_RDB); + if (err < 0) + return err; + *value = saa_readl(reg); + d2printk("dsp read => 0x%06x\n", *value & 0xffffff); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_IDA); + if (err < 0) + return err; + return 0; +} +#endif + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value) +{ + int err; + + d2printk("dsp write reg 0x%x = 0x%06x\n",reg<<2,value); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_WRR); + if (err < 0) + return err; + saa_writel(reg,value); + err = saa_dsp_wait_bit(dev,SAA7135_DSP_RWSTATE_WRR); + if (err < 0) + return err; + return 0; +} + +static int getstereo_7133(struct saa7134_dev *dev) +{ + int retval = V4L2_TUNER_SUB_MONO; + u32 value; + + value = saa_readl(0x528 >> 2); + if (value & 0x20) + retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + if (value & 0x40) + retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + return retval; +} + +static int mute_input_7133(struct saa7134_dev *dev) +{ + u32 reg = 0; + int mask; + + switch (dev->input->amux) { + case TV: + reg = 0x02; + break; + case LINE1: + reg = 0x00; + break; + case LINE2: + case LINE2_LEFT: + reg = 0x01; + break; + } + if (dev->ctl_mute) + reg = 0x07; + saa_writel(0x594 >> 2, reg); + + /* switch gpio-connected external audio mux */ + if (0 != card(dev).gpiomask) { + mask = card(dev).gpiomask; + saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, mask, mask); + saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, mask, dev->input->gpio); + saa7134_track_gpio(dev,dev->input->name); + } + return 0; +} + +static int tvaudio_thread_ddep(void *data) +{ + struct saa7134_dev *dev = data; + u32 value, norms, clock; + + daemonize("%s", dev->name); + allow_signal(SIGTERM); + + clock = saa7134_boards[dev->board].audio_clock; + if (UNSET != audio_clock_override) + clock = audio_clock_override; + saa_writel(0x598 >> 2, clock); + + /* unmute */ + saa_dsp_writel(dev, 0x474 >> 2, 0x00); + saa_dsp_writel(dev, 0x450 >> 2, 0x00); + + for (;;) { + tvaudio_sleep(dev,-1); + if (dev->thread.shutdown || signal_pending(current)) + goto done; + + restart: + dev->thread.scan1 = dev->thread.scan2; + dprintk("tvaudio thread scan start [%d]\n",dev->thread.scan1); + + if (audio_ddep >= 0x04 && audio_ddep <= 0x0e) { + /* insmod option override */ + norms = (audio_ddep << 2) | 0x01; + dprintk("ddep override: %s\n",stdres[audio_ddep]); + } else if (&card(dev).radio == dev->input) { + dprintk("FM Radio\n"); + norms = (0x0f << 2) | 0x01; + } else { + /* (let chip) scan for sound carrier */ + norms = 0; + if (dev->tvnorm->id & V4L2_STD_PAL) { + dprintk("PAL scan\n"); + norms |= 0x2c; /* B/G + D/K + I */ + } + if (dev->tvnorm->id & V4L2_STD_NTSC) { + dprintk("NTSC scan\n"); + norms |= 0x40; /* M */ + } + if (dev->tvnorm->id & V4L2_STD_SECAM) { + dprintk("SECAM scan\n"); + norms |= 0x18; /* L + D/K */ + } + if (0 == norms) + norms = 0x7c; /* all */ + dprintk("scanning:%s%s%s%s%s\n", + (norms & 0x04) ? " B/G" : "", + (norms & 0x08) ? " D/K" : "", + (norms & 0x10) ? " L/L'" : "", + (norms & 0x20) ? " I" : "", + (norms & 0x40) ? " M" : ""); + } + + /* kick automatic standard detection */ + saa_dsp_writel(dev, 0x454 >> 2, 0); + saa_dsp_writel(dev, 0x454 >> 2, norms | 0x80); + + /* setup crossbars */ + saa_dsp_writel(dev, 0x464 >> 2, 0x000000); + saa_dsp_writel(dev, 0x470 >> 2, 0x101010); + + if (tvaudio_sleep(dev,3000)) + goto restart; + value = saa_readl(0x528 >> 2) & 0xffffff; + + dprintk("tvaudio thread status: 0x%x [%s%s%s]\n", + value, stdres[value & 0x1f], + (value & 0x000020) ? ",stereo" : "", + (value & 0x000040) ? ",dual" : ""); + dprintk("detailed status: " + "%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s\n", + (value & 0x000080) ? " A2/EIAJ pilot tone " : "", + (value & 0x000100) ? " A2/EIAJ dual " : "", + (value & 0x000200) ? " A2/EIAJ stereo " : "", + (value & 0x000400) ? " A2/EIAJ noise mute " : "", + + (value & 0x000800) ? " BTSC/FM radio pilot " : "", + (value & 0x001000) ? " SAP carrier " : "", + (value & 0x002000) ? " BTSC stereo noise mute " : "", + (value & 0x004000) ? " SAP noise mute " : "", + (value & 0x008000) ? " VDSP " : "", + + (value & 0x010000) ? " NICST " : "", + (value & 0x020000) ? " NICDU " : "", + (value & 0x040000) ? " NICAM muted " : "", + (value & 0x080000) ? " NICAM reserve sound " : "", + + (value & 0x100000) ? " init done " : ""); + } + + done: + complete_and_exit(&dev->thread.exit, 0); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* common stuff + external entry points */ + +static void saa7134_enable_i2s(struct saa7134_dev *dev) +{ + int i2s_format; + + if (!card_is_empress(dev)) + return; + i2s_format = (dev->input->amux == TV) ? 0x00 : 0x01; + + /* enable I2S audio output for the mpeg encoder */ + saa_writeb(SAA7134_I2S_OUTPUT_SELECT, 0x80); + saa_writeb(SAA7134_I2S_OUTPUT_FORMAT, i2s_format); + saa_writeb(SAA7134_I2S_OUTPUT_LEVEL, 0x0F); + saa_writeb(SAA7134_I2S_AUDIO_OUTPUT, 0x01); +} + +int saa7134_tvaudio_rx2mode(u32 rx) +{ + u32 mode; + + mode = V4L2_TUNER_MODE_MONO; + if (rx & V4L2_TUNER_SUB_STEREO) + mode = V4L2_TUNER_MODE_STEREO; + else if (rx & V4L2_TUNER_SUB_LANG1) + mode = V4L2_TUNER_MODE_LANG1; + else if (rx & V4L2_TUNER_SUB_LANG2) + mode = V4L2_TUNER_MODE_LANG2; + return mode; +} + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7130: + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mute_input_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mute_input_7133(dev); + break; + } +} + +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in) +{ + dev->input = in; + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7130: + case PCI_DEVICE_ID_PHILIPS_SAA7134: + mute_input_7134(dev); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + mute_input_7133(dev); + break; + } + saa7134_enable_i2s(dev); +} + +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level) +{ + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + saa_writeb(SAA7134_CHANNEL1_LEVEL, level & 0x1f); + saa_writeb(SAA7134_CHANNEL2_LEVEL, level & 0x1f); + saa_writeb(SAA7134_NICAM_LEVEL_ADJUST, level & 0x1f); + break; + } +} + +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev) +{ + int retval = V4L2_TUNER_SUB_MONO; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + if (dev->tvaudio) + retval = tvaudio_getstereo(dev,dev->tvaudio); + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + retval = getstereo_7133(dev); + break; + } + return retval; +} + +int saa7134_tvaudio_init2(struct saa7134_dev *dev) +{ + DECLARE_MUTEX_LOCKED(sem); + int (*my_thread)(void *data) = NULL; + + switch (dev->pci->device) { + case PCI_DEVICE_ID_PHILIPS_SAA7134: + my_thread = tvaudio_thread; + break; + case PCI_DEVICE_ID_PHILIPS_SAA7133: + case PCI_DEVICE_ID_PHILIPS_SAA7135: + my_thread = tvaudio_thread_ddep; + break; + } + + dev->thread.pid = -1; + if (my_thread) { + /* start tvaudio thread */ + init_waitqueue_head(&dev->thread.wq); + init_completion(&dev->thread.exit); + dev->thread.pid = kernel_thread(my_thread,dev,0); + if (dev->thread.pid < 0) + printk(KERN_WARNING "%s: kernel_thread() failed\n", + dev->name); + saa7134_tvaudio_do_scan(dev); + } + + saa7134_enable_i2s(dev); + return 0; +} + +int saa7134_tvaudio_fini(struct saa7134_dev *dev) +{ + /* shutdown tvaudio thread */ + if (dev->thread.pid >= 0) { + dev->thread.shutdown = 1; + wake_up_interruptible(&dev->thread.wq); + wait_for_completion(&dev->thread.exit); + } + saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, 0x00); /* LINE1 */ + return 0; +} + +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev) +{ + if (dev->thread.pid >= 0) { + dev->thread.mode = UNSET; + dev->thread.scan2++; + wake_up_interruptible(&dev->thread.wq); + } else { + dev->automute = 0; + saa7134_tvaudio_setmute(dev); + } + return 0; +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-vbi.c b/drivers/media/video/saa7134/saa7134-vbi.c new file mode 100644 index 000000000000..86954cc7c377 --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-vbi.c @@ -0,0 +1,270 @@ +/* + * $Id: saa7134-vbi.c,v 1.6 2004/12/10 12:33:39 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +/* ------------------------------------------------------------------ */ + +static unsigned int vbi_debug = 0; +module_param(vbi_debug, int, 0644); +MODULE_PARM_DESC(vbi_debug,"enable debug messages [vbi]"); + +static unsigned int vbibufs = 4; +module_param(vbibufs, int, 0444); +MODULE_PARM_DESC(vbibufs,"number of vbi buffers, range 2-32"); + +#define dprintk(fmt, arg...) if (vbi_debug) \ + printk(KERN_DEBUG "%s/vbi: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ + +#define VBI_LINE_COUNT 16 +#define VBI_LINE_LENGTH 2048 +#define VBI_SCALE 0x200 + +static void task_init(struct saa7134_dev *dev, struct saa7134_buf *buf, + int task) +{ + struct saa7134_tvnorm *norm = dev->tvnorm; + + /* setup video scaler */ + saa_writeb(SAA7134_VBI_H_START1(task), norm->h_start & 0xff); + saa_writeb(SAA7134_VBI_H_START2(task), norm->h_start >> 8); + saa_writeb(SAA7134_VBI_H_STOP1(task), norm->h_stop & 0xff); + saa_writeb(SAA7134_VBI_H_STOP2(task), norm->h_stop >> 8); + saa_writeb(SAA7134_VBI_V_START1(task), norm->vbi_v_start & 0xff); + saa_writeb(SAA7134_VBI_V_START2(task), norm->vbi_v_start >> 8); + saa_writeb(SAA7134_VBI_V_STOP1(task), norm->vbi_v_stop & 0xff); + saa_writeb(SAA7134_VBI_V_STOP2(task), norm->vbi_v_stop >> 8); + + saa_writeb(SAA7134_VBI_H_SCALE_INC1(task), VBI_SCALE & 0xff); + saa_writeb(SAA7134_VBI_H_SCALE_INC2(task), VBI_SCALE >> 8); + saa_writeb(SAA7134_VBI_PHASE_OFFSET_LUMA(task), 0x00); + saa_writeb(SAA7134_VBI_PHASE_OFFSET_CHROMA(task), 0x00); + + saa_writeb(SAA7134_VBI_H_LEN1(task), buf->vb.width & 0xff); + saa_writeb(SAA7134_VBI_H_LEN2(task), buf->vb.width >> 8); + saa_writeb(SAA7134_VBI_V_LEN1(task), buf->vb.height & 0xff); + saa_writeb(SAA7134_VBI_V_LEN2(task), buf->vb.height >> 8); + + saa_andorb(SAA7134_DATA_PATH(task), 0xc0, 0x00); +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + unsigned long control,base; + + dprintk("buffer_activate [%p]\n",buf); + buf->vb.state = STATE_ACTIVE; + buf->top_seen = 0; + + task_init(dev,buf,TASK_A); + task_init(dev,buf,TASK_B); + saa_writeb(SAA7134_OFMT_DATA_A, 0x06); + saa_writeb(SAA7134_OFMT_DATA_B, 0x06); + + /* DMA: setup channel 2+3 (= VBI Task A+B) */ + base = saa7134_buffer_base(buf); + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + saa_writel(SAA7134_RS_BA1(2),base); + saa_writel(SAA7134_RS_BA2(2),base + buf->vb.size/2); + saa_writel(SAA7134_RS_PITCH(2),buf->vb.width); + saa_writel(SAA7134_RS_CONTROL(2),control); + saa_writel(SAA7134_RS_BA1(3),base); + saa_writel(SAA7134_RS_BA2(3),base + buf->vb.size/2); + saa_writel(SAA7134_RS_PITCH(3),buf->vb.width); + saa_writel(SAA7134_RS_CONTROL(3),control); + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dev->vbi_q.timeout, jiffies+BUFFER_TIMEOUT); + + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + struct saa7134_tvnorm *norm = dev->tvnorm; + unsigned int lines, llength, size; + int err; + + lines = norm->vbi_v_stop - norm->vbi_v_start +1; + if (lines > VBI_LINE_COUNT) + lines = VBI_LINE_COUNT; +#if 1 + llength = VBI_LINE_LENGTH; +#else + llength = (norm->h_stop - norm->h_start +1) * 2; + if (llength > VBI_LINE_LENGTH) + llength = VBI_LINE_LENGTH; +#endif + size = lines * llength * 2; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + if (buf->vb.size != size) + saa7134_dma_free(dev,buf); + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = llength; + buf->vb.height = lines; + buf->vb.size = size; + buf->pt = &fh->pt_vbi; + + err = videobuf_iolock(dev->pci,&buf->vb,NULL); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + buf->vb.dma.sglist, + buf->vb.dma.sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + buf->vb.field = field; + return 0; + + oops: + saa7134_dma_free(dev,buf); + return err; +} + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = fh->dev; + int llength,lines; + + lines = dev->tvnorm->vbi_v_stop - dev->tvnorm->vbi_v_start +1; +#if 1 + llength = VBI_LINE_LENGTH; +#else + llength = (norm->h_stop - norm->h_start +1) * 2; + if (llength > VBI_LINE_LENGTH) + llength = VBI_LINE_LENGTH; +#endif + *size = lines * llength * 2; + if (0 == *count) + *count = vbibufs; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_buffer_queue(dev,&dev->vbi_q,buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_dma_free(dev,buf); +} + +struct videobuf_queue_ops saa7134_vbi_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +int saa7134_vbi_init1(struct saa7134_dev *dev) +{ + INIT_LIST_HEAD(&dev->vbi_q.queue); + init_timer(&dev->vbi_q.timeout); + dev->vbi_q.timeout.function = saa7134_buffer_timeout; + dev->vbi_q.timeout.data = (unsigned long)(&dev->vbi_q); + dev->vbi_q.dev = dev; + + if (vbibufs < 2) + vbibufs = 2; + if (vbibufs > VIDEO_MAX_FRAME) + vbibufs = VIDEO_MAX_FRAME; + return 0; +} + +int saa7134_vbi_fini(struct saa7134_dev *dev) +{ + /* nothing */ + return 0; +} + +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status) +{ + spin_lock(&dev->slock); + if (dev->vbi_q.curr) { + dev->vbi_fieldcount++; + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->vbi_q.curr->top_seen = 1; + goto done; + } + if (!dev->vbi_q.curr->top_seen) + goto done; + + dev->vbi_q.curr->vb.field_count = dev->vbi_fieldcount; + saa7134_buffer_finish(dev,&dev->vbi_q,STATE_DONE); + } + saa7134_buffer_next(dev,&dev->vbi_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134-video.c b/drivers/media/video/saa7134/saa7134-video.c new file mode 100644 index 000000000000..5d66060026ff --- /dev/null +++ b/drivers/media/video/saa7134/saa7134-video.c @@ -0,0 +1,2406 @@ +/* + * $Id: saa7134-video.c,v 1.28 2005/02/15 15:59:35 kraxel Exp $ + * + * device driver for philips saa7134 based TV cards + * video4linux video interface + * + * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "saa7134-reg.h" +#include "saa7134.h" + +#define V4L2_I2C_CLIENTS 1 + +/* ------------------------------------------------------------------ */ + +static unsigned int video_debug = 0; +static unsigned int gbuffers = 8; +static unsigned int noninterlaced = 0; +static unsigned int gbufsize = 720*576*4; +static unsigned int gbufsize_max = 720*576*4; +module_param(video_debug, int, 0644); +MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); +module_param(gbuffers, int, 0444); +MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32"); +module_param(noninterlaced, int, 0644); +MODULE_PARM_DESC(noninterlaced,"video input is noninterlaced"); + +#define dprintk(fmt, arg...) if (video_debug) \ + printk(KERN_DEBUG "%s/video: " fmt, dev->name , ## arg) + +/* ------------------------------------------------------------------ */ +/* data structs for video */ + +static int video_out[][9] = { + [CCIR656] = { 0x00, 0xb1, 0x00, 0xa1, 0x00, 0x04, 0x06, 0x00, 0x00 }, +}; + +static struct saa7134_format formats[] = { + { + .name = "8 bpp gray", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .pm = 0x06, + },{ + .name = "15 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .pm = 0x13 | 0x80, + },{ + .name = "15 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .pm = 0x13 | 0x80, + .bswap = 1, + },{ + .name = "16 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .pm = 0x10 | 0x80, + },{ + .name = "16 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .pm = 0x10 | 0x80, + .bswap = 1, + },{ + .name = "24 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .pm = 0x11, + },{ + .name = "24 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .pm = 0x11, + .bswap = 1, + },{ + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .pm = 0x12, + },{ + .name = "32 bpp RGB, be", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .pm = 0x12, + .bswap = 1, + .wswap = 1, + },{ + .name = "4:2:2 packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .pm = 0x00, + .bswap = 1, + .yuv = 1, + },{ + .name = "4:2:2 packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .pm = 0x00, + .yuv = 1, + },{ + .name = "4:2:2 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .pm = 0x09, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 0, + },{ + .name = "4:2:0 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .hshift = 1, + .vshift = 1, + },{ + .name = "4:2:0 planar, Y-Cb-Cr", + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .pm = 0x0a, + .yuv = 1, + .planar = 1, + .uvswap = 1, + .hshift = 1, + .vshift = 1, + } +}; +#define FORMATS ARRAY_SIZE(formats) + +#define NORM_625_50 \ + .h_start = 0, \ + .h_stop = 719, \ + .video_v_start = 24, \ + .video_v_stop = 311, \ + .vbi_v_start = 7, \ + .vbi_v_stop = 22, \ + .src_timing = 4 + +#define NORM_525_60 \ + .h_start = 0, \ + .h_stop = 703, \ + .video_v_start = 22, \ + .video_v_stop = 22+239, \ + .vbi_v_start = 10, /* FIXME */ \ + .vbi_v_stop = 21, /* FIXME */ \ + .src_timing = 1 + +static struct saa7134_tvnorm tvnorms[] = { + { + .name = "PAL", /* autodetect */ + .id = V4L2_STD_PAL, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-BG", + .id = V4L2_STD_PAL_BG, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-I", + .id = V4L2_STD_PAL_I, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-DK", + .id = V4L2_STD_PAL_DK, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "NTSC", + .id = V4L2_STD_NTSC, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0x89, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + },{ + .name = "SECAM", + .id = V4L2_STD_SECAM, + NORM_625_50, + + .sync_control = 0x18, /* old: 0x58, */ + .luma_control = 0x1b, + .chroma_ctrl1 = 0xd1, + .chroma_gain = 0x80, + .chroma_ctrl2 = 0x00, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-M", + .id = V4L2_STD_PAL_M, + NORM_525_60, + + .sync_control = 0x59, + .luma_control = 0x40, + .chroma_ctrl1 = 0xb9, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x0e, + .vgate_misc = 0x18, + + },{ + .name = "PAL-Nc", + .id = V4L2_STD_PAL_Nc, + NORM_625_50, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0xa1, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + + },{ + .name = "PAL-60", + .id = V4L2_STD_PAL_60, + + .h_start = 0, + .h_stop = 719, + .video_v_start = 22, + .video_v_stop = 22+239, + .vbi_v_start = 10, /* FIXME */ + .vbi_v_stop = 21, /* FIXME */ + .src_timing = 1, + + .sync_control = 0x18, + .luma_control = 0x40, + .chroma_ctrl1 = 0x81, + .chroma_gain = 0x2a, + .chroma_ctrl2 = 0x06, + .vgate_misc = 0x1c, + } +}; +#define TVNORMS ARRAY_SIZE(tvnorms) + +#define V4L2_CID_PRIVATE_INVERT (V4L2_CID_PRIVATE_BASE + 0) +#define V4L2_CID_PRIVATE_Y_ODD (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_PRIVATE_Y_EVEN (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_PRIVATE_AUTOMUTE (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 4) + +static const struct v4l2_queryctrl no_ctrl = { + .name = "42", + .flags = V4L2_CTRL_FLAG_DISABLED, +}; +static const struct v4l2_queryctrl video_ctrls[] = { + /* --- video --- */ + { + .id = V4L2_CID_BRIGHTNESS, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_CONTRAST, + .name = "Contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 68, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_SATURATION, + .name = "Saturation", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 64, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_HUE, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_VFLIP, + .name = "vertical flip", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, + /* --- audio --- */ + { + .id = V4L2_CID_AUDIO_MUTE, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_AUDIO_VOLUME, + .name = "Volume", + .minimum = -15, + .maximum = 15, + .step = 1, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + /* --- private --- */ + { + .id = V4L2_CID_PRIVATE_INVERT, + .name = "Invert", + .minimum = 0, + .maximum = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + },{ + .id = V4L2_CID_PRIVATE_Y_ODD, + .name = "y offset odd field", + .minimum = 0, + .maximum = 128, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_Y_EVEN, + .name = "y offset even field", + .minimum = 0, + .maximum = 128, + .default_value = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + },{ + .id = V4L2_CID_PRIVATE_AUTOMUTE, + .name = "automute", + .minimum = 0, + .maximum = 1, + .default_value = 1, + .type = V4L2_CTRL_TYPE_BOOLEAN, + } +}; +static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); + +static const struct v4l2_queryctrl* ctrl_by_id(unsigned int id) +{ + unsigned int i; + + for (i = 0; i < CTRLS; i++) + if (video_ctrls[i].id == id) + return video_ctrls+i; + return NULL; +} + +static struct saa7134_format* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < FORMATS; i++) + if (formats[i].fourcc == fourcc) + return formats+i; + return NULL; +} + +/* ----------------------------------------------------------------------- */ +/* resource management */ + +static int res_get(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bit) +{ + if (fh->resources & bit) + /* have it already allocated */ + return 1; + + /* is it free? */ + down(&dev->lock); + if (dev->resources & bit) { + /* no, someone else uses it */ + up(&dev->lock); + return 0; + } + /* it's free, grab it */ + fh->resources |= bit; + dev->resources |= bit; + dprintk("res: get %d\n",bit); + up(&dev->lock); + return 1; +} + +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) +{ + return (dev->resources & bit); +} + +static +void res_free(struct saa7134_dev *dev, struct saa7134_fh *fh, unsigned int bits) +{ + if ((fh->resources & bits) != bits) + BUG(); + + down(&dev->lock); + fh->resources &= ~bits; + dev->resources &= ~bits; + dprintk("res: put %d\n",bits); + up(&dev->lock); +} + +/* ------------------------------------------------------------------ */ + +static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm) +{ + int luma_control,sync_control,mux; + + dprintk("set tv norm = %s\n",norm->name); + dev->tvnorm = norm; + + mux = card_in(dev,dev->ctl_input).vmux; + luma_control = norm->luma_control; + sync_control = norm->sync_control; + + if (mux > 5) + luma_control |= 0x80; /* svideo */ + if (noninterlaced || dev->nosignal) + sync_control |= 0x20; + + /* setup cropping */ + dev->crop_bounds.left = norm->h_start; + dev->crop_defrect.left = norm->h_start; + dev->crop_bounds.width = norm->h_stop - norm->h_start +1; + dev->crop_defrect.width = norm->h_stop - norm->h_start +1; + + dev->crop_bounds.top = (norm->vbi_v_stop+1)*2; + dev->crop_defrect.top = norm->video_v_start*2; + dev->crop_bounds.height = ((norm->id & V4L2_STD_525_60) ? 524 : 624) + - dev->crop_bounds.top; + dev->crop_defrect.height = (norm->video_v_stop - norm->video_v_start +1)*2; + + dev->crop_current = dev->crop_defrect; + + /* setup video decoder */ + saa_writeb(SAA7134_INCR_DELAY, 0x08); + saa_writeb(SAA7134_ANALOG_IN_CTRL1, 0xc0 | mux); + saa_writeb(SAA7134_ANALOG_IN_CTRL2, 0x00); + + saa_writeb(SAA7134_ANALOG_IN_CTRL3, 0x90); + saa_writeb(SAA7134_ANALOG_IN_CTRL4, 0x90); + saa_writeb(SAA7134_HSYNC_START, 0xeb); + saa_writeb(SAA7134_HSYNC_STOP, 0xe0); + saa_writeb(SAA7134_SOURCE_TIMING1, norm->src_timing); + + saa_writeb(SAA7134_SYNC_CTRL, sync_control); + saa_writeb(SAA7134_LUMA_CTRL, luma_control); + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_contrast); + + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_saturation); + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + saa_writeb(SAA7134_CHROMA_CTRL1, norm->chroma_ctrl1); + saa_writeb(SAA7134_CHROMA_GAIN, norm->chroma_gain); + + saa_writeb(SAA7134_CHROMA_CTRL2, norm->chroma_ctrl2); + saa_writeb(SAA7134_MODE_DELAY_CTRL, 0x00); + + saa_writeb(SAA7134_ANALOG_ADC, 0x01); + saa_writeb(SAA7134_VGATE_START, 0x11); + saa_writeb(SAA7134_VGATE_STOP, 0xfe); + saa_writeb(SAA7134_MISC_VGATE_MSB, norm->vgate_misc); + saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40); + saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80); + +#ifdef V4L2_I2C_CLIENTS + saa7134_i2c_call_clients(dev,VIDIOC_S_STD,&norm->id); +#else + { + /* pass down info to the i2c chips (v4l1) */ + struct video_channel c; + memset(&c,0,sizeof(c)); + c.channel = dev->ctl_input; + c.norm = VIDEO_MODE_PAL; + if (norm->id & V4L2_STD_NTSC) + c.norm = VIDEO_MODE_NTSC; + if (norm->id & V4L2_STD_SECAM) + c.norm = VIDEO_MODE_SECAM; + saa7134_i2c_call_clients(dev,VIDIOCSCHAN,&c); + } +#endif +} + +static void video_mux(struct saa7134_dev *dev, int input) +{ + dprintk("video input = %d [%s]\n",input,card_in(dev,input).name); + dev->ctl_input = input; + set_tvnorm(dev,dev->tvnorm); + saa7134_tvaudio_setinput(dev,&card_in(dev,input)); +} + +static void set_h_prescale(struct saa7134_dev *dev, int task, int prescale) +{ + static const struct { + int xpsc; + int xacl; + int xc2_1; + int xdcg; + int vpfy; + } vals[] = { + /* XPSC XACL XC2_1 XDCG VPFY */ + { 1, 0, 0, 0, 0 }, + { 2, 2, 1, 2, 2 }, + { 3, 4, 1, 3, 2 }, + { 4, 8, 1, 4, 2 }, + { 5, 8, 1, 4, 2 }, + { 6, 8, 1, 4, 3 }, + { 7, 8, 1, 4, 3 }, + { 8, 15, 0, 4, 3 }, + { 9, 15, 0, 4, 3 }, + { 10, 16, 1, 5, 3 }, + }; + static const int count = ARRAY_SIZE(vals); + int i; + + for (i = 0; i < count; i++) + if (vals[i].xpsc == prescale) + break; + if (i == count) + return; + + saa_writeb(SAA7134_H_PRESCALE(task), vals[i].xpsc); + saa_writeb(SAA7134_ACC_LENGTH(task), vals[i].xacl); + saa_writeb(SAA7134_LEVEL_CTRL(task), + (vals[i].xc2_1 << 3) | (vals[i].xdcg)); + saa_andorb(SAA7134_FIR_PREFILTER_CTRL(task), 0x0f, + (vals[i].vpfy << 2) | vals[i].vpfy); +} + +static void set_v_scale(struct saa7134_dev *dev, int task, int yscale) +{ + int val,mirror; + + saa_writeb(SAA7134_V_SCALE_RATIO1(task), yscale & 0xff); + saa_writeb(SAA7134_V_SCALE_RATIO2(task), yscale >> 8); + + mirror = (dev->ctl_mirror) ? 0x02 : 0x00; + if (yscale < 2048) { + /* LPI */ + dprintk("yscale LPI yscale=%d\n",yscale); + saa_writeb(SAA7134_V_FILTER(task), 0x00 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), 0x40); + saa_writeb(SAA7134_CHROMA_SATURATION(task), 0x40); + } else { + /* ACM */ + val = 0x40 * 1024 / yscale; + dprintk("yscale ACM yscale=%d val=0x%x\n",yscale,val); + saa_writeb(SAA7134_V_FILTER(task), 0x01 | mirror); + saa_writeb(SAA7134_LUMA_CONTRAST(task), val); + saa_writeb(SAA7134_CHROMA_SATURATION(task), val); + } + saa_writeb(SAA7134_LUMA_BRIGHT(task), 0x80); +} + +static void set_size(struct saa7134_dev *dev, int task, + int width, int height, int interlace) +{ + int prescale,xscale,yscale,y_even,y_odd; + int h_start, h_stop, v_start, v_stop; + int div = interlace ? 2 : 1; + + /* setup video scaler */ + h_start = dev->crop_current.left; + v_start = dev->crop_current.top/2; + h_stop = (dev->crop_current.left + dev->crop_current.width -1); + v_stop = (dev->crop_current.top + dev->crop_current.height -1)/2; + + saa_writeb(SAA7134_VIDEO_H_START1(task), h_start & 0xff); + saa_writeb(SAA7134_VIDEO_H_START2(task), h_start >> 8); + saa_writeb(SAA7134_VIDEO_H_STOP1(task), h_stop & 0xff); + saa_writeb(SAA7134_VIDEO_H_STOP2(task), h_stop >> 8); + saa_writeb(SAA7134_VIDEO_V_START1(task), v_start & 0xff); + saa_writeb(SAA7134_VIDEO_V_START2(task), v_start >> 8); + saa_writeb(SAA7134_VIDEO_V_STOP1(task), v_stop & 0xff); + saa_writeb(SAA7134_VIDEO_V_STOP2(task), v_stop >> 8); + + prescale = dev->crop_current.width / width; + if (0 == prescale) + prescale = 1; + xscale = 1024 * dev->crop_current.width / prescale / width; + yscale = 512 * div * dev->crop_current.height / height; + dprintk("prescale=%d xscale=%d yscale=%d\n",prescale,xscale,yscale); + set_h_prescale(dev,task,prescale); + saa_writeb(SAA7134_H_SCALE_INC1(task), xscale & 0xff); + saa_writeb(SAA7134_H_SCALE_INC2(task), xscale >> 8); + set_v_scale(dev,task,yscale); + + saa_writeb(SAA7134_VIDEO_PIXELS1(task), width & 0xff); + saa_writeb(SAA7134_VIDEO_PIXELS2(task), width >> 8); + saa_writeb(SAA7134_VIDEO_LINES1(task), height/div & 0xff); + saa_writeb(SAA7134_VIDEO_LINES2(task), height/div >> 8); + + /* deinterlace y offsets */ + y_odd = dev->ctl_y_odd; + y_even = dev->ctl_y_even; + saa_writeb(SAA7134_V_PHASE_OFFSET0(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET1(task), y_even); + saa_writeb(SAA7134_V_PHASE_OFFSET2(task), y_odd); + saa_writeb(SAA7134_V_PHASE_OFFSET3(task), y_even); +} + +/* ------------------------------------------------------------------ */ + +struct cliplist { + __u16 position; + __u8 enable; + __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) +{ + __u8 winbits = 0; + int i; + + for (i = 0; i < entries; i++) { + winbits |= cl[i].enable; + winbits &= ~cl[i].disable; + if (i < 15 && cl[i].position == cl[i+1].position) + continue; + saa_writeb(reg + 0, winbits); + saa_writeb(reg + 2, cl[i].position & 0xff); + saa_writeb(reg + 3, cl[i].position >> 8); + dprintk("clip: %s winbits=%02x pos=%d\n", + name,winbits,cl[i].position); + reg += 8; + } + for (; reg < 0x400; reg += 8) { + saa_writeb(reg+ 0, 0); + saa_writeb(reg + 1, 0); + saa_writeb(reg + 2, 0); + saa_writeb(reg + 3, 0); + } +} + +static int clip_range(int val) +{ + if (val < 0) + val = 0; + return val; +} + +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 div = interlace ? 2 : 1; + + memset(col,0,sizeof(col)); cols = 0; + memset(row,0,sizeof(row)); rows = 0; + for (i = 0; i < nclips && i < 8; i++) { + col[cols].position = clip_range(clips[i].c.left); + col[cols].enable = (1 << i); + cols++; + col[cols].position = clip_range(clips[i].c.left+clips[i].c.width); + col[cols].disable = (1 << i); + cols++; + row[rows].position = clip_range(clips[i].c.top / div); + row[rows].enable = (1 << i); + rows++; + row[rows].position = clip_range((clips[i].c.top + clips[i].c.height) + / div); + row[rows].disable = (1 << i); + rows++; + } + sort_cliplist(col,cols); + sort_cliplist(row,rows); + set_cliplist(dev,0x380,col,cols,"cols"); + set_cliplist(dev,0x384,row,rows,"rows"); + return 0; +} + +static int verify_preview(struct saa7134_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (NULL == dev->ovbuf.base) + return -EINVAL; + if (NULL == dev->ovfmt) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + if (win->clipcount > 2048) + return -EINVAL; + + field = win->field; + maxw = dev->crop_current.width; + maxh = dev->crop_current.height; + + if (V4L2_FIELD_ANY == field) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +static int start_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +{ + unsigned long base,control,bpl; + int err; + + err = verify_preview(dev,&fh->win); + if (0 != err) + return err; + + dev->ovfield = fh->win.field; + dprintk("start_preview %dx%d+%d+%d %s field=%s\n", + fh->win.w.width,fh->win.w.height, + fh->win.w.left,fh->win.w.top, + dev->ovfmt->name,v4l2_field_names[dev->ovfield]); + + /* setup window + clipping */ + set_size(dev,TASK_B,fh->win.w.width,fh->win.w.height, + V4L2_FIELD_HAS_BOTH(dev->ovfield)); + setup_clipping(dev,fh->clips,fh->nclips, + V4L2_FIELD_HAS_BOTH(dev->ovfield)); + if (dev->ovfmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_B), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_B, dev->ovfmt->pm | 0x20); + + /* dma: setup channel 1 (= Video Task B) */ + base = (unsigned long)dev->ovbuf.base; + base += dev->ovbuf.fmt.bytesperline * fh->win.w.top; + base += dev->ovfmt->depth/8 * fh->win.w.left; + bpl = dev->ovbuf.fmt.bytesperline; + control = SAA7134_RS_CONTROL_BURST_16; + if (dev->ovfmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (dev->ovfmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(dev->ovfield)) { + saa_writel(SAA7134_RS_BA1(1),base); + saa_writel(SAA7134_RS_BA2(1),base+bpl); + saa_writel(SAA7134_RS_PITCH(1),bpl*2); + saa_writel(SAA7134_RS_CONTROL(1),control); + } else { + saa_writel(SAA7134_RS_BA1(1),base); + saa_writel(SAA7134_RS_BA2(1),base); + saa_writel(SAA7134_RS_PITCH(1),bpl); + saa_writel(SAA7134_RS_CONTROL(1),control); + } + + /* start dma */ + dev->ovenable = 1; + saa7134_set_dmabits(dev); + + return 0; +} + +static int stop_preview(struct saa7134_dev *dev, struct saa7134_fh *fh) +{ + dev->ovenable = 0; + saa7134_set_dmabits(dev); + return 0; +} + +/* ------------------------------------------------------------------ */ + +static int buffer_activate(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next) +{ + unsigned long base,control,bpl; + unsigned long bpl_uv,lines_uv,base2,base3,tmp; /* planar */ + + dprintk("buffer_activate buf=%p\n",buf); + buf->vb.state = STATE_ACTIVE; + buf->top_seen = 0; + + set_size(dev,TASK_A,buf->vb.width,buf->vb.height, + V4L2_FIELD_HAS_BOTH(buf->vb.field)); + if (buf->fmt->yuv) + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x03); + else + saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x01); + saa_writeb(SAA7134_OFMT_VIDEO_A, buf->fmt->pm); + + /* DMA: setup channel 0 (= Video Task A0) */ + base = saa7134_buffer_base(buf); + if (buf->fmt->planar) + bpl = buf->vb.width; + else + bpl = (buf->vb.width * buf->fmt->depth) / 8; + control = SAA7134_RS_CONTROL_BURST_16 | + SAA7134_RS_CONTROL_ME | + (buf->pt->dma >> 12); + if (buf->fmt->bswap) + control |= SAA7134_RS_CONTROL_BSWAP; + if (buf->fmt->wswap) + control |= SAA7134_RS_CONTROL_WSWAP; + if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base+bpl); + saa_writel(SAA7134_RS_PITCH(0),bpl*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(0),base); + saa_writel(SAA7134_RS_BA2(0),base); + saa_writel(SAA7134_RS_PITCH(0),bpl); + } + saa_writel(SAA7134_RS_CONTROL(0),control); + + if (buf->fmt->planar) { + /* DMA: setup channel 4+5 (= planar task A) */ + bpl_uv = bpl >> buf->fmt->hshift; + lines_uv = buf->vb.height >> buf->fmt->vshift; + base2 = base + bpl * buf->vb.height; + base3 = base2 + bpl_uv * lines_uv; + if (buf->fmt->uvswap) + tmp = base2, base2 = base3, base3 = tmp; + dprintk("uv: bpl=%ld lines=%ld base2/3=%ld/%ld\n", + bpl_uv,lines_uv,base2,base3); + if (V4L2_FIELD_HAS_BOTH(buf->vb.field)) { + /* interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2+bpl_uv); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv*2); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3+bpl_uv); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv*2); + } else { + /* non-interlaced */ + saa_writel(SAA7134_RS_BA1(4),base2); + saa_writel(SAA7134_RS_BA2(4),base2); + saa_writel(SAA7134_RS_PITCH(4),bpl_uv); + saa_writel(SAA7134_RS_BA1(5),base3); + saa_writel(SAA7134_RS_BA2(5),base3); + saa_writel(SAA7134_RS_PITCH(5),bpl_uv); + } + saa_writel(SAA7134_RS_CONTROL(4),control); + saa_writel(SAA7134_RS_CONTROL(5),control); + } + + /* start DMA */ + saa7134_set_dmabits(dev); + mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_dev *dev = fh->dev; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + unsigned int size; + int err; + + /* sanity checks */ + if (NULL == fh->fmt) + return -EINVAL; + if (fh->width < 48 || + fh->height < 32 || + fh->width/4 > dev->crop_current.width || + fh->height/4 > dev->crop_current.height || + fh->width > dev->crop_bounds.width || + fh->height > dev->crop_bounds.height) + return -EINVAL; + size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < size) + return -EINVAL; + + dprintk("buffer_prepare [%d,size=%dx%d,bytes=%d,fields=%s,%s]\n", + vb->i,fh->width,fh->height,size,v4l2_field_names[field], + fh->fmt->name); + if (buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.size != size || + buf->vb.field != field || + buf->fmt != fh->fmt) { + saa7134_dma_free(dev,buf); + } + + if (STATE_NEEDS_INIT == buf->vb.state) { + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.size = size; + buf->vb.field = field; + buf->fmt = fh->fmt; + buf->pt = &fh->pt_cap; + + err = videobuf_iolock(dev->pci,&buf->vb,&dev->ovbuf); + if (err) + goto oops; + err = saa7134_pgtable_build(dev->pci,buf->pt, + buf->vb.dma.sglist, + buf->vb.dma.sglen, + saa7134_buffer_startpage(buf)); + if (err) + goto oops; + } + buf->vb.state = STATE_PREPARED; + buf->activate = buffer_activate; + return 0; + + oops: + saa7134_dma_free(dev,buf); + return err; +} + +static int +buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ + struct saa7134_fh *fh = q->priv_data; + + *size = fh->fmt->depth * fh->width * fh->height >> 3; + if (0 == *count) + *count = gbuffers; + *count = saa7134_buffer_count(*size,*count); + return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_buffer_queue(fh->dev,&fh->dev->video_q,buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct saa7134_fh *fh = q->priv_data; + struct saa7134_buf *buf = container_of(vb,struct saa7134_buf,vb); + + saa7134_dma_free(fh->dev,buf); +} + +static struct videobuf_queue_ops video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +static int get_control(struct saa7134_dev *dev, struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = dev->ctl_bright; + break; + case V4L2_CID_HUE: + c->value = dev->ctl_hue; + break; + case V4L2_CID_CONTRAST: + c->value = dev->ctl_contrast; + break; + case V4L2_CID_SATURATION: + c->value = dev->ctl_saturation; + break; + case V4L2_CID_AUDIO_MUTE: + c->value = dev->ctl_mute; + break; + case V4L2_CID_AUDIO_VOLUME: + c->value = dev->ctl_volume; + break; + case V4L2_CID_PRIVATE_INVERT: + c->value = dev->ctl_invert; + break; + case V4L2_CID_VFLIP: + c->value = dev->ctl_mirror; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + c->value = dev->ctl_y_even; + break; + case V4L2_CID_PRIVATE_Y_ODD: + c->value = dev->ctl_y_odd; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + c->value = dev->ctl_automute; + break; + default: + return -EINVAL; + } + return 0; +} + +static int set_control(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_control *c) +{ + const struct v4l2_queryctrl* ctrl; + unsigned long flags; + int restart_overlay = 0; + + ctrl = ctrl_by_id(c->id); + if (NULL == ctrl) + return -EINVAL; + dprintk("set_control name=%s val=%d\n",ctrl->name,c->value); + switch (ctrl->type) { + case V4L2_CTRL_TYPE_BOOLEAN: + case V4L2_CTRL_TYPE_MENU: + case V4L2_CTRL_TYPE_INTEGER: + if (c->value < ctrl->minimum) + c->value = ctrl->minimum; + if (c->value > ctrl->maximum) + c->value = ctrl->maximum; + break; + default: + /* nothing */; + }; + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + dev->ctl_bright = c->value; + saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); + break; + case V4L2_CID_HUE: + dev->ctl_hue = c->value; + saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); + break; + case V4L2_CID_CONTRAST: + dev->ctl_contrast = c->value; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + break; + case V4L2_CID_SATURATION: + dev->ctl_saturation = c->value; + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_AUDIO_MUTE: + dev->ctl_mute = c->value; + saa7134_tvaudio_setmute(dev); + break; + case V4L2_CID_AUDIO_VOLUME: + dev->ctl_volume = c->value; + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + break; + case V4L2_CID_PRIVATE_INVERT: + dev->ctl_invert = c->value; + saa_writeb(SAA7134_DEC_LUMA_CONTRAST, + dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); + saa_writeb(SAA7134_DEC_CHROMA_SATURATION, + dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); + break; + case V4L2_CID_VFLIP: + dev->ctl_mirror = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_Y_EVEN: + dev->ctl_y_even = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_Y_ODD: + dev->ctl_y_odd = c->value; + restart_overlay = 1; + break; + case V4L2_CID_PRIVATE_AUTOMUTE: + dev->ctl_automute = c->value; + if (dev->tda9887_conf) { + if (dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + else + dev->tda9887_conf &= ~TDA9887_AUTOMUTE; + saa7134_i2c_call_clients(dev, TDA9887_SET_CONFIG, + &dev->tda9887_conf); + } + break; + default: + return -EINVAL; + } + if (restart_overlay && fh && res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + return 0; +} + +/* ------------------------------------------------------------------ */ + +static struct videobuf_queue* saa7134_queue(struct saa7134_fh *fh) +{ + struct videobuf_queue* q = NULL; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + q = &fh->cap; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + q = &fh->vbi; + break; + default: + BUG(); + } + return q; +} + +static int saa7134_resource(struct saa7134_fh *fh) +{ + int res = 0; + + 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; +} + +static int video_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct saa7134_dev *h,*dev = NULL; + struct saa7134_fh *fh; + struct list_head *list; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + int radio = 0; + + list_for_each(list,&saa7134_devlist) { + h = list_entry(list, struct saa7134_dev, devlist); + if (h->video_dev && (h->video_dev->minor == minor)) + dev = h; + if (h->radio_dev && (h->radio_dev->minor == minor)) { + radio = 1; + dev = h; + } + if (h->vbi_dev && (h->vbi_dev->minor == minor)) { + type = V4L2_BUF_TYPE_VBI_CAPTURE; + dev = h; + } + } + if (NULL == dev) + return -ENODEV; + + dprintk("open minor=%d radio=%d type=%s\n",minor,radio, + v4l2_type_names[type]); + + /* allocate + initialize per filehandle data */ + fh = kmalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) + return -ENOMEM; + memset(fh,0,sizeof(*fh)); + file->private_data = fh; + fh->dev = dev; + fh->radio = radio; + fh->type = type; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); + fh->width = 720; + fh->height = 576; + v4l2_prio_open(&dev->prio,&fh->prio); + + videobuf_queue_init(&fh->cap, &video_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct saa7134_buf), + fh); + videobuf_queue_init(&fh->vbi, &saa7134_vbi_qops, + dev->pci, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct saa7134_buf), + fh); + saa7134_pgtable_alloc(dev->pci,&fh->pt_cap); + saa7134_pgtable_alloc(dev->pci,&fh->pt_vbi); + + if (fh->radio) { + /* switch to radio mode */ + saa7134_tvaudio_setinput(dev,&card(dev).radio); + saa7134_i2c_call_clients(dev,AUDC_SET_RADIO,NULL); + } else { + /* switch to video/vbi mode */ + video_mux(dev,dev->ctl_input); + } + return 0; +} + +static ssize_t +video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ + struct saa7134_fh *fh = file->private_data; + + switch (fh->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + return videobuf_read_one(saa7134_queue(fh), + data, count, ppos, + file->f_flags & O_NONBLOCK); + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (!res_get(fh->dev,fh,RESOURCE_VBI)) + return -EBUSY; + return videobuf_read_stream(saa7134_queue(fh), + data, count, ppos, 1, + file->f_flags & O_NONBLOCK); + break; + default: + BUG(); + return 0; + } +} + +static unsigned int +video_poll(struct file *file, struct poll_table_struct *wait) +{ + struct saa7134_fh *fh = file->private_data; + struct videobuf_buffer *buf = NULL; + + if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) + return videobuf_poll_stream(file, &fh->vbi, wait); + + if (res_check(fh,RESOURCE_VIDEO)) { + if (!list_empty(&fh->cap.stream)) + buf = list_entry(fh->cap.stream.next, struct videobuf_buffer, stream); + } else { + down(&fh->cap.lock); + if (UNSET == fh->cap.read_off) { + /* need to capture a new frame */ + if (res_locked(fh->dev,RESOURCE_VIDEO)) { + up(&fh->cap.lock); + return POLLERR; + } + if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,fh->cap.field)) { + up(&fh->cap.lock); + return POLLERR; + } + fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf); + fh->cap.read_off = 0; + } + up(&fh->cap.lock); + buf = fh->cap.read_buf; + } + + if (!buf) + return POLLERR; + + poll_wait(file, &buf->done, wait); + if (buf->state == STATE_DONE || + buf->state == STATE_ERROR) + return POLLIN|POLLRDNORM; + return 0; +} + +static int video_release(struct inode *inode, struct file *file) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + unsigned long flags; + + /* turn off overlay */ + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + res_free(dev,fh,RESOURCE_OVERLAY); + } + + /* stop video capture */ + if (res_check(fh, RESOURCE_VIDEO)) { + videobuf_streamoff(&fh->cap); + res_free(dev,fh,RESOURCE_VIDEO); + } + if (fh->cap.read_buf) { + buffer_release(&fh->cap,fh->cap.read_buf); + kfree(fh->cap.read_buf); + } + + /* stop vbi capture */ + if (res_check(fh, RESOURCE_VBI)) { + if (fh->vbi.streaming) + videobuf_streamoff(&fh->vbi); + if (fh->vbi.reading) + videobuf_read_stop(&fh->vbi); + res_free(dev,fh,RESOURCE_VBI); + } + + /* free stuff */ + videobuf_mmap_free(&fh->cap); + videobuf_mmap_free(&fh->vbi); + saa7134_pgtable_free(dev->pci,&fh->pt_cap); + saa7134_pgtable_free(dev->pci,&fh->pt_vbi); + + v4l2_prio_close(&dev->prio,&fh->prio); + file->private_data = NULL; + kfree(fh); + return 0; +} + +static int +video_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct saa7134_fh *fh = file->private_data; + + return videobuf_mmap_mapper(saa7134_queue(fh), vma); +} + +/* ------------------------------------------------------------------ */ + +static void saa7134_vbi_fmt(struct saa7134_dev *dev, struct v4l2_format *f) +{ + struct saa7134_tvnorm *norm = dev->tvnorm; + + f->fmt.vbi.sampling_rate = 6750000 * 4; + f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; + f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + f->fmt.vbi.offset = 64 * 4; + f->fmt.vbi.start[0] = norm->vbi_v_start; + f->fmt.vbi.count[0] = norm->vbi_v_stop - norm->vbi_v_start +1; + f->fmt.vbi.start[1] = norm->video_v_stop + norm->vbi_v_start +1; + f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; + f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ + +#if 0 + if (V4L2_STD_PAL == norm->id) { + /* FIXME */ + f->fmt.vbi.start[0] += 3; + f->fmt.vbi.start[1] += 3*2; + } +#endif +} + +static int saa7134_g_fmt(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_format *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(&f->fmt.pix,0,sizeof(f->fmt.pix)); + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->cap.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = fh->win; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + saa7134_vbi_fmt(dev,f); + return 0; + default: + return -EINVAL; + } +} + +static int saa7134_try_fmt(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_format *f) +{ + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + { + struct saa7134_format *fmt; + enum v4l2_field field; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) + return -EINVAL; + + field = f->fmt.pix.field; + maxw = min(dev->crop_current.width*4, dev->crop_bounds.width); + maxh = min(dev->crop_current.height*4, dev->crop_bounds.height); + + if (V4L2_FIELD_ANY == field) { + field = (f->fmt.pix.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_BOTTOM; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + f->fmt.pix.field = field; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + err = verify_preview(dev,&f->fmt.win); + if (0 != err) + return err; + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + saa7134_vbi_fmt(dev,f); + return 0; + default: + return -EINVAL; + } +} + +static int saa7134_s_fmt(struct saa7134_dev *dev, struct saa7134_fh *fh, + struct v4l2_format *f) +{ + unsigned long flags; + int err; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + err = saa7134_try_fmt(dev,fh,f); + if (0 != err) + return err; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->cap.field = f->fmt.pix.field; + return 0; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + err = verify_preview(dev,&f->fmt.win); + if (0 != err) + return err; + + down(&dev->lock); + fh->win = f->fmt.win; + fh->nclips = f->fmt.win.clipcount; + if (fh->nclips > 8) + fh->nclips = 8; + if (copy_from_user(fh->clips,f->fmt.win.clips, + sizeof(struct v4l2_clip)*fh->nclips)) { + up(&dev->lock); + return -EFAULT; + } + + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + up(&dev->lock); + return 0; + case V4L2_BUF_TYPE_VBI_CAPTURE: + saa7134_vbi_fmt(dev,f); + return 0; + default: + return -EINVAL; + } +} + +int saa7134_common_ioctl(struct saa7134_dev *dev, + unsigned int cmd, void *arg) +{ + int err; + + switch (cmd) { + case VIDIOC_QUERYCTRL: + { + const struct v4l2_queryctrl *ctrl; + struct v4l2_queryctrl *c = arg; + + if ((c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) && + (c->id < V4L2_CID_PRIVATE_BASE || + c->id >= V4L2_CID_PRIVATE_LASTP1)) + return -EINVAL; + ctrl = ctrl_by_id(c->id); + *c = (NULL != ctrl) ? *ctrl : no_ctrl; + return 0; + } + case VIDIOC_G_CTRL: + return get_control(dev,arg); + case VIDIOC_S_CTRL: + { + down(&dev->lock); + err = set_control(dev,NULL,arg); + up(&dev->lock); + return err; + } + /* --- input switching --------------------------------------- */ + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + unsigned int n; + + n = i->index; + if (n >= SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev,i->index).name) + return -EINVAL; + memset(i,0,sizeof(*i)); + i->index = n; + i->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(i->name,card_in(dev,n).name); + if (card_in(dev,n).tv) + i->type = V4L2_INPUT_TYPE_TUNER; + i->audioset = 1; + if (n == dev->ctl_input) { + int v1 = saa_readb(SAA7134_STATUS_VIDEO1); + int v2 = saa_readb(SAA7134_STATUS_VIDEO2); + + if (0 != (v1 & 0x40)) + i->status |= V4L2_IN_ST_NO_H_LOCK; + if (0 != (v2 & 0x40)) + i->status |= V4L2_IN_ST_NO_SYNC; + if (0 != (v2 & 0x0e)) + i->status |= V4L2_IN_ST_MACROVISION; + } + for (n = 0; n < TVNORMS; n++) + i->std |= tvnorms[n].id; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = dev->ctl_input; + return 0; + } + case VIDIOC_S_INPUT: + { + int *i = arg; + + if (*i < 0 || *i >= SAA7134_INPUT_MAX) + return -EINVAL; + if (NULL == card_in(dev,*i).name) + return -EINVAL; + down(&dev->lock); + video_mux(dev,*i); + up(&dev->lock); + return 0; + } + + } + return 0; +} +EXPORT_SYMBOL(saa7134_common_ioctl); + +/* + * This function is _not_ called directly, but from + * video_generic_ioctl (and maybe others). userspace + * copying is done already, arg is a kernel pointer. + */ +static int video_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + unsigned long flags; + int err; + + if (video_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + + switch (cmd) { + case VIDIOC_S_CTRL: + case VIDIOC_S_STD: + case VIDIOC_S_INPUT: + case VIDIOC_S_TUNER: + case VIDIOC_S_FREQUENCY: + err = v4l2_prio_check(&dev->prio,&fh->prio); + if (0 != err) + return err; + } + + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = SAA7134_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_TUNER | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; + } + + /* --- tv standards ------------------------------------------ */ + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *e = arg; + unsigned int i; + + i = e->index; + if (i >= TVNORMS) + return -EINVAL; + err = v4l2_video_std_construct(e, tvnorms[e->index].id, + tvnorms[e->index].name); + e->index = i; + if (err < 0) + return err; + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + + *id = dev->tvnorm->id; + return 0; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + unsigned int i; + + for (i = 0; i < TVNORMS; i++) + if (*id == tvnorms[i].id) + break; + if (i == TVNORMS) + for (i = 0; i < TVNORMS; i++) + if (*id & tvnorms[i].id) + break; + if (i == TVNORMS) + return -EINVAL; + + down(&dev->lock); + if (res_check(fh, RESOURCE_OVERLAY)) { + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + set_tvnorm(dev,&tvnorms[i]); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } else + set_tvnorm(dev,&tvnorms[i]); + saa7134_tvaudio_do_scan(dev); + up(&dev->lock); + return 0; + } + + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + cap->bounds = dev->crop_bounds; + cap->defrect = dev->crop_defrect; + cap->pixelaspect.numerator = 1; + cap->pixelaspect.denominator = 1; + if (dev->tvnorm->id & V4L2_STD_525_60) { + cap->pixelaspect.numerator = 11; + cap->pixelaspect.denominator = 10; + } + if (dev->tvnorm->id & V4L2_STD_625_50) { + cap->pixelaspect.numerator = 54; + cap->pixelaspect.denominator = 59; + } + return 0; + } + + case VIDIOC_G_CROP: + { + struct v4l2_crop * crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + crop->c = dev->crop_current; + return 0; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &dev->crop_bounds; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) + return -EINVAL; + if (crop->c.height < 0) + return -EINVAL; + if (crop->c.width < 0) + return -EINVAL; + + if (res_locked(fh->dev,RESOURCE_OVERLAY)) + return -EBUSY; + if (res_locked(fh->dev,RESOURCE_VIDEO)) + return -EBUSY; + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.top = b->left; + if (crop->c.left > b->left + b->width) + crop->c.top = b->left + b->width; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = b->left - crop->c.left + b->width; + + dev->crop_current = crop->c; + return 0; + } + + /* --- tuner ioctls ------------------------------------------ */ + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + int n; + + if (0 != t->index) + return -EINVAL; + memset(t,0,sizeof(*t)); + for (n = 0; n < SAA7134_INPUT_MAX; n++) + if (card_in(dev,n).tv) + break; + if (NULL != card_in(dev,n).name) { + strcpy(t->name, "Television"); + t->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + t->rangehigh = 0xffffffffUL; + t->rxsubchans = saa7134_tvaudio_getstereo(dev); + t->audmode = saa7134_tvaudio_rx2mode(t->rxsubchans); + } + if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03)) + t->signal = 0xffff; + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + int rx,mode; + + mode = dev->thread.mode; + if (UNSET == mode) { + rx = saa7134_tvaudio_getstereo(dev); + mode = saa7134_tvaudio_rx2mode(t->rxsubchans); + } + if (mode != t->audmode) { + dev->thread.mode = t->audmode; + } + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + memset(f,0,sizeof(*f)); + f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + f->frequency = dev->ctl_freq; + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (0 != f->tuner) + return -EINVAL; + if (0 == fh->radio && V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + if (1 == fh->radio && V4L2_TUNER_RADIO != f->type) + return -EINVAL; + down(&dev->lock); + dev->ctl_freq = f->frequency; +#ifdef V4L2_I2C_CLIENTS + saa7134_i2c_call_clients(dev,VIDIOC_S_FREQUENCY,f); +#else + saa7134_i2c_call_clients(dev,VIDIOCSFREQ,&dev->ctl_freq); +#endif + saa7134_tvaudio_do_scan(dev); + up(&dev->lock); + return 0; + } + + /* --- control ioctls ---------------------------------------- */ + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + return saa7134_common_ioctl(dev, cmd, arg); + + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + memset(a,0,sizeof(*a)); + strcpy(a->name,"audio"); + return 0; + } + case VIDIOC_S_AUDIO: + return 0; + case VIDIOC_G_PARM: + { + struct v4l2_captureparm *parm = arg; + memset(parm,0,sizeof(*parm)); + return 0; + } + + case VIDIOC_G_PRIORITY: + { + enum v4l2_priority *p = arg; + + *p = v4l2_prio_max(&dev->prio); + return 0; + } + case VIDIOC_S_PRIORITY: + { + enum v4l2_priority *prio = arg; + + return v4l2_prio_change(&dev->prio, &fh->prio, *prio); + } + + /* --- preview ioctls ---------------------------------------- */ + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *f = arg; + enum v4l2_buf_type type; + unsigned int index; + + index = f->index; + type = f->type; + switch (type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + if (index >= FORMATS) + return -EINVAL; + if (f->type == V4L2_BUF_TYPE_VIDEO_OVERLAY && + formats[index].planar) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + strlcpy(f->description,formats[index].name,sizeof(f->description)); + f->pixelformat = formats[index].fourcc; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (0 != index) + return -EINVAL; + memset(f,0,sizeof(*f)); + f->index = index; + f->type = type; + f->pixelformat = V4L2_PIX_FMT_GREY; + strcpy(f->description,"vbi data"); + break; + default: + return -EINVAL; + } + return 0; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + *fb = dev->ovbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + return 0; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + struct saa7134_format *fmt; + + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (NULL == fmt) + return -EINVAL; + + /* ok, accept it */ + dev->ovbuf = *fb; + dev->ovfmt = fmt; + if (0 == dev->ovbuf.fmt.bytesperline) + dev->ovbuf.fmt.bytesperline = + dev->ovbuf.fmt.width*fmt->depth/8; + return 0; + } + case VIDIOC_OVERLAY: + { + int *on = arg; + + if (*on) { + if (!res_get(dev,fh,RESOURCE_OVERLAY)) + return -EBUSY; + spin_lock_irqsave(&dev->slock,flags); + start_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + } + if (!*on) { + if (!res_check(fh, RESOURCE_OVERLAY)) + return -EINVAL; + spin_lock_irqsave(&dev->slock,flags); + stop_preview(dev,fh); + spin_unlock_irqrestore(&dev->slock,flags); + res_free(dev,fh,RESOURCE_OVERLAY); + } + return 0; + } + + /* --- capture ioctls ---------------------------------------- */ + case VIDIOC_G_FMT: + { + struct v4l2_format *f = arg; + return saa7134_g_fmt(dev,fh,f); + } + case VIDIOC_S_FMT: + { + struct v4l2_format *f = arg; + return saa7134_s_fmt(dev,fh,f); + } + case VIDIOC_TRY_FMT: + { + struct v4l2_format *f = arg; + return saa7134_try_fmt(dev,fh,f); + } + + case VIDIOCGMBUF: + { + struct video_mbuf *mbuf = arg; + struct videobuf_queue *q; + struct v4l2_requestbuffers req; + unsigned int i; + + q = saa7134_queue(fh); + memset(&req,0,sizeof(req)); + req.type = q->type; + req.count = gbuffers; + req.memory = V4L2_MEMORY_MMAP; + err = videobuf_reqbufs(q,&req); + if (err < 0) + return err; + memset(mbuf,0,sizeof(*mbuf)); + mbuf->frames = req.count; + mbuf->size = 0; + for (i = 0; i < mbuf->frames; i++) { + mbuf->offsets[i] = q->bufs[i]->boff; + mbuf->size += q->bufs[i]->bsize; + } + return 0; + } + case VIDIOC_REQBUFS: + return videobuf_reqbufs(saa7134_queue(fh),arg); + + case VIDIOC_QUERYBUF: + return videobuf_querybuf(saa7134_queue(fh),arg); + + case VIDIOC_QBUF: + return videobuf_qbuf(saa7134_queue(fh),arg); + + case VIDIOC_DQBUF: + return videobuf_dqbuf(saa7134_queue(fh),arg, + file->f_flags & O_NONBLOCK); + + case VIDIOC_STREAMON: + { + int res = saa7134_resource(fh); + + if (!res_get(dev,fh,res)) + return -EBUSY; + return videobuf_streamon(saa7134_queue(fh)); + } + case VIDIOC_STREAMOFF: + { + int res = saa7134_resource(fh); + + err = videobuf_streamoff(saa7134_queue(fh)); + if (err < 0) + return err; + res_free(dev,fh,res); + return 0; + } + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + video_do_ioctl); + } + return 0; +} + +static int video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, video_do_ioctl); +} + +static int radio_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct saa7134_fh *fh = file->private_data; + struct saa7134_dev *dev = fh->dev; + + if (video_debug > 1) + saa7134_print_ioctl(dev->name,cmd); + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memset(cap,0,sizeof(*cap)); + strcpy(cap->driver, "saa7134"); + strlcpy(cap->card, saa7134_boards[dev->board].name, + sizeof(cap->card)); + sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci)); + cap->version = SAA7134_VERSION_CODE; + cap->capabilities = V4L2_CAP_TUNER; + return 0; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + + if (0 != t->index) + return -EINVAL; + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Radio"); + t->rangelow = (int)(65*16); + t->rangehigh = (int)(108*16); + +#ifdef V4L2_I2C_CLIENTS + saa7134_i2c_call_clients(dev,VIDIOC_G_TUNER,t); +#else + { + struct video_tuner vt; + memset(&vt,0,sizeof(vt)); + saa7134_i2c_call_clients(dev,VIDIOCGTUNER,&vt); + t->signal = vt.signal; + } +#endif + return 0; + } + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + strcpy(i->name,"Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + return 0; + } + case VIDIOC_G_INPUT: + { + int *i = arg; + *i = 0; + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + memset(a,0,sizeof(*a)); + strcpy(a->name,"Radio"); + return 0; + } + case VIDIOC_G_STD: + { + v4l2_std_id *id = arg; + *id = 0; + return 0; + } + case VIDIOC_S_AUDIO: + case VIDIOC_S_TUNER: + case VIDIOC_S_INPUT: + case VIDIOC_S_STD: + return 0; + + case VIDIOC_QUERYCTRL: + { + const struct v4l2_queryctrl *ctrl; + struct v4l2_queryctrl *c = arg; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + if (c->id == V4L2_CID_AUDIO_MUTE) { + ctrl = ctrl_by_id(c->id); + *c = *ctrl; + } else + *c = no_ctrl; + return 0; + } + + case VIDIOC_G_CTRL: + case VIDIOC_S_CTRL: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + return video_do_ioctl(inode,file,cmd,arg); + + default: + return v4l_compat_translate_ioctl(inode,file,cmd,arg, + radio_do_ioctl); + } + return 0; +} + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, radio_do_ioctl); +} + +static struct file_operations video_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .read = video_read, + .poll = video_poll, + .mmap = video_mmap, + .ioctl = video_ioctl, + .llseek = no_llseek, +}; + +static struct file_operations radio_fops = +{ + .owner = THIS_MODULE, + .open = video_open, + .release = video_release, + .ioctl = radio_ioctl, + .llseek = no_llseek, +}; + +/* ----------------------------------------------------------- */ +/* exported stuff */ + +struct video_device saa7134_video_template = +{ + .name = "saa7134-video", + .type = VID_TYPE_CAPTURE|VID_TYPE_TUNER|VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING|VID_TYPE_SCALES, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +struct video_device saa7134_vbi_template = +{ + .name = "saa7134-vbi", + .type = VID_TYPE_TUNER|VID_TYPE_TELETEXT, + .hardware = 0, + .fops = &video_fops, + .minor = -1, +}; + +struct video_device saa7134_radio_template = +{ + .name = "saa7134-radio", + .type = VID_TYPE_TUNER, + .hardware = 0, + .fops = &radio_fops, + .minor = -1, +}; + +int saa7134_video_init1(struct saa7134_dev *dev) +{ + /* sanitycheck insmod options */ + if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) + gbuffers = 2; + if (gbufsize < 0 || gbufsize > gbufsize_max) + gbufsize = gbufsize_max; + gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; + + /* put some sensible defaults into the data structures ... */ + dev->ctl_bright = ctrl_by_id(V4L2_CID_BRIGHTNESS)->default_value; + dev->ctl_contrast = ctrl_by_id(V4L2_CID_CONTRAST)->default_value; + dev->ctl_hue = ctrl_by_id(V4L2_CID_HUE)->default_value; + dev->ctl_saturation = ctrl_by_id(V4L2_CID_SATURATION)->default_value; + dev->ctl_volume = ctrl_by_id(V4L2_CID_AUDIO_VOLUME)->default_value; + dev->ctl_mute = 1; // ctrl_by_id(V4L2_CID_AUDIO_MUTE)->default_value; + dev->ctl_invert = ctrl_by_id(V4L2_CID_PRIVATE_INVERT)->default_value; + dev->ctl_automute = ctrl_by_id(V4L2_CID_PRIVATE_AUTOMUTE)->default_value; + + if (dev->tda9887_conf && dev->ctl_automute) + dev->tda9887_conf |= TDA9887_AUTOMUTE; + dev->automute = 0; + + INIT_LIST_HEAD(&dev->video_q.queue); + init_timer(&dev->video_q.timeout); + dev->video_q.timeout.function = saa7134_buffer_timeout; + dev->video_q.timeout.data = (unsigned long)(&dev->video_q); + dev->video_q.dev = dev; + + if (saa7134_boards[dev->board].video_out) { + /* enable video output */ + int vo = saa7134_boards[dev->board].video_out; + saa_writeb(SAA7134_VIDEO_PORT_CTRL0, video_out[vo][0]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL1, video_out[vo][1]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL2, video_out[vo][2]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL3, video_out[vo][3]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL4, video_out[vo][4]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL5, video_out[vo][5]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL6, video_out[vo][6]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL7, video_out[vo][7]); + saa_writeb(SAA7134_VIDEO_PORT_CTRL8, video_out[vo][8]); + } + + return 0; +} + +int saa7134_video_init2(struct saa7134_dev *dev) +{ + /* init video hw */ + set_tvnorm(dev,&tvnorms[0]); + video_mux(dev,0); + saa7134_tvaudio_setmute(dev); + saa7134_tvaudio_setvolume(dev,dev->ctl_volume); + 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[] = { + "(no signal)", "NTSC", "PAL", "SECAM" }; + u32 st1,st2; + + st1 = saa_readb(SAA7134_STATUS_VIDEO1); + st2 = saa_readb(SAA7134_STATUS_VIDEO2); + dprintk("DCSDT: pll: %s, sync: %s, norm: %s\n", + (st1 & 0x40) ? "not locked" : "locked", + (st2 & 0x40) ? "no" : "yes", + st[st1 & 0x03]); + dev->nosignal = (st1 & 0x40) || (st2 & 0x40); + + if (dev->nosignal) { + /* no video signal -> mute audio */ + if (dev->ctl_automute) + dev->automute = 1; + saa7134_tvaudio_setmute(dev); + saa_setb(SAA7134_SYNC_CTRL, 0x20); + } else { + /* wake up tvaudio audio carrier scan thread */ + saa7134_tvaudio_do_scan(dev); + if (!noninterlaced) + saa_clearb(SAA7134_SYNC_CTRL, 0x20); + } + if (dev->mops && dev->mops->signal_change) + dev->mops->signal_change(dev); +} + +void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status) +{ + enum v4l2_field field; + + spin_lock(&dev->slock); + if (dev->video_q.curr) { + dev->video_fieldcount++; + field = dev->video_q.curr->vb.field; + if (V4L2_FIELD_HAS_BOTH(field)) { + /* make sure we have seen both fields */ + if ((status & 0x10) == 0x00) { + dev->video_q.curr->top_seen = 1; + goto done; + } + if (!dev->video_q.curr->top_seen) + goto done; + } else if (field == V4L2_FIELD_TOP) { + if ((status & 0x10) != 0x10) + goto done; + } else if (field == V4L2_FIELD_BOTTOM) { + if ((status & 0x10) != 0x00) + goto done; + } + dev->video_q.curr->vb.field_count = dev->video_fieldcount; + saa7134_buffer_finish(dev,&dev->video_q,STATE_DONE); + } + saa7134_buffer_next(dev,&dev->video_q); + + done: + spin_unlock(&dev->slock); +} + +/* ----------------------------------------------------------- */ +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7134/saa7134.h b/drivers/media/video/saa7134/saa7134.h new file mode 100644 index 000000000000..ac90a9853236 --- /dev/null +++ b/drivers/media/video/saa7134/saa7134.h @@ -0,0 +1,618 @@ +/* + * $Id: saa7134.h,v 1.38 2005/03/07 12:01:51 kraxel Exp $ + * + * v4l2 device driver for philips saa7134 based TV cards + * + * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/version.h> +#define SAA7134_VERSION_CODE KERNEL_VERSION(0,2,12) + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/kdev_t.h> +#include <linux/input.h> + +#include <asm/io.h> + +#include <media/tuner.h> +#include <media/audiochip.h> +#include <media/id.h> +#include <media/ir-common.h> +#include <media/video-buf.h> +#include <media/video-buf-dvb.h> + +#ifndef TRUE +# define TRUE (1==1) +#endif +#ifndef FALSE +# define FALSE (1==0) +#endif +#define UNSET (-1U) + +/* 2.4 / 2.5 driver compatibility stuff */ + +/* ----------------------------------------------------------- */ +/* enums */ + +enum saa7134_tvaudio_mode { + TVAUDIO_FM_MONO = 1, + TVAUDIO_FM_BG_STEREO = 2, + TVAUDIO_FM_SAT_STEREO = 3, + TVAUDIO_FM_K_STEREO = 4, + TVAUDIO_NICAM_AM = 5, + TVAUDIO_NICAM_FM = 6, +}; + +enum saa7134_audio_in { + TV = 1, + LINE1 = 2, + LINE2 = 3, + LINE2_LEFT, +}; + +enum saa7134_video_out { + CCIR656 = 1, +}; + +/* ----------------------------------------------------------- */ +/* static data */ + +struct saa7134_tvnorm { + char *name; + v4l2_std_id id; + + /* video decoder */ + unsigned int sync_control; + unsigned int luma_control; + unsigned int chroma_ctrl1; + unsigned int chroma_gain; + unsigned int chroma_ctrl2; + unsigned int vgate_misc; + + /* video scaler */ + unsigned int h_start; + unsigned int h_stop; + unsigned int video_v_start; + unsigned int video_v_stop; + unsigned int vbi_v_start; + unsigned int vbi_v_stop; + unsigned int src_timing; +}; + +struct saa7134_tvaudio { + char *name; + v4l2_std_id std; + enum saa7134_tvaudio_mode mode; + int carr1; + int carr2; +}; + +struct saa7134_format { + char *name; + unsigned int fourcc; + unsigned int depth; + unsigned int pm; + unsigned int vshift; /* vertical downsampling (for planar yuv) */ + unsigned int hshift; /* horizontal downsampling (for planar yuv) */ + unsigned int bswap:1; + unsigned int wswap:1; + unsigned int yuv:1; + unsigned int planar:1; + unsigned int uvswap:1; +}; + +/* ----------------------------------------------------------- */ +/* card configuration */ + +#define SAA7134_BOARD_NOAUTO UNSET +#define SAA7134_BOARD_UNKNOWN 0 +#define SAA7134_BOARD_PROTEUS_PRO 1 +#define SAA7134_BOARD_FLYVIDEO3000 2 +#define SAA7134_BOARD_FLYVIDEO2000 3 +#define SAA7134_BOARD_EMPRESS 4 +#define SAA7134_BOARD_MONSTERTV 5 +#define SAA7134_BOARD_MD9717 6 +#define SAA7134_BOARD_TVSTATION_RDS 7 +#define SAA7134_BOARD_CINERGY400 8 +#define SAA7134_BOARD_MD5044 9 +#define SAA7134_BOARD_KWORLD 10 +#define SAA7134_BOARD_CINERGY600 11 +#define SAA7134_BOARD_MD7134 12 +#define SAA7134_BOARD_TYPHOON_90031 13 +#define SAA7134_BOARD_ELSA 14 +#define SAA7134_BOARD_ELSA_500TV 15 +#define SAA7134_BOARD_ASUSTeK_TVFM7134 16 +#define SAA7134_BOARD_VA1000POWER 17 +#define SAA7134_BOARD_BMK_MPEX_NOTUNER 18 +#define SAA7134_BOARD_VIDEOMATE_TV 19 +#define SAA7134_BOARD_CRONOS_PLUS 20 +#define SAA7134_BOARD_10MOONSTVMASTER 21 +#define SAA7134_BOARD_MD2819 22 +#define SAA7134_BOARD_BMK_MPEX_TUNER 23 +#define SAA7134_BOARD_TVSTATION_DVR 24 +#define SAA7134_BOARD_ASUSTEK_TVFM7133 25 +#define SAA7134_BOARD_PINNACLE_PCTV_STEREO 26 +#define SAA7134_BOARD_MANLI_MTV002 27 +#define SAA7134_BOARD_MANLI_MTV001 28 +#define SAA7134_BOARD_TG3000TV 29 +#define SAA7134_BOARD_ECS_TVP3XP 30 +#define SAA7134_BOARD_ECS_TVP3XP_4CB5 31 +#define SAA7134_BOARD_AVACSSMARTTV 32 +#define SAA7134_BOARD_AVERMEDIA_DVD_EZMAKER 33 +#define SAA7134_BOARD_NOVAC_PRIMETV7133 34 +#define SAA7134_BOARD_AVERMEDIA_STUDIO_305 35 +#define SAA7133_BOARD_UPMOST_PURPLE_TV 36 +#define SAA7134_BOARD_ITEMS_MTV005 37 +#define SAA7134_BOARD_CINERGY200 38 +#define SAA7134_BOARD_FLYTVPLATINUM_MINI 39 +#define SAA7134_BOARD_VIDEOMATE_TV_PVR 40 +#define SAA7134_BOARD_VIDEOMATE_TV_GOLD_PLUS 41 +#define SAA7134_BOARD_SABRENT_SBTTVFM 42 +#define SAA7134_BOARD_ZOLID_XPERT_TV7134 43 +#define SAA7134_BOARD_EMPIRE_PCI_TV_RADIO_LE 44 +#define SAA7134_BOARD_AVERMEDIA_307 45 +#define SAA7134_BOARD_AVERMEDIA_CARDBUS 46 +#define SAA7134_BOARD_CINERGY400_CARDBUS 47 +#define SAA7134_BOARD_CINERGY600_MK3 48 +#define SAA7134_BOARD_VIDEOMATE_GOLD_PLUS 49 +#define SAA7134_BOARD_PINNACLE_300I_DVBT_PAL 50 +#define SAA7134_BOARD_PROVIDEO_PV952 51 +#define SAA7134_BOARD_AVERMEDIA_305 52 +#define SAA7135_BOARD_ASUSTeK_TVFM7135 53 +#define SAA7134_BOARD_FLYTVPLATINUM_FM 54 +#define SAA7134_BOARD_FLYDVBTDUO 55 + +#define SAA7134_MAXBOARDS 8 +#define SAA7134_INPUT_MAX 8 + +struct saa7134_input { + char *name; + unsigned int vmux; + enum saa7134_audio_in amux; + unsigned int gpio; + unsigned int tv:1; +}; + +enum saa7134_mpeg_type { + SAA7134_MPEG_UNUSED, + SAA7134_MPEG_EMPRESS, + SAA7134_MPEG_DVB, +}; + +struct saa7134_board { + char *name; + unsigned int audio_clock; + + /* input switching */ + unsigned int gpiomask; + struct saa7134_input inputs[SAA7134_INPUT_MAX]; + struct saa7134_input radio; + struct saa7134_input mute; + + /* i2c chip info */ + unsigned int tuner_type; + unsigned int tda9887_conf; + + /* peripheral I/O */ + enum saa7134_video_out video_out; + enum saa7134_mpeg_type mpeg; +}; + +#define card_has_radio(dev) (NULL != saa7134_boards[dev->board].radio.name) +#define card_is_empress(dev) (SAA7134_MPEG_EMPRESS == saa7134_boards[dev->board].mpeg) +#define card_is_dvb(dev) (SAA7134_MPEG_DVB == saa7134_boards[dev->board].mpeg) +#define card_has_mpeg(dev) (SAA7134_MPEG_UNUSED != saa7134_boards[dev->board].mpeg) +#define card(dev) (saa7134_boards[dev->board]) +#define card_in(dev,n) (saa7134_boards[dev->board].inputs[n]) + +/* ----------------------------------------------------------- */ +/* device / file handle status */ + +#define RESOURCE_OVERLAY 1 +#define RESOURCE_VIDEO 2 +#define RESOURCE_VBI 4 + +#define INTERLACE_AUTO 0 +#define INTERLACE_ON 1 +#define INTERLACE_OFF 2 + +#define BUFFER_TIMEOUT (HZ/2) /* 0.5 seconds */ + +struct saa7134_dev; +struct saa7134_dma; + +/* saa7134 page table */ +struct saa7134_pgtable { + unsigned int size; + u32 *cpu; + dma_addr_t dma; +}; + +/* tvaudio thread status */ +struct saa7134_thread { + pid_t pid; + struct completion exit; + wait_queue_head_t wq; + unsigned int shutdown; + unsigned int scan1; + unsigned int scan2; + unsigned int mode; +}; + +/* buffer for one video/vbi/ts frame */ +struct saa7134_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + /* saa7134 specific */ + struct saa7134_format *fmt; + unsigned int top_seen; + int (*activate)(struct saa7134_dev *dev, + struct saa7134_buf *buf, + struct saa7134_buf *next); + + /* page tables */ + struct saa7134_pgtable *pt; +}; + +struct saa7134_dmaqueue { + struct saa7134_dev *dev; + struct saa7134_buf *curr; + struct list_head queue; + struct timer_list timeout; + unsigned int need_two; +}; + +/* video filehandle status */ +struct saa7134_fh { + struct saa7134_dev *dev; + unsigned int radio; + enum v4l2_buf_type type; + unsigned int resources; +#ifdef VIDIOC_G_PRIORITY + enum v4l2_priority prio; +#endif + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip clips[8]; + unsigned int nclips; + + /* video capture */ + struct saa7134_format *fmt; + unsigned int width,height; + struct videobuf_queue cap; + struct saa7134_pgtable pt_cap; + + /* vbi capture */ + struct videobuf_queue vbi; + struct saa7134_pgtable pt_vbi; +}; + +/* oss dsp status */ +struct saa7134_oss { + struct semaphore lock; + int minor_mixer; + int minor_dsp; + unsigned int users_dsp; + + /* mixer */ + enum saa7134_audio_in input; + unsigned int count; + unsigned int line1; + unsigned int line2; + + /* dsp */ + unsigned int afmt; + unsigned int rate; + unsigned int channels; + unsigned int recording_on; + unsigned int dma_running; + unsigned int blocks; + unsigned int blksize; + unsigned int bufsize; + struct saa7134_pgtable pt; + struct videobuf_dmabuf dma; + wait_queue_head_t wq; + unsigned int dma_blk; + unsigned int read_offset; + unsigned int read_count; +}; + +/* IR input */ +struct saa7134_ir { + struct input_dev dev; + struct ir_input_state ir; + char name[32]; + char phys[32]; + u32 mask_keycode; + u32 mask_keydown; + u32 mask_keyup; + int polling; + u32 last_gpio; + struct timer_list timer; +}; + +/* ts/mpeg status */ +struct saa7134_ts { + /* TS capture */ + struct saa7134_pgtable pt_ts; + int nr_packets; + int nr_bufs; +}; + +/* ts/mpeg ops */ +struct saa7134_mpeg_ops { + enum saa7134_mpeg_type type; + struct list_head next; + int (*init)(struct saa7134_dev *dev); + int (*fini)(struct saa7134_dev *dev); + void (*signal_change)(struct saa7134_dev *dev); +}; + +/* global device status */ +struct saa7134_dev { + struct list_head devlist; + struct semaphore lock; + spinlock_t slock; +#ifdef VIDIOC_G_PRIORITY + struct v4l2_prio_state prio; +#endif + + /* various device info */ + unsigned int resources; + struct video_device *video_dev; + struct video_device *radio_dev; + struct video_device *vbi_dev; + struct saa7134_oss oss; + + /* infrared remote */ + int has_remote; + struct saa7134_ir *remote; + + /* pci i/o */ + char name[32]; + int nr; + struct pci_dev *pci; + unsigned char pci_rev,pci_lat; + __u32 __iomem *lmmio; + __u8 __iomem *bmmio; + + /* config info */ + unsigned int board; + unsigned int tuner_type; + unsigned int tda9887_conf; + unsigned int gpio_value; + unsigned int irq2_mask; + + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + unsigned char eedata[64]; + + /* video overlay */ + struct v4l2_framebuffer ovbuf; + struct saa7134_format *ovfmt; + unsigned int ovenable; + enum v4l2_field ovfield; + + /* video+ts+vbi capture */ + struct saa7134_dmaqueue video_q; + struct saa7134_dmaqueue vbi_q; + unsigned int video_fieldcount; + unsigned int vbi_fieldcount; + + /* various v4l controls */ + struct saa7134_tvnorm *tvnorm; /* video */ + struct saa7134_tvaudio *tvaudio; + unsigned int ctl_input; + int ctl_bright; + int ctl_contrast; + int ctl_hue; + int ctl_saturation; + int ctl_freq; + int ctl_mute; /* audio */ + int ctl_volume; + int ctl_invert; /* private */ + int ctl_mirror; + int ctl_y_odd; + int ctl_y_even; + int ctl_automute; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + /* other global state info */ + unsigned int automute; + struct saa7134_thread thread; + struct saa7134_input *input; + struct saa7134_input *hw_input; + unsigned int hw_mute; + int last_carrier; + int nosignal; + + /* SAA7134_MPEG_* */ + struct saa7134_ts ts; + struct saa7134_dmaqueue ts_q; + struct saa7134_mpeg_ops *mops; + + /* SAA7134_MPEG_EMPRESS only */ + struct video_device *empress_dev; + struct videobuf_queue empress_tsq; + unsigned int empress_users; + struct work_struct empress_workqueue; + int empress_started; + + /* SAA7134_MPEG_DVB only */ + struct videobuf_dvb dvb; +}; + +/* ----------------------------------------------------------- */ + +#define saa_readl(reg) readl(dev->lmmio + (reg)) +#define saa_writel(reg,value) writel((value), dev->lmmio + (reg)); +#define saa_andorl(reg,mask,value) \ + writel((readl(dev->lmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->lmmio+(reg)) +#define saa_setl(reg,bit) saa_andorl((reg),(bit),(bit)) +#define saa_clearl(reg,bit) saa_andorl((reg),(bit),0) + +#define saa_readb(reg) readb(dev->bmmio + (reg)) +#define saa_writeb(reg,value) writeb((value), dev->bmmio + (reg)); +#define saa_andorb(reg,mask,value) \ + writeb((readb(dev->bmmio+(reg)) & ~(mask)) |\ + ((value) & (mask)), dev->bmmio+(reg)) +#define saa_setb(reg,bit) saa_andorb((reg),(bit),(bit)) +#define saa_clearb(reg,bit) saa_andorb((reg),(bit),0) + +#define saa_wait(us) { udelay(us); } + +/* ----------------------------------------------------------- */ +/* saa7134-core.c */ + +extern struct list_head saa7134_devlist; + +void saa7134_print_ioctl(char *name, unsigned int cmd); +void saa7134_track_gpio(struct saa7134_dev *dev, char *msg); + +#define SAA7134_PGTABLE_SIZE 4096 + +int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt); +int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt, + struct scatterlist *list, unsigned int length, + unsigned int startpage); +void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt); + +int saa7134_buffer_count(unsigned int size, unsigned int count); +int saa7134_buffer_startpage(struct saa7134_buf *buf); +unsigned long saa7134_buffer_base(struct saa7134_buf *buf); + +int saa7134_buffer_queue(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + struct saa7134_buf *buf); +void saa7134_buffer_finish(struct saa7134_dev *dev, struct saa7134_dmaqueue *q, + unsigned int state); +void saa7134_buffer_next(struct saa7134_dev *dev, struct saa7134_dmaqueue *q); +void saa7134_buffer_timeout(unsigned long data); +void saa7134_dma_free(struct saa7134_dev *dev,struct saa7134_buf *buf); + +int saa7134_set_dmabits(struct saa7134_dev *dev); + +/* ----------------------------------------------------------- */ +/* saa7134-cards.c */ + +extern struct saa7134_board saa7134_boards[]; +extern const unsigned int saa7134_bcount; +extern struct pci_device_id __devinitdata saa7134_pci_tbl[]; + +extern int saa7134_board_init1(struct saa7134_dev *dev); +extern int saa7134_board_init2(struct saa7134_dev *dev); + + +/* ----------------------------------------------------------- */ +/* saa7134-i2c.c */ + +int saa7134_i2c_register(struct saa7134_dev *dev); +int saa7134_i2c_unregister(struct saa7134_dev *dev); +void saa7134_i2c_call_clients(struct saa7134_dev *dev, + unsigned int cmd, void *arg); + + +/* ----------------------------------------------------------- */ +/* saa7134-video.c */ + +extern struct video_device saa7134_video_template; +extern struct video_device saa7134_radio_template; + +int saa7134_common_ioctl(struct saa7134_dev *dev, + unsigned int cmd, void *arg); + +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); + + +/* ----------------------------------------------------------- */ +/* saa7134-ts.c */ + +#define TS_PACKET_SIZE 188 /* TS packets 188 bytes */ + +extern struct videobuf_queue_ops saa7134_ts_qops; + +int saa7134_ts_init1(struct saa7134_dev *dev); +int saa7134_ts_fini(struct saa7134_dev *dev); +void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status); + +int saa7134_ts_register(struct saa7134_mpeg_ops *ops); +void saa7134_ts_unregister(struct saa7134_mpeg_ops *ops); + +/* ----------------------------------------------------------- */ +/* saa7134-vbi.c */ + +extern struct videobuf_queue_ops saa7134_vbi_qops; +extern struct video_device saa7134_vbi_template; + +int saa7134_vbi_init1(struct saa7134_dev *dev); +int saa7134_vbi_fini(struct saa7134_dev *dev); +void saa7134_irq_vbi_done(struct saa7134_dev *dev, unsigned long status); + + +/* ----------------------------------------------------------- */ +/* saa7134-tvaudio.c */ + +int saa7134_tvaudio_rx2mode(u32 rx); + +void saa7134_tvaudio_setmute(struct saa7134_dev *dev); +void saa7134_tvaudio_setinput(struct saa7134_dev *dev, + struct saa7134_input *in); +void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level); +int saa7134_tvaudio_getstereo(struct saa7134_dev *dev); + +int saa7134_tvaudio_init2(struct saa7134_dev *dev); +int saa7134_tvaudio_fini(struct saa7134_dev *dev); +int saa7134_tvaudio_do_scan(struct saa7134_dev *dev); + +int saa_dsp_writel(struct saa7134_dev *dev, int reg, u32 value); + +/* ----------------------------------------------------------- */ +/* saa7134-oss.c */ + +extern struct file_operations saa7134_dsp_fops; +extern struct file_operations saa7134_mixer_fops; + +int saa7134_oss_init1(struct saa7134_dev *dev); +int saa7134_oss_fini(struct saa7134_dev *dev); +void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status); + +/* ----------------------------------------------------------- */ +/* saa7134-input.c */ + +int saa7134_input_init1(struct saa7134_dev *dev); +void saa7134_input_fini(struct saa7134_dev *dev); +void saa7134_input_irq(struct saa7134_dev *dev); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/saa7146.h b/drivers/media/video/saa7146.h new file mode 100644 index 000000000000..f305ec802ea6 --- /dev/null +++ b/drivers/media/video/saa7146.h @@ -0,0 +1,115 @@ +/* + saa7146.h - definitions philips saa7146 based cards + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + 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. +*/ + +#ifndef __SAA7146__ +#define __SAA7146__ + +#define SAA7146_VERSION_CODE 0x000101 + +#include <linux/types.h> +#include <linux/wait.h> + +#include <linux/videodev.h> + +#ifndef O_NONCAP +#define O_NONCAP O_TRUNC +#endif + +#define MAX_GBUFFERS 2 +#define FBUF_SIZE 0x190000 + +#ifdef __KERNEL__ + +struct saa7146_window +{ + int x, y; + ushort width, height; + ushort bpp, bpl; + ushort swidth, sheight; + short cropx, cropy; + ushort cropwidth, cropheight; + unsigned long vidadr; + int color_fmt; + ushort depth; +}; + +/* Per-open data for handling multiple opens on one device */ +struct device_open +{ + int isopen; + int noncapturing; + struct saa7146 *dev; +}; +#define MAX_OPENS 3 + +struct saa7146 +{ + struct video_device video_dev; + struct video_picture picture; + struct video_audio audio_dev; + struct video_info vidinfo; + int user; + int cap; + int capuser; + int irqstate; /* irq routine is state driven */ + int writemode; + int playmode; + unsigned int nr; + unsigned long irq; /* IRQ used by SAA7146 card */ + unsigned short id; + struct pci_dev *dev; + unsigned char revision; + unsigned char boardcfg[64]; /* 64 bytes of config from eeprom */ + unsigned long saa7146_adr; /* bus address of IO mem from PCI BIOS */ + struct saa7146_window win; + unsigned char __iomem *saa7146_mem; /* pointer to mapped IO memory */ + struct device_open open_data[MAX_OPENS]; +#define MAX_MARKS 16 + /* for a/v sync */ + int endmark[MAX_MARKS], endmarkhead, endmarktail; + u32 *dmaRPS1, *pageRPS1, *dmaRPS2, *pageRPS2, *dmavid1, *dmavid2, + *dmavid3, *dmaa1in, *dmaa1out, *dmaa2in, *dmaa2out, + *pagedebi, *pagevid1, *pagevid2, *pagevid3, *pagea1in, + *pagea1out, *pagea2in, *pagea2out; + wait_queue_head_t i2cq, debiq, audq, vidq; + u8 *vidbuf, *audbuf, *osdbuf, *dmadebi; + int audhead, vidhead, osdhead, audtail, vidtail, osdtail; + spinlock_t lock; /* the device lock */ +}; +#endif + +#ifdef _ALPHA_SAA7146 +#define saawrite(dat,adr) writel((dat), saa->saa7146_adr+(adr)) +#define saaread(adr) readl(saa->saa7146_adr+(adr)) +#else +#define saawrite(dat,adr) writel((dat), saa->saa7146_mem+(adr)) +#define saaread(adr) readl(saa->saa7146_mem+(adr)) +#endif + +#define saaand(dat,adr) saawrite((dat) & saaread(adr), adr) +#define saaor(dat,adr) saawrite((dat) | saaread(adr), adr) +#define saaaor(dat,mask,adr) saawrite((dat) | ((mask) & saaread(adr)), adr) + +/* bitmask of attached hardware found */ +#define SAA7146_UNKNOWN 0x00000000 +#define SAA7146_SAA7111 0x00000001 +#define SAA7146_SAA7121 0x00000002 +#define SAA7146_IBMMPEG 0x00000004 + +#endif diff --git a/drivers/media/video/saa7146reg.h b/drivers/media/video/saa7146reg.h new file mode 100644 index 000000000000..6cc910f50a4b --- /dev/null +++ b/drivers/media/video/saa7146reg.h @@ -0,0 +1,283 @@ +/* + saa7146.h - definitions philips saa7146 based cards + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + 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. +*/ + +#ifndef __SAA7146_REG__ +#define __SAA7146_REG__ +#define SAA7146_BASE_ODD1 0x00 +#define SAA7146_BASE_EVEN1 0x04 +#define SAA7146_PROT_ADDR1 0x08 +#define SAA7146_PITCH1 0x0c +#define SAA7146_PAGE1 0x10 +#define SAA7146_NUM_LINE_BYTE1 0x14 +#define SAA7146_BASE_ODD2 0x18 +#define SAA7146_BASE_EVEN2 0x1c +#define SAA7146_PROT_ADDR2 0x20 +#define SAA7146_PITCH2 0x24 +#define SAA7146_PAGE2 0x28 +#define SAA7146_NUM_LINE_BYTE2 0x2c +#define SAA7146_BASE_ODD3 0x30 +#define SAA7146_BASE_EVEN3 0x34 +#define SAA7146_PROT_ADDR3 0x38 +#define SAA7146_PITCH3 0x3c +#define SAA7146_PAGE3 0x40 +#define SAA7146_NUM_LINE_BYTE3 0x44 +#define SAA7146_PCI_BT_V1 0x48 +#define SAA7146_PCI_BT_V2 0x49 +#define SAA7146_PCI_BT_V3 0x4a +#define SAA7146_PCI_BT_DEBI 0x4b +#define SAA7146_PCI_BT_A 0x4c +#define SAA7146_DD1_INIT 0x50 +#define SAA7146_DD1_STREAM_B 0x54 +#define SAA7146_DD1_STREAM_A 0x56 +#define SAA7146_BRS_CTRL 0x58 +#define SAA7146_HPS_CTRL 0x5c +#define SAA7146_HPS_V_SCALE 0x60 +#define SAA7146_HPS_V_GAIN 0x64 +#define SAA7146_HPS_H_PRESCALE 0x68 +#define SAA7146_HPS_H_SCALE 0x6c +#define SAA7146_BCS_CTRL 0x70 +#define SAA7146_CHROMA_KEY_RANGE 0x74 +#define SAA7146_CLIP_FORMAT_CTRL 0x78 +#define SAA7146_DEBI_CONFIG 0x7c +#define SAA7146_DEBI_COMMAND 0x80 +#define SAA7146_DEBI_PAGE 0x84 +#define SAA7146_DEBI_AD 0x88 +#define SAA7146_I2C_TRANSFER 0x8c +#define SAA7146_I2C_STATUS 0x90 +#define SAA7146_BASE_A1_IN 0x94 +#define SAA7146_PROT_A1_IN 0x98 +#define SAA7146_PAGE_A1_IN 0x9C +#define SAA7146_BASE_A1_OUT 0xa0 +#define SAA7146_PROT_A1_OUT 0xa4 +#define SAA7146_PAGE_A1_OUT 0xa8 +#define SAA7146_BASE_A2_IN 0xac +#define SAA7146_PROT_A2_IN 0xb0 +#define SAA7146_PAGE_A2_IN 0xb4 +#define SAA7146_BASE_A2_OUT 0xb8 +#define SAA7146_PROT_A2_OUT 0xbc +#define SAA7146_PAGE_A2_OUT 0xc0 +#define SAA7146_RPS_PAGE0 0xc4 +#define SAA7146_RPS_PAGE1 0xc8 +#define SAA7146_RPS_THRESH0 0xcc +#define SAA7146_RPS_THRESH1 0xd0 +#define SAA7146_RPS_TOV0 0xd4 +#define SAA7146_RPS_TOV1 0xd8 +#define SAA7146_IER 0xdc +#define SAA7146_GPIO_CTRL 0xe0 +#define SAA7146_EC1SSR 0xe4 +#define SAA7146_EC2SSR 0xe8 +#define SAA7146_ECT1R 0xec +#define SAA7146_ECT2R 0xf0 +#define SAA7146_ACON1 0xf4 +#define SAA7146_ACON2 0xf8 +#define SAA7146_MC1 0xfc +#define SAA7146_MC2 0x100 +#define SAA7146_RPS_ADDR0 0x104 +#define SAA7146_RPS_ADDR1 0x108 +#define SAA7146_ISR 0x10c +#define SAA7146_PSR 0x110 +#define SAA7146_SSR 0x114 +#define SAA7146_EC1R 0x118 +#define SAA7146_EC2R 0x11c +#define SAA7146_VDP1 0x120 +#define SAA7146_VDP2 0x124 +#define SAA7146_VDP3 0x128 +#define SAA7146_ADP1 0x12c +#define SAA7146_ADP2 0x130 +#define SAA7146_ADP3 0x134 +#define SAA7146_ADP4 0x138 +#define SAA7146_DDP 0x13c +#define SAA7146_LEVEL_REP 0x140 +#define SAA7146_FB_BUFFER1 0x144 +#define SAA7146_FB_BUFFER2 0x148 +#define SAA7146_A_TIME_SLOT1 0x180 +#define SAA7146_A_TIME_SLOT2 0x1C0 + +/* bitfield defines */ +#define MASK_31 0x80000000 +#define MASK_30 0x40000000 +#define MASK_29 0x20000000 +#define MASK_28 0x10000000 +#define MASK_27 0x08000000 +#define MASK_26 0x04000000 +#define MASK_25 0x02000000 +#define MASK_24 0x01000000 +#define MASK_23 0x00800000 +#define MASK_22 0x00400000 +#define MASK_21 0x00200000 +#define MASK_20 0x00100000 +#define MASK_19 0x00080000 +#define MASK_18 0x00040000 +#define MASK_17 0x00020000 +#define MASK_16 0x00010000 +#define MASK_15 0x00008000 +#define MASK_14 0x00004000 +#define MASK_13 0x00002000 +#define MASK_12 0x00001000 +#define MASK_11 0x00000800 +#define MASK_10 0x00000400 +#define MASK_09 0x00000200 +#define MASK_08 0x00000100 +#define MASK_07 0x00000080 +#define MASK_06 0x00000040 +#define MASK_05 0x00000020 +#define MASK_04 0x00000010 +#define MASK_03 0x00000008 +#define MASK_02 0x00000004 +#define MASK_01 0x00000002 +#define MASK_00 0x00000001 +#define MASK_B0 0x000000ff +#define MASK_B1 0x0000ff00 +#define MASK_B2 0x00ff0000 +#define MASK_B3 0xff000000 +#define MASK_W0 0x0000ffff +#define MASK_W1 0xffff0000 +#define MASK_PA 0xfffffffc +#define MASK_PR 0xfffffffe +#define MASK_ER 0xffffffff +#define MASK_NONE 0x00000000 + +#define SAA7146_PAGE_MAP_EN MASK_11 +/* main control register 1 */ +#define SAA7146_MC1_MRST_N MASK_15 +#define SAA7146_MC1_ERPS1 MASK_13 +#define SAA7146_MC1_ERPS0 MASK_12 +#define SAA7146_MC1_EDP MASK_11 +#define SAA7146_MC1_EVP MASK_10 +#define SAA7146_MC1_EAP MASK_09 +#define SAA7146_MC1_EI2C MASK_08 +#define SAA7146_MC1_TR_E_DEBI MASK_07 +#define SAA7146_MC1_TR_E_1 MASK_06 +#define SAA7146_MC1_TR_E_2 MASK_05 +#define SAA7146_MC1_TR_E_3 MASK_04 +#define SAA7146_MC1_TR_E_A2_OUT MASK_03 +#define SAA7146_MC1_TR_E_A2_IN MASK_02 +#define SAA7146_MC1_TR_E_A1_OUT MASK_01 +#define SAA7146_MC1_TR_E_A1_IN MASK_00 +/* main control register 2 */ +#define SAA7146_MC2_RPS_SIG4 MASK_15 +#define SAA7146_MC2_RPS_SIG3 MASK_14 +#define SAA7146_MC2_RPS_SIG2 MASK_13 +#define SAA7146_MC2_RPS_SIG1 MASK_12 +#define SAA7146_MC2_RPS_SIG0 MASK_11 +#define SAA7146_MC2_UPLD_D1_B MASK_10 +#define SAA7146_MC2_UPLD_D1_A MASK_09 +#define SAA7146_MC2_UPLD_BRS MASK_08 +#define SAA7146_MC2_UPLD_HPS_H MASK_06 +#define SAA7146_MC2_UPLD_HPS_V MASK_05 +#define SAA7146_MC2_UPLD_DMA3 MASK_04 +#define SAA7146_MC2_UPLD_DMA2 MASK_03 +#define SAA7146_MC2_UPLD_DMA1 MASK_02 +#define SAA7146_MC2_UPLD_DEBI MASK_01 +#define SAA7146_MC2_UPLD_I2C MASK_00 +/* Primary Status Register and Interrupt Enable/Status Registers */ +#define SAA7146_PSR_PPEF MASK_31 +#define SAA7146_PSR_PABO MASK_30 +#define SAA7146_PSR_PPED MASK_29 +#define SAA7146_PSR_RPS_I1 MASK_28 +#define SAA7146_PSR_RPS_I0 MASK_27 +#define SAA7146_PSR_RPS_LATE1 MASK_26 +#define SAA7146_PSR_RPS_LATE0 MASK_25 +#define SAA7146_PSR_RPS_E1 MASK_24 +#define SAA7146_PSR_RPS_E0 MASK_23 +#define SAA7146_PSR_RPS_TO1 MASK_22 +#define SAA7146_PSR_RPS_TO0 MASK_21 +#define SAA7146_PSR_UPLD MASK_20 +#define SAA7146_PSR_DEBI_S MASK_19 +#define SAA7146_PSR_DEBI_E MASK_18 +#define SAA7146_PSR_I2C_S MASK_17 +#define SAA7146_PSR_I2C_E MASK_16 +#define SAA7146_PSR_A2_IN MASK_15 +#define SAA7146_PSR_A2_OUT MASK_14 +#define SAA7146_PSR_A1_IN MASK_13 +#define SAA7146_PSR_A1_OUT MASK_12 +#define SAA7146_PSR_AFOU MASK_11 +#define SAA7146_PSR_V_PE MASK_10 +#define SAA7146_PSR_VFOU MASK_09 +#define SAA7146_PSR_FIDA MASK_08 +#define SAA7146_PSR_FIDB MASK_07 +#define SAA7146_PSR_PIN3 MASK_06 +#define SAA7146_PSR_PIN2 MASK_05 +#define SAA7146_PSR_PIN1 MASK_04 +#define SAA7146_PSR_PIN0 MASK_03 +#define SAA7146_PSR_ECS MASK_02 +#define SAA7146_PSR_EC3S MASK_01 +#define SAA7146_PSR_EC0S MASK_00 +/* Secondary Status Register */ +#define SAA7146_SSR_PRQ MASK_31 +#define SAA7146_SSR_PMA MASK_30 +#define SAA7146_SSR_RPS_RE1 MASK_29 +#define SAA7146_SSR_RPS_PE1 MASK_28 +#define SAA7146_SSR_RPS_A1 MASK_27 +#define SAA7146_SSR_RPS_RE0 MASK_26 +#define SAA7146_SSR_RPS_PE0 MASK_25 +#define SAA7146_SSR_RPS_A0 MASK_24 +#define SAA7146_SSR_DEBI_TO MASK_23 +#define SAA7146_SSR_DEBI_EF MASK_22 +#define SAA7146_SSR_I2C_EA MASK_21 +#define SAA7146_SSR_I2C_EW MASK_20 +#define SAA7146_SSR_I2C_ER MASK_19 +#define SAA7146_SSR_I2C_EL MASK_18 +#define SAA7146_SSR_I2C_EF MASK_17 +#define SAA7146_SSR_V3P MASK_16 +#define SAA7146_SSR_V2P MASK_15 +#define SAA7146_SSR_V1P MASK_14 +#define SAA7146_SSR_VF3 MASK_13 +#define SAA7146_SSR_VF2 MASK_12 +#define SAA7146_SSR_VF1 MASK_11 +#define SAA7146_SSR_AF2_IN MASK_10 +#define SAA7146_SSR_AF2_OUT MASK_09 +#define SAA7146_SSR_AF1_IN MASK_08 +#define SAA7146_SSR_AF1_OUT MASK_07 +#define SAA7146_SSR_VGT MASK_05 +#define SAA7146_SSR_LNQG MASK_04 +#define SAA7146_SSR_EC5S MASK_03 +#define SAA7146_SSR_EC4S MASK_02 +#define SAA7146_SSR_EC2S MASK_01 +#define SAA7146_SSR_EC1S MASK_00 +/* I2C status register */ +#define SAA7146_I2C_ABORT MASK_07 +#define SAA7146_I2C_SPERR MASK_06 +#define SAA7146_I2C_APERR MASK_05 +#define SAA7146_I2C_DTERR MASK_04 +#define SAA7146_I2C_DRERR MASK_03 +#define SAA7146_I2C_AL MASK_02 +#define SAA7146_I2C_ERR MASK_01 +#define SAA7146_I2C_BUSY MASK_00 +/* output formats */ +#define SAA7146_YUV422 0 +#define SAA7146_RGB16 0 +#define SAA7146_YUV444 1 +#define SAA7146_RGB24 1 +#define SAA7146_ARGB32 2 +#define SAA7146_YUV411 3 +#define SAA7146_ARGB15 3 +#define SAA7146_YUV2 4 +#define SAA7146_RGAB15 4 +#define SAA7146_Y8 6 +#define SAA7146_YUV8 7 +#define SAA7146_RGB8 7 +#define SAA7146_YUV444p 8 +#define SAA7146_YUV422p 9 +#define SAA7146_YUV420p 10 +#define SAA7146_YUV1620 11 +#define SAA7146_Y1 13 +#define SAA7146_Y2 14 +#define SAA7146_YUV1 15 +#endif diff --git a/drivers/media/video/saa7185.c b/drivers/media/video/saa7185.c new file mode 100644 index 000000000000..5f0b224c3cb6 --- /dev/null +++ b/drivers/media/video/saa7185.c @@ -0,0 +1,524 @@ +/* + * saa7185 - Philips SAA7185B video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> + +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7185 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct saa7185 { + unsigned char reg[128]; + + int norm; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_SAA7185 0x88 + +/* ----------------------------------------------------------------------- */ + +static inline int +saa7185_read (struct i2c_client *client) +{ + return i2c_smbus_read_byte(client); +} + +static int +saa7185_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct saa7185 *encoder = i2c_get_clientdata(client); + + dprintk(1, KERN_DEBUG "SAA7185: %02x set to %02x\n", reg, value); + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7185_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct saa7185 *encoder = i2c_get_clientdata(client); + struct i2c_msg msg; + u8 block_data[32]; + + msg.addr = client->addr; + msg.flags = 0; + while (len >= 2) { + msg.buf = (char *) block_data; + msg.len = 0; + block_data[msg.len++] = reg = data[0]; + do { + block_data[msg.len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + msg.len < 32); + if ((ret = i2c_transfer(client->adapter, + &msg, 1)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = saa7185_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------- */ + +static const unsigned char init_common[] = { + 0x3a, 0x0f, /* CBENB=0, V656=0, VY2C=1, + * YUV2C=1, MY2C=1, MUV2C=1 */ + + 0x42, 0x6b, /* OVLY0=107 */ + 0x43, 0x00, /* OVLU0=0 white */ + 0x44, 0x00, /* OVLV0=0 */ + 0x45, 0x22, /* OVLY1=34 */ + 0x46, 0xac, /* OVLU1=172 yellow */ + 0x47, 0x0e, /* OVLV1=14 */ + 0x48, 0x03, /* OVLY2=3 */ + 0x49, 0x1d, /* OVLU2=29 cyan */ + 0x4a, 0xac, /* OVLV2=172 */ + 0x4b, 0xf0, /* OVLY3=240 */ + 0x4c, 0xc8, /* OVLU3=200 green */ + 0x4d, 0xb9, /* OVLV3=185 */ + 0x4e, 0xd4, /* OVLY4=212 */ + 0x4f, 0x38, /* OVLU4=56 magenta */ + 0x50, 0x47, /* OVLV4=71 */ + 0x51, 0xc1, /* OVLY5=193 */ + 0x52, 0xe3, /* OVLU5=227 red */ + 0x53, 0x54, /* OVLV5=84 */ + 0x54, 0xa3, /* OVLY6=163 */ + 0x55, 0x54, /* OVLU6=84 blue */ + 0x56, 0xf2, /* OVLV6=242 */ + 0x57, 0x90, /* OVLY7=144 */ + 0x58, 0x00, /* OVLU7=0 black */ + 0x59, 0x00, /* OVLV7=0 */ + + 0x5a, 0x00, /* CHPS=0 */ + 0x5b, 0x76, /* GAINU=118 */ + 0x5c, 0xa5, /* GAINV=165 */ + 0x5d, 0x3c, /* BLCKL=60 */ + 0x5e, 0x3a, /* BLNNL=58 */ + 0x5f, 0x3a, /* CCRS=0, BLNVB=58 */ + 0x60, 0x00, /* NULL */ + + /* 0x61 - 0x66 set according to norm */ + + 0x67, 0x00, /* 0 : caption 1st byte odd field */ + 0x68, 0x00, /* 0 : caption 2nd byte odd field */ + 0x69, 0x00, /* 0 : caption 1st byte even field */ + 0x6a, 0x00, /* 0 : caption 2nd byte even field */ + + 0x6b, 0x91, /* MODIN=2, PCREF=0, SCCLN=17 */ + 0x6c, 0x20, /* SRCV1=0, TRCV2=1, ORCV1=0, PRCV1=0, + * CBLF=0, ORCV2=0, PRCV2=0 */ + 0x6d, 0x00, /* SRCM1=0, CCEN=0 */ + + 0x6e, 0x0e, /* HTRIG=0x005, approx. centered, at + * least for PAL */ + 0x6f, 0x00, /* HTRIG upper bits */ + 0x70, 0x20, /* PHRES=0, SBLN=1, VTRIG=0 */ + + /* The following should not be needed */ + + 0x71, 0x15, /* BMRQ=0x115 */ + 0x72, 0x90, /* EMRQ=0x690 */ + 0x73, 0x61, /* EMRQ=0x690, BMRQ=0x115 */ + 0x74, 0x00, /* NULL */ + 0x75, 0x00, /* NULL */ + 0x76, 0x00, /* NULL */ + 0x77, 0x15, /* BRCV=0x115 */ + 0x78, 0x90, /* ERCV=0x690 */ + 0x79, 0x61, /* ERCV=0x690, BRCV=0x115 */ + + /* Field length controls */ + + 0x7a, 0x70, /* FLC=0 */ + + /* The following should not be needed if SBLN = 1 */ + + 0x7b, 0x16, /* FAL=22 */ + 0x7c, 0x35, /* LAL=244 */ + 0x7d, 0x20, /* LAL=244, FAL=22 */ +}; + +static const unsigned char init_pal[] = { + 0x61, 0x1e, /* FISE=0, PAL=1, SCBW=1, RTCE=1, + * YGS=1, INPI=0, DOWN=0 */ + 0x62, 0xc8, /* DECTYP=1, BSTA=72 */ + 0x63, 0xcb, /* FSC0 */ + 0x64, 0x8a, /* FSC1 */ + 0x65, 0x09, /* FSC2 */ + 0x66, 0x2a, /* FSC3 */ +}; + +static const unsigned char init_ntsc[] = { + 0x61, 0x1d, /* FISE=1, PAL=0, SCBW=1, RTCE=1, + * YGS=1, INPI=0, DOWN=0 */ + 0x62, 0xe6, /* DECTYP=1, BSTA=102 */ + 0x63, 0x1f, /* FSC0 */ + 0x64, 0x7c, /* FSC1 */ + 0x65, 0xf0, /* FSC2 */ + 0x66, 0x21, /* FSC3 */ +}; + +static int +saa7185_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7185 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + saa7185_write_block(client, init_common, + sizeof(init_common)); + switch (encoder->norm) { + + case VIDEO_MODE_NTSC: + saa7185_write_block(client, init_ntsc, + sizeof(init_ntsc)); + break; + + case VIDEO_MODE_PAL: + saa7185_write_block(client, init_pal, + sizeof(init_pal)); + break; + } + + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + cap->flags = + VIDEO_ENCODER_PAL | VIDEO_ENCODER_NTSC | + VIDEO_ENCODER_SECAM | VIDEO_ENCODER_CCIR; + cap->inputs = 1; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int *iarg = arg; + + //saa7185_write_block(client, init_common, sizeof(init_common)); + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + saa7185_write_block(client, init_ntsc, + sizeof(init_ntsc)); + break; + + case VIDEO_MODE_PAL: + saa7185_write_block(client, init_pal, + sizeof(init_pal)); + break; + + case VIDEO_MODE_SECAM: + default: + return -EINVAL; + + } + encoder->norm = *iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int *iarg = arg; + + /* RJ: *iarg = 0: input is from SA7111 + *iarg = 1: input is from ZR36060 */ + + switch (*iarg) { + + case 0: + /* Switch RTCE to 1 */ + saa7185_write(client, 0x61, + (encoder->reg[0x61] & 0xf7) | 0x08); + saa7185_write(client, 0x6e, 0x01); + break; + + case 1: + /* Switch RTCE to 0 */ + saa7185_write(client, 0x61, + (encoder->reg[0x61] & 0xf7) | 0x00); + /* SW: a slight sync problem... */ + saa7185_write(client, 0x6e, 0x00); + break; + + default: + return -EINVAL; + + } + } + break; + + case ENCODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case ENCODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + encoder->enable = !!*iarg; + saa7185_write(client, 0x61, + (encoder->reg[0x61] & 0xbf) | + (encoder->enable ? 0x00 : 0x40)); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { I2C_SAA7185 >> 1, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver i2c_driver_saa7185; + +static int +saa7185_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct saa7185 *encoder; + + dprintk(1, + KERN_INFO + "saa7185.c: detecting saa7185 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7185; + client->flags = I2C_CLIENT_ALLOW_USE; + strlcpy(I2C_NAME(client), "saa7185", sizeof(I2C_NAME(client))); + + encoder = kmalloc(sizeof(struct saa7185), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(encoder, 0, sizeof(struct saa7185)); + encoder->norm = VIDEO_MODE_NTSC; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + i = saa7185_write_block(client, init_common, sizeof(init_common)); + if (i >= 0) { + i = saa7185_write_block(client, init_ntsc, + sizeof(init_ntsc)); + } + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init error %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version %d at address 0x%x\n", + I2C_NAME(client), saa7185_read(client) >> 5, + client->addr << 1); + } + + return 0; +} + +static int +saa7185_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7185.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7185_detect_client); +} + +static int +saa7185_detach_client (struct i2c_client *client) +{ + struct saa7185 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + saa7185_write(client, 0x61, (encoder->reg[0x61]) | 0x40); /* SW: output off is active */ + //saa7185_write(client, 0x3a, (encoder->reg[0x3a]) | 0x80); /* SW: color bar */ + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7185 = { + .owner = THIS_MODULE, + .name = "saa7185", /* name */ + + .id = I2C_DRIVERID_SAA7185B, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = saa7185_attach_adapter, + .detach_client = saa7185_detach_client, + .command = saa7185_command, +}; + +static int __init +saa7185_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7185); +} + +static void __exit +saa7185_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7185); +} + +module_init(saa7185_init); +module_exit(saa7185_exit); diff --git a/drivers/media/video/saa7196.h b/drivers/media/video/saa7196.h new file mode 100644 index 000000000000..f92f21cfbcac --- /dev/null +++ b/drivers/media/video/saa7196.h @@ -0,0 +1,117 @@ +/* + Definitions for the Philips SAA7196 digital video decoder, + scaler, and clock generator circuit (DESCpro), as used in + the PlanB video input of the Powermac 7x00/8x00 series. + + Copyright (C) 1998 Michel Lanners (mlan@cpu.lu) + + The register defines are shamelessly copied from the meteor + driver out of NetBSD (with permission), + and are copyrighted (c) 1995 Mark Tinguely and Jim Lowe + (Thanks !) + + Additional debugging and coding by Takashi Oe (toe@unlinfo.unl.edu) + + The default values used for PlanB are my mistakes. +*/ + +/* $Id: saa7196.h,v 1.5 1999/03/26 23:28:47 mlan Exp $ */ + +#ifndef _SAA7196_H_ +#define _SAA7196_H_ + +#define SAA7196_NUMREGS 0x31 /* Number of registers (used)*/ +#define NUM_SUPPORTED_NORM 3 /* Number of supported norms by PlanB */ + +/* Decoder part: */ +#define SAA7196_IDEL 0x00 /* Increment delay */ +#define SAA7196_HSB5 0x01 /* H-sync begin; 50 hz */ +#define SAA7196_HSS5 0x02 /* H-sync stop; 50 hz */ +#define SAA7196_HCB5 0x03 /* H-clamp begin; 50 hz */ +#define SAA7196_HCS5 0x04 /* H-clamp stop; 50 hz */ +#define SAA7196_HSP5 0x05 /* H-sync after PHI1; 50 hz */ +#define SAA7196_LUMC 0x06 /* Luminance control */ +#define SAA7196_HUEC 0x07 /* Hue control */ +#define SAA7196_CKTQ 0x08 /* Colour Killer Threshold QAM (PAL, NTSC) */ +#define SAA7196_CKTS 0x09 /* Colour Killer Threshold SECAM */ +#define SAA7196_PALS 0x0a /* PAL switch sensitivity */ +#define SAA7196_SECAMS 0x0b /* SECAM switch sensitivity */ +#define SAA7196_CGAINC 0x0c /* Chroma gain control */ +#define SAA7196_STDC 0x0d /* Standard/Mode control */ +#define SAA7196_IOCC 0x0e /* I/O and Clock Control */ +#define SAA7196_CTRL1 0x0f /* Control #1 */ +#define SAA7196_CTRL2 0x10 /* Control #2 */ +#define SAA7196_CGAINR 0x11 /* Chroma Gain Reference */ +#define SAA7196_CSAT 0x12 /* Chroma Saturation */ +#define SAA7196_CONT 0x13 /* Luminance Contrast */ +#define SAA7196_HSB6 0x14 /* H-sync begin; 60 hz */ +#define SAA7196_HSS6 0x15 /* H-sync stop; 60 hz */ +#define SAA7196_HCB6 0x16 /* H-clamp begin; 60 hz */ +#define SAA7196_HCS6 0x17 /* H-clamp stop; 60 hz */ +#define SAA7196_HSP6 0x18 /* H-sync after PHI1; 60 hz */ +#define SAA7196_BRIG 0x19 /* Luminance Brightness */ + +/* Scaler part: */ +#define SAA7196_FMTS 0x20 /* Formats and sequence */ +#define SAA7196_OUTPIX 0x21 /* Output data pixel/line */ +#define SAA7196_INPIX 0x22 /* Input data pixel/line */ +#define SAA7196_HWS 0x23 /* Horiz. window start */ +#define SAA7196_HFILT 0x24 /* Horiz. filter */ +#define SAA7196_OUTLINE 0x25 /* Output data lines/field */ +#define SAA7196_INLINE 0x26 /* Input data lines/field */ +#define SAA7196_VWS 0x27 /* Vertical window start */ +#define SAA7196_VYP 0x28 /* AFS/vertical Y processing */ +#define SAA7196_VBS 0x29 /* Vertical Bypass start */ +#define SAA7196_VBCNT 0x2a /* Vertical Bypass count */ +#define SAA7196_VBP 0x2b /* veritcal Bypass Polarity */ +#define SAA7196_VLOW 0x2c /* Colour-keying lower V limit */ +#define SAA7196_VHIGH 0x2d /* Colour-keying upper V limit */ +#define SAA7196_ULOW 0x2e /* Colour-keying lower U limit */ +#define SAA7196_UHIGH 0x2f /* Colour-keying upper U limit */ +#define SAA7196_DPATH 0x30 /* Data path setting */ + +/* Initialization default values: */ + +unsigned char saa_regs[NUM_SUPPORTED_NORM][SAA7196_NUMREGS] = { + +/* PAL, 768x576 (no scaling), composite video-in */ +/* Decoder: */ + { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x63, 0xff, + 0xfe, 0xf0, 0xfe, 0xe0, 0x20, 0x06, 0x3b, 0x98, + 0x00, 0x59, 0x41, 0x45, 0x34, 0x0a, 0xf4, 0xd2, + 0xe9, 0xa2, +/* Padding */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +/* Scaler: */ + 0x72, 0x80, 0x00, 0x03, 0x8d, 0x20, 0x20, 0x12, + 0xa5, 0x12, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x87 }, + +/* NTSC, 640x480? (no scaling), composite video-in */ +/* Decoder: */ + { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x50, 0x00, + 0xf8, 0xf0, 0xfe, 0xe0, 0x00, 0x06, 0x3b, 0x98, + 0x00, 0x2c, 0x3d, 0x40, 0x34, 0x0a, 0xf4, 0xd2, + 0xe9, 0x98, +/* Padding */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +/* Scaler: */ + 0x72, 0x80, 0x80, 0x03, 0x89, 0xf0, 0xf0, 0x0d, + 0xa0, 0x0d, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x87 }, + +/* SECAM, 768x576 (no scaling), composite video-in */ +/* Decoder: */ + { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x63, 0xff, + 0xfe, 0xf0, 0xfe, 0xe0, 0x20, 0x07, 0x3b, 0x98, + 0x00, 0x59, 0x41, 0x45, 0x34, 0x0a, 0xf4, 0xd2, + 0xe9, 0xa2, +/* Padding */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +/* Scaler: */ + 0x72, 0x80, 0x00, 0x03, 0x8d, 0x20, 0x20, 0x12, + 0xa5, 0x12, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x87 } + }; + +#endif /* _SAA7196_H_ */ diff --git a/drivers/media/video/stradis.c b/drivers/media/video/stradis.c new file mode 100644 index 000000000000..b57743571087 --- /dev/null +++ b/drivers/media/video/stradis.c @@ -0,0 +1,2258 @@ +/* + * stradis.c - stradis 4:2:2 mpeg decoder driver + * + * Stradis 4:2:2 MPEG-2 Decoder Driver + * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/types.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <asm/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/videodev.h> + +#include "saa7146.h" +#include "saa7146reg.h" +#include "ibmmpeg2.h" +#include "saa7121.h" +#include "cs8420.h" + +#define DEBUG(x) /* debug driver */ +#undef IDEBUG /* debug irq handler */ +#undef MDEBUG /* debug memory management */ + +#define SAA7146_MAX 6 + +static struct saa7146 saa7146s[SAA7146_MAX]; + +static int saa_num = 0; /* number of SAA7146s in use */ + +static int video_nr = -1; +module_param(video_nr, int, 0); +MODULE_LICENSE("GPL"); + + +#define nDebNormal 0x00480000 +#define nDebNoInc 0x00480000 +#define nDebVideo 0xd0480000 +#define nDebAudio 0xd0400000 +#define nDebDMA 0x02c80000 + +#define oDebNormal 0x13c80000 +#define oDebNoInc 0x13c80000 +#define oDebVideo 0xd1080000 +#define oDebAudio 0xd1080000 +#define oDebDMA 0x03080000 + +#define NewCard (saa->boardcfg[3]) +#define ChipControl (saa->boardcfg[1]) +#define NTSCFirstActive (saa->boardcfg[4]) +#define PALFirstActive (saa->boardcfg[5]) +#define NTSCLastActive (saa->boardcfg[54]) +#define PALLastActive (saa->boardcfg[55]) +#define Have2MB (saa->boardcfg[18] & 0x40) +#define HaveCS8420 (saa->boardcfg[18] & 0x04) +#define IBMMPEGCD20 (saa->boardcfg[18] & 0x20) +#define HaveCS3310 (saa->boardcfg[18] & 0x01) +#define CS3310MaxLvl ((saa->boardcfg[30] << 8) | saa->boardcfg[31]) +#define HaveCS4341 (saa->boardcfg[40] == 2) +#define SDIType (saa->boardcfg[27]) +#define CurrentMode (saa->boardcfg[2]) + +#define debNormal (NewCard ? nDebNormal : oDebNormal) +#define debNoInc (NewCard ? nDebNoInc : oDebNoInc) +#define debVideo (NewCard ? nDebVideo : oDebVideo) +#define debAudio (NewCard ? nDebAudio : oDebAudio) +#define debDMA (NewCard ? nDebDMA : oDebDMA) + +#ifdef USE_RESCUE_EEPROM_SDM275 +static unsigned char rescue_eeprom[64] = { +0x00,0x01,0x04,0x13,0x26,0x0f,0x10,0x00,0x00,0x00,0x43,0x63,0x22,0x01,0x29,0x15,0x73,0x00,0x1f, 'd', 'e', 'c', 'x', 'l', 'd', 'v', 'a',0x02,0x00,0x01,0x00,0xcc,0xa4,0x63,0x09,0xe2,0x10,0x00,0x0a,0x00,0x02,0x02, 'd', 'e', 'c', 'x', 'l', 'a',0x00,0x00,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +#endif + +/* ----------------------------------------------------------------------- */ +/* Hardware I2C functions */ +static void I2CWipe(struct saa7146 *saa) +{ + int i; + /* set i2c to ~=100kHz, abort transfer, clear busy */ + saawrite(0x600 | SAA7146_I2C_ABORT, SAA7146_I2C_STATUS); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + saawrite(0x600, SAA7146_I2C_STATUS); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + saawrite(0x600, SAA7146_I2C_STATUS); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); +} + +/* read I2C */ +static int I2CRead(struct saa7146 *saa, unsigned char addr, + unsigned char subaddr, int dosub) +{ + int i; + + if (saaread(SAA7146_I2C_STATUS) & 0x3c) + I2CWipe(saa); + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++) + schedule(); + if (i == 1000) + I2CWipe(saa); + if (dosub) + saawrite(((addr & 0xfe) << 24) | (((addr | 1) & 0xff) << 8) | + ((subaddr & 0xff) << 16) | 0xed, SAA7146_I2C_TRANSFER); + else + saawrite(((addr & 0xfe) << 24) | (((addr | 1) & 0xff) << 16) | + 0xf1, SAA7146_I2C_TRANSFER); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + /* wait for valid data */ + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++) + schedule(); + if (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_ERR) + return -1; + if (i == 1000) + printk("i2c setup read timeout\n"); + saawrite(0x41, SAA7146_I2C_TRANSFER); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + /* wait for valid data */ + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_TRANSFER) & SAA7146_I2C_BUSY); i++) + schedule(); + if (saaread(SAA7146_I2C_TRANSFER) & SAA7146_I2C_ERR) + return -1; + if (i == 1000) + printk("i2c read timeout\n"); + return ((saaread(SAA7146_I2C_TRANSFER) >> 24) & 0xff); +} + +/* set both to write both bytes, reset it to write only b1 */ + +static int I2CWrite(struct saa7146 *saa, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + int i; + u32 data; + + if (saaread(SAA7146_I2C_STATUS) & 0x3c) + I2CWipe(saa); + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++) + schedule(); + if (i == 1000) + I2CWipe(saa); + data = ((addr & 0xfe) << 24) | ((b1 & 0xff) << 16); + if (both) + data |= ((b2 & 0xff) << 8) | 0xe5; + else + data |= 0xd1; + saawrite(data, SAA7146_I2C_TRANSFER); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | SAA7146_MC2_UPLD_I2C, + SAA7146_MC2); + return 0; +} + +static void attach_inform(struct saa7146 *saa, int id) +{ + int i; + + DEBUG(printk(KERN_DEBUG "stradis%d: i2c: device found=%02x\n", saa->nr, id)); + if (id == 0xa0) { /* we have rev2 or later board, fill in info */ + for (i = 0; i < 64; i++) + saa->boardcfg[i] = I2CRead(saa, 0xa0, i, 1); +#ifdef USE_RESCUE_EEPROM_SDM275 + if (saa->boardcfg[0] != 0) { + printk("stradis%d: WARNING: EEPROM STORED VALUES HAVE BEEN IGNORED\n", saa->nr); + for (i = 0; i < 64; i++) + saa->boardcfg[i] = rescue_eeprom[i]; + } +#endif + printk("stradis%d: config =", saa->nr); + for (i = 0; i < 51; i++) { + printk(" %02x",saa->boardcfg[i]); + } + printk("\n"); + } +} + +static void I2CBusScan(struct saa7146 *saa) +{ + int i; + for (i = 0; i < 0xff; i += 2) + if ((I2CRead(saa, i, 0, 0)) >= 0) + attach_inform(saa, i); +} + +static int debiwait_maxwait = 0; + +static int wait_for_debi_done(struct saa7146 *saa) +{ + int i; + + /* wait for registers to be programmed */ + for (i = 0; i < 100000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_DEBI); i++) + saaread(SAA7146_MC2); + /* wait for transfer to complete */ + for (i = 0; i < 500000 && + (saaread(SAA7146_PSR) & SAA7146_PSR_DEBI_S); i++) + saaread(SAA7146_MC2); + if (i > debiwait_maxwait) + printk("wait-for-debi-done maxwait: %d\n", + debiwait_maxwait = i); + + if (i == 500000) + return -1; + return 0; +} + +static int debiwrite(struct saa7146 *saa, u32 config, int addr, + u32 val, int count) +{ + u32 cmd; + if (count <= 0 || count > 32764) + return -1; + if (wait_for_debi_done(saa) < 0) + return -1; + saawrite(config, SAA7146_DEBI_CONFIG); + if (count <= 4) /* immediate transfer */ + saawrite(val, SAA7146_DEBI_AD); + else /* block transfer */ + saawrite(virt_to_bus(saa->dmadebi), SAA7146_DEBI_AD); + saawrite((cmd = (count << 17) | (addr & 0xffff)), SAA7146_DEBI_COMMAND); + saawrite((SAA7146_MC2_UPLD_DEBI << 16) | SAA7146_MC2_UPLD_DEBI, + SAA7146_MC2); + return 0; +} + +static u32 debiread(struct saa7146 *saa, u32 config, int addr, int count) +{ + u32 result = 0; + + if (count > 32764 || count <= 0) + return 0; + if (wait_for_debi_done(saa) < 0) + return 0; + saawrite(virt_to_bus(saa->dmadebi), SAA7146_DEBI_AD); + saawrite((count << 17) | 0x10000 | (addr & 0xffff), + SAA7146_DEBI_COMMAND); + saawrite(config, SAA7146_DEBI_CONFIG); + saawrite((SAA7146_MC2_UPLD_DEBI << 16) | SAA7146_MC2_UPLD_DEBI, + SAA7146_MC2); + if (count > 4) /* not an immediate transfer */ + return count; + wait_for_debi_done(saa); + result = saaread(SAA7146_DEBI_AD); + if (count == 1) + result &= 0xff; + if (count == 2) + result &= 0xffff; + if (count == 3) + result &= 0xffffff; + return result; +} + +#if 0 /* unused */ +/* MUST be a multiple of 8 bytes and 8-byte aligned and < 32768 bytes */ +/* data copied into saa->dmadebi buffer, caller must re-enable interrupts */ +static void ibm_block_dram_read(struct saa7146 *saa, int address, int bytes) +{ + int i, j; + u32 *buf; + buf = (u32 *) saa->dmadebi; + if (bytes > 0x7000) + bytes = 0x7000; + saawrite(0, SAA7146_IER); /* disable interrupts */ + for (i=0; i < 10000 && + (debiread(saa, debNormal, IBM_MP2_DRAM_CMD_STAT, 2) + & 0x8000); i++) + saaread(SAA7146_MC2); + if (i == 10000) + printk(KERN_ERR "stradis%d: dram_busy never cleared\n", + saa->nr); + debiwrite(saa, debNormal, IBM_MP2_SRC_ADDR, (address<<16) | + (address>>16), 4); + debiwrite(saa, debNormal, IBM_MP2_BLOCK_SIZE, bytes, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_STAT, 0x8a10, 2); + for (j = 0; j < bytes/4; j++) { + for (i = 0; i < 10000 && + (!(debiread(saa, debNormal, IBM_MP2_DRAM_CMD_STAT, 2) + & 0x4000)); i++) + saaread(SAA7146_MC2); + if (i == 10000) + printk(KERN_ERR "stradis%d: dram_ready never set\n", + saa->nr); + buf[j] = debiread(saa, debNormal, IBM_MP2_DRAM_DATA, 4); + } +} +#endif /* unused */ + +static void do_irq_send_data(struct saa7146 *saa) +{ + int split, audbytes, vidbytes; + + saawrite(SAA7146_PSR_PIN1, SAA7146_IER); + /* if special feature mode in effect, disable audio sending */ + if (saa->playmode != VID_PLAY_NORMAL) + saa->audtail = saa->audhead = 0; + if (saa->audhead <= saa->audtail) + audbytes = saa->audtail - saa->audhead; + else + audbytes = 65536 - (saa->audhead - saa->audtail); + if (saa->vidhead <= saa->vidtail) + vidbytes = saa->vidtail - saa->vidhead; + else + vidbytes = 524288 - (saa->vidhead - saa->vidtail); + if (audbytes == 0 && vidbytes == 0 && saa->osdtail == saa->osdhead) { + saawrite(0, SAA7146_IER); + return; + } + /* if at least 1 block audio waiting and audio fifo isn't full */ + if (audbytes >= 2048 && (debiread(saa, debNormal, + IBM_MP2_AUD_FIFO, 2) & 0xff) < 60) { + if (saa->audhead > saa->audtail) + split = 65536 - saa->audhead; + else + split = 0; + audbytes = 2048; + if (split > 0 && split < 2048) { + memcpy(saa->dmadebi, saa->audbuf + saa->audhead, + split); + saa->audhead = 0; + audbytes -= split; + } else + split = 0; + memcpy(saa->dmadebi + split, saa->audbuf + saa->audhead, + audbytes); + saa->audhead += audbytes; + saa->audhead &= 0xffff; + debiwrite(saa, debAudio, (NewCard? IBM_MP2_AUD_FIFO : + IBM_MP2_AUD_FIFOW), 0, 2048); + wake_up_interruptible(&saa->audq); + /* if at least 1 block video waiting and video fifo isn't full */ + } else if (vidbytes >= 30720 && (debiread(saa, debNormal, + IBM_MP2_FIFO, 2)) < 16384) { + if (saa->vidhead > saa->vidtail) + split = 524288 - saa->vidhead; + else + split = 0; + vidbytes = 30720; + if (split > 0 && split < 30720) { + memcpy(saa->dmadebi, saa->vidbuf + saa->vidhead, + split); + saa->vidhead = 0; + vidbytes -= split; + } else + split = 0; + memcpy(saa->dmadebi + split, saa->vidbuf + saa->vidhead, + vidbytes); + saa->vidhead += vidbytes; + saa->vidhead &= 0x7ffff; + debiwrite(saa, debVideo, (NewCard ? IBM_MP2_FIFO : + IBM_MP2_FIFOW), 0, 30720); + wake_up_interruptible(&saa->vidq); + } + saawrite(SAA7146_PSR_DEBI_S | SAA7146_PSR_PIN1, SAA7146_IER); +} + +static void send_osd_data(struct saa7146 *saa) +{ + int size = saa->osdtail - saa->osdhead; + if (size > 30720) + size = 30720; + /* ensure some multiple of 8 bytes is transferred */ + size = 8 * ((size + 8)>>3); + if (size) { + debiwrite(saa, debNormal, IBM_MP2_OSD_ADDR, + (saa->osdhead>>3), 2); + memcpy(saa->dmadebi, &saa->osdbuf[saa->osdhead], size); + saa->osdhead += size; + /* block transfer of next 8 bytes to ~32k bytes */ + debiwrite(saa, debNormal, IBM_MP2_OSD_DATA, 0, size); + } + if (saa->osdhead >= saa->osdtail) { + saa->osdhead = saa->osdtail = 0; + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2); + } +} + +static irqreturn_t saa7146_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7146 *saa = (struct saa7146 *) dev_id; + u32 stat, astat; + int count; + int handled = 0; + + count = 0; + while (1) { + /* get/clear interrupt status bits */ + stat = saaread(SAA7146_ISR); + astat = stat & saaread(SAA7146_IER); + if (!astat) + break; + handled = 1; + saawrite(astat, SAA7146_ISR); + if (astat & SAA7146_PSR_DEBI_S) { + do_irq_send_data(saa); + } + if (astat & SAA7146_PSR_PIN1) { + int istat; + /* the following read will trigger DEBI_S */ + istat = debiread(saa, debNormal, IBM_MP2_HOST_INT, 2); + if (istat & 1) { + saawrite(0, SAA7146_IER); + send_osd_data(saa); + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + } + if (istat & 0x20) { /* Video Start */ + saa->vidinfo.frame_count++; + } + if (istat & 0x400) { /* Picture Start */ + /* update temporal reference */ + } + if (istat & 0x200) { /* Picture Resolution Change */ + /* read new resolution */ + } + if (istat & 0x100) { /* New User Data found */ + /* read new user data */ + } + if (istat & 0x1000) { /* new GOP/SMPTE */ + /* read new SMPTE */ + } + if (istat & 0x8000) { /* Sequence Start Code */ + /* reset frame counter, load sizes */ + saa->vidinfo.frame_count = 0; + saa->vidinfo.h_size = 704; + saa->vidinfo.v_size = 480; +#if 0 + if (saa->endmarkhead != saa->endmarktail) { + saa->audhead = + saa->endmark[saa->endmarkhead]; + saa->endmarkhead++; + if (saa->endmarkhead >= MAX_MARKS) + saa->endmarkhead = 0; + } +#endif + } + if (istat & 0x4000) { /* Sequence Error Code */ + if (saa->endmarkhead != saa->endmarktail) { + saa->audhead = + saa->endmark[saa->endmarkhead]; + saa->endmarkhead++; + if (saa->endmarkhead >= MAX_MARKS) + saa->endmarkhead = 0; + } + } + } +#ifdef IDEBUG + if (astat & SAA7146_PSR_PPEF) { + IDEBUG(printk("stradis%d irq: PPEF\n", saa->nr)); + } + if (astat & SAA7146_PSR_PABO) { + IDEBUG(printk("stradis%d irq: PABO\n", saa->nr)); + } + if (astat & SAA7146_PSR_PPED) { + IDEBUG(printk("stradis%d irq: PPED\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_I1) { + IDEBUG(printk("stradis%d irq: RPS_I1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_I0) { + IDEBUG(printk("stradis%d irq: RPS_I0\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_LATE1) { + IDEBUG(printk("stradis%d irq: RPS_LATE1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_LATE0) { + IDEBUG(printk("stradis%d irq: RPS_LATE0\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_E1) { + IDEBUG(printk("stradis%d irq: RPS_E1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_E0) { + IDEBUG(printk("stradis%d irq: RPS_E0\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_TO1) { + IDEBUG(printk("stradis%d irq: RPS_TO1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_TO0) { + IDEBUG(printk("stradis%d irq: RPS_TO0\n", saa->nr)); + } + if (astat & SAA7146_PSR_UPLD) { + IDEBUG(printk("stradis%d irq: UPLD\n", saa->nr)); + } + if (astat & SAA7146_PSR_DEBI_E) { + IDEBUG(printk("stradis%d irq: DEBI_E\n", saa->nr)); + } + if (astat & SAA7146_PSR_I2C_S) { + IDEBUG(printk("stradis%d irq: I2C_S\n", saa->nr)); + } + if (astat & SAA7146_PSR_I2C_E) { + IDEBUG(printk("stradis%d irq: I2C_E\n", saa->nr)); + } + if (astat & SAA7146_PSR_A2_IN) { + IDEBUG(printk("stradis%d irq: A2_IN\n", saa->nr)); + } + if (astat & SAA7146_PSR_A2_OUT) { + IDEBUG(printk("stradis%d irq: A2_OUT\n", saa->nr)); + } + if (astat & SAA7146_PSR_A1_IN) { + IDEBUG(printk("stradis%d irq: A1_IN\n", saa->nr)); + } + if (astat & SAA7146_PSR_A1_OUT) { + IDEBUG(printk("stradis%d irq: A1_OUT\n", saa->nr)); + } + if (astat & SAA7146_PSR_AFOU) { + IDEBUG(printk("stradis%d irq: AFOU\n", saa->nr)); + } + if (astat & SAA7146_PSR_V_PE) { + IDEBUG(printk("stradis%d irq: V_PE\n", saa->nr)); + } + if (astat & SAA7146_PSR_VFOU) { + IDEBUG(printk("stradis%d irq: VFOU\n", saa->nr)); + } + if (astat & SAA7146_PSR_FIDA) { + IDEBUG(printk("stradis%d irq: FIDA\n", saa->nr)); + } + if (astat & SAA7146_PSR_FIDB) { + IDEBUG(printk("stradis%d irq: FIDB\n", saa->nr)); + } + if (astat & SAA7146_PSR_PIN3) { + IDEBUG(printk("stradis%d irq: PIN3\n", saa->nr)); + } + if (astat & SAA7146_PSR_PIN2) { + IDEBUG(printk("stradis%d irq: PIN2\n", saa->nr)); + } + if (astat & SAA7146_PSR_PIN0) { + IDEBUG(printk("stradis%d irq: PIN0\n", saa->nr)); + } + if (astat & SAA7146_PSR_ECS) { + IDEBUG(printk("stradis%d irq: ECS\n", saa->nr)); + } + if (astat & SAA7146_PSR_EC3S) { + IDEBUG(printk("stradis%d irq: EC3S\n", saa->nr)); + } + if (astat & SAA7146_PSR_EC0S) { + IDEBUG(printk("stradis%d irq: EC0S\n", saa->nr)); + } +#endif + count++; + if (count > 15) + printk(KERN_WARNING "stradis%d: irq loop %d\n", + saa->nr, count); + if (count > 20) { + saawrite(0, SAA7146_IER); + printk(KERN_ERR + "stradis%d: IRQ loop cleared\n", saa->nr); + } + } + return IRQ_RETVAL(handled); +} + +static int ibm_send_command(struct saa7146 *saa, + int command, int data, int chain) +{ + int i; + + if (chain) + debiwrite(saa, debNormal, IBM_MP2_COMMAND, (command << 1) | 1, 2); + else + debiwrite(saa, debNormal, IBM_MP2_COMMAND, command << 1, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_DATA, data, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_STAT, 1, 2); + for (i = 0; i < 100 && + (debiread(saa, debNormal, IBM_MP2_CMD_STAT, 2) & 1); i++) + schedule(); + if (i == 100) + return -1; + return 0; +} + +static void cs4341_setlevel(struct saa7146 *saa, int left, int right) +{ + I2CWrite(saa, 0x22, 0x03, left > 94 ? 94 : left, 2); + I2CWrite(saa, 0x22, 0x04, right > 94 ? 94 : right, 2); +} + +static void initialize_cs4341(struct saa7146 *saa) +{ + int i; + for (i = 0; i < 200; i++) { + /* auto mute off, power on, no de-emphasis */ + /* I2S data up to 24-bit 64xFs internal SCLK */ + I2CWrite(saa, 0x22, 0x01, 0x11, 2); + /* ATAPI mixer settings */ + I2CWrite(saa, 0x22, 0x02, 0x49, 2); + /* attenuation left 3db */ + I2CWrite(saa, 0x22, 0x03, 0x00, 2); + /* attenuation right 3db */ + I2CWrite(saa, 0x22, 0x04, 0x00, 2); + I2CWrite(saa, 0x22, 0x01, 0x10, 2); + if (I2CRead(saa, 0x22, 0x02, 1) == 0x49) + break; + schedule(); + } + printk("stradis%d: CS4341 initialized (%d)\n", saa->nr, i); + return; +} + +static void initialize_cs8420(struct saa7146 *saa, int pro) +{ + int i; + u8 *sequence; + if (pro) + sequence = mode8420pro; + else + sequence = mode8420con; + for (i = 0; i < INIT8420LEN; i++) + I2CWrite(saa, 0x20, init8420[i * 2], + init8420[i * 2 + 1], 2); + for (i = 0; i < MODE8420LEN; i++) + I2CWrite(saa, 0x20, sequence[i * 2], + sequence[i * 2 + 1], 2); + printk("stradis%d: CS8420 initialized\n", saa->nr); +} + +static void initialize_saa7121(struct saa7146 *saa, int dopal) +{ + int i, mod; + u8 *sequence; + if (dopal) + sequence = init7121pal; + else + sequence = init7121ntsc; + mod = saaread(SAA7146_PSR) & 0x08; + /* initialize PAL/NTSC video encoder */ + for (i = 0; i < INIT7121LEN; i++) { + if (NewCard) { /* handle new card encoder differences */ + if (sequence[i*2] == 0x3a) + I2CWrite(saa, 0x88, 0x3a, 0x13, 2); + else if (sequence[i*2] == 0x6b) + I2CWrite(saa, 0x88, 0x6b, 0x20, 2); + else if (sequence[i*2] == 0x6c) + I2CWrite(saa, 0x88, 0x6c, + dopal ? 0x09 : 0xf5, 2); + else if (sequence[i*2] == 0x6d) + I2CWrite(saa, 0x88, 0x6d, + dopal ? 0x20 : 0x00, 2); + else if (sequence[i*2] == 0x7a) + I2CWrite(saa, 0x88, 0x7a, + dopal ? (PALFirstActive - 1) : + (NTSCFirstActive - 4), 2); + else if (sequence[i*2] == 0x7b) + I2CWrite(saa, 0x88, 0x7b, + dopal ? PALLastActive : + NTSCLastActive, 2); + else I2CWrite(saa, 0x88, sequence[i * 2], + sequence[i * 2 + 1], 2); + } else { + if (sequence[i*2] == 0x6b && mod) + I2CWrite(saa, 0x88, 0x6b, + (sequence[i * 2 + 1] ^ 0x09), 2); + else if (sequence[i*2] == 0x7a) + I2CWrite(saa, 0x88, 0x7a, + dopal ? (PALFirstActive - 1) : + (NTSCFirstActive - 4), 2); + else if (sequence[i*2] == 0x7b) + I2CWrite(saa, 0x88, 0x7b, + dopal ? PALLastActive : + NTSCLastActive, 2); + else + I2CWrite(saa, 0x88, sequence[i * 2], + sequence[i * 2 + 1], 2); + } + } +} + +static void set_genlock_offset(struct saa7146 *saa, int noffset) +{ + int nCode; + int PixelsPerLine = 858; + if (CurrentMode == VIDEO_MODE_PAL) + PixelsPerLine = 864; + if (noffset > 500) + noffset = 500; + else if (noffset < -500) + noffset = -500; + nCode = noffset + 0x100; + if (nCode == 1) + nCode = 0x401; + else if (nCode < 1) nCode = 0x400 + PixelsPerLine + nCode; + debiwrite(saa, debNormal, XILINX_GLDELAY, nCode, 2); +} + +static void set_out_format(struct saa7146 *saa, int mode) +{ + initialize_saa7121(saa, (mode == VIDEO_MODE_NTSC ? 0 : 1)); + saa->boardcfg[2] = mode; + /* do not adjust analog video parameters here, use saa7121 init */ + /* you will affect the SDI output on the new card */ + if (mode == VIDEO_MODE_PAL) { /* PAL */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x0808, 2); + mdelay(50); + saawrite(0x012002c0, SAA7146_NUM_LINE_BYTE1); + if (NewCard) { + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + 0xe100, 2); + mdelay(50); + } + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + NewCard ? 0xe500: 0x6500, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_DLY, + (1 << 8) | + (NewCard ? PALFirstActive : PALFirstActive-6), 2); + } else { /* NTSC */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x0800, 2); + mdelay(50); + saawrite(0x00f002c0, SAA7146_NUM_LINE_BYTE1); + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + NewCard ? 0xe100: 0x6100, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_DLY, + (1 << 8) | + (NewCard ? NTSCFirstActive : NTSCFirstActive-6), 2); + } +} + + +/* Intialize bitmangler to map from a byte value to the mangled word that + * must be output to program the Xilinx part through the DEBI port. + * Xilinx Data Bit->DEBI Bit: 0->15 1->7 2->6 3->12 4->11 5->2 6->1 7->0 + * transfer FPGA code, init IBM chip, transfer IBM microcode + * rev2 card mangles: 0->7 1->6 2->5 3->4 4->3 5->2 6->1 7->0 + */ +static u16 bitmangler[256]; + +static int initialize_fpga(struct video_code *bitdata) +{ + int i, num, startindex, failure = 0, loadtwo, loadfile = 0; + u16 *dmabuf; + u8 *newdma; + struct saa7146 *saa; + + /* verify fpga code */ + for (startindex = 0; startindex < bitdata->datasize; startindex++) + if (bitdata->data[startindex] == 255) + break; + if (startindex == bitdata->datasize) { + printk(KERN_INFO "stradis: bad fpga code\n"); + return -1; + } + /* initialize all detected cards */ + for (num = 0; num < saa_num; num++) { + saa = &saa7146s[num]; + if (saa->boardcfg[0] > 20) + continue; /* card was programmed */ + loadtwo = (saa->boardcfg[18] & 0x10); + if (!NewCard) /* we have an old board */ + for (i = 0; i < 256; i++) + bitmangler[i] = ((i & 0x01) << 15) | + ((i & 0x02) << 6) | ((i & 0x04) << 4) | + ((i & 0x08) << 9) | ((i & 0x10) << 7) | + ((i & 0x20) >> 3) | ((i & 0x40) >> 5) | + ((i & 0x80) >> 7); + else /* else we have a new board */ + for (i = 0; i < 256; i++) + bitmangler[i] = ((i & 0x01) << 7) | + ((i & 0x02) << 5) | ((i & 0x04) << 3) | + ((i & 0x08) << 1) | ((i & 0x10) >> 1) | + ((i & 0x20) >> 3) | ((i & 0x40) >> 5) | + ((i & 0x80) >> 7); + + dmabuf = (u16 *) saa->dmadebi; + newdma = (u8 *) saa->dmadebi; + if (NewCard) { /* SDM2xxx */ + if (!strncmp(bitdata->loadwhat, "decoder2", 8)) + continue; /* fpga not for this card */ + if (!strncmp(&saa->boardcfg[42], + bitdata->loadwhat, 8)) { + loadfile = 1; + } else if (loadtwo && !strncmp(&saa->boardcfg[19], + bitdata->loadwhat, 8)) { + loadfile = 2; + } else if (!saa->boardcfg[42] && /* special */ + !strncmp("decxl", bitdata->loadwhat, 8)) { + loadfile = 1; + } else + continue; /* fpga not for this card */ + if (loadfile != 1 && loadfile != 2) { + continue; /* skip to next card */ + } + if (saa->boardcfg[0] && loadfile == 1 ) + continue; /* skip to next card */ + if (saa->boardcfg[0] != 1 && loadfile == 2) + continue; /* skip to next card */ + saa->boardcfg[0]++; /* mark fpga handled */ + printk("stradis%d: loading %s\n", saa->nr, + bitdata->loadwhat); + if (loadtwo && loadfile == 2) + goto send_fpga_stuff; + /* turn on the Audio interface to set PROG low */ + saawrite(0x00400040, SAA7146_GPIO_CTRL); + saaread(SAA7146_PSR); /* ensure posted write */ + /* wait for everyone to reset */ + mdelay(10); + saawrite(0x00400000, SAA7146_GPIO_CTRL); + } else { /* original card */ + if (strncmp(bitdata->loadwhat, "decoder2", 8)) + continue; /* fpga not for this card */ + /* Pull the Xilinx PROG signal WS3 low */ + saawrite(0x02000200, SAA7146_MC1); + /* Turn on the Audio interface so can set PROG low */ + saawrite(0x000000c0, SAA7146_ACON1); + /* Pull the Xilinx INIT signal (GPIO2) low */ + saawrite(0x00400000, SAA7146_GPIO_CTRL); + /* Make sure everybody resets */ + saaread(SAA7146_PSR); /* ensure posted write */ + mdelay(10); + /* Release the Xilinx PROG signal */ + saawrite(0x00000000, SAA7146_ACON1); + /* Turn off the Audio interface */ + saawrite(0x02000000, SAA7146_MC1); + } + /* Release Xilinx INIT signal (WS2) */ + saawrite(0x00000000, SAA7146_GPIO_CTRL); + /* Wait for the INIT to go High */ + for (i = 0; i < 10000 && + !(saaread(SAA7146_PSR) & SAA7146_PSR_PIN2); i++) + schedule(); + if (i == 1000) { + printk(KERN_INFO "stradis%d: no fpga INIT\n", saa->nr); + return -1; + } +send_fpga_stuff: + if (NewCard) { + for (i = startindex; i < bitdata->datasize; i++) + newdma[i - startindex] = + bitmangler[bitdata->data[i]]; + debiwrite(saa, 0x01420000, 0, 0, + ((bitdata->datasize - startindex) + 5)); + if (loadtwo) { + if (loadfile == 1) { + printk("stradis%d: " + "awaiting 2nd FPGA bitfile\n", + saa->nr); + continue; /* skip to next card */ + } + + } + } else { + for (i = startindex; i < bitdata->datasize; i++) + dmabuf[i - startindex] = + bitmangler[bitdata->data[i]]; + debiwrite(saa, 0x014a0000, 0, 0, + ((bitdata->datasize - startindex) + 5) * 2); + } + for (i = 0; i < 1000 && + !(saaread(SAA7146_PSR) & SAA7146_PSR_PIN2); i++) + schedule(); + if (i == 1000) { + printk(KERN_INFO "stradis%d: FPGA load failed\n", + saa->nr); + failure++; + continue; + } + if (!NewCard) { + /* Pull the Xilinx INIT signal (GPIO2) low */ + saawrite(0x00400000, SAA7146_GPIO_CTRL); + saaread(SAA7146_PSR); /* ensure posted write */ + mdelay(2); + saawrite(0x00000000, SAA7146_GPIO_CTRL); + mdelay(2); + } + printk(KERN_INFO "stradis%d: FPGA Loaded\n", saa->nr); + saa->boardcfg[0] = 26; /* mark fpga programmed */ + /* set VXCO to its lowest frequency */ + debiwrite(saa, debNormal, XILINX_PWM, 0, 2); + if (NewCard) { + /* mute CS3310 */ + if (HaveCS3310) + debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, + 0, 2); + /* set VXCO to PWM mode, release reset, blank on */ + debiwrite(saa, debNormal, XILINX_CTL0, 0xffc4, 2); + mdelay(10); + /* unmute CS3310 */ + if (HaveCS3310) + debiwrite(saa, debNormal, XILINX_CTL0, + 0x2020, 2); + } + /* set source Black */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x1707, 2); + saa->boardcfg[4] = 22; /* set NTSC First Active Line */ + saa->boardcfg[5] = 23; /* set PAL First Active Line */ + saa->boardcfg[54] = 2; /* set NTSC Last Active Line - 256 */ + saa->boardcfg[55] = 54; /* set PAL Last Active Line - 256 */ + set_out_format(saa, VIDEO_MODE_NTSC); + mdelay(50); + /* begin IBM chip init */ + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 4, 2); + saaread(SAA7146_PSR); /* wait for reset */ + mdelay(5); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0, 2); + debiread(saa, debNormal, IBM_MP2_CHIP_CONTROL, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0x10, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_ADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_MODE, 0x2e, 2); + if (NewCard) { + mdelay(5); + /* set i2s rate converter to 48KHz */ + debiwrite(saa, debNormal, 0x80c0, 6, 2); + /* we must init CS8420 first since rev b pulls i2s */ + /* master clock low and CS4341 needs i2s master to */ + /* run the i2c port. */ + if (HaveCS8420) { + /* 0=consumer, 1=pro */ + initialize_cs8420(saa, 0); + } + mdelay(5); + if (HaveCS4341) + initialize_cs4341(saa); + } + debiwrite(saa, debNormal, IBM_MP2_INFC_CTL, 0x48, 2); + debiwrite(saa, debNormal, IBM_MP2_BEEP_CTL, 0xa000, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_LBOR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_TBOR, 0, 2); + if (NewCard) + set_genlock_offset(saa, 0); + debiwrite(saa, debNormal, IBM_MP2_FRNT_ATTEN, 0, 2); +#if 0 + /* enable genlock */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x8000, 2); +#else + /* disable genlock */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x8080, 2); +#endif + } + return failure; +} + +static int do_ibm_reset(struct saa7146 *saa) +{ + /* failure if decoder not previously programmed */ + if (saa->boardcfg[0] < 37) + return -EIO; + /* mute CS3310 */ + if (HaveCS3310) + debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, 0, 2); + /* disable interrupts */ + saawrite(0, SAA7146_IER); + saa->audhead = saa->audtail = 0; + saa->vidhead = saa->vidtail = 0; + /* tristate debi bus, disable debi transfers */ + saawrite(0x00880000, SAA7146_MC1); + /* ensure posted write */ + saaread(SAA7146_MC1); + mdelay(50); + /* re-enable debi transfers */ + saawrite(0x00880088, SAA7146_MC1); + /* set source Black */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x1707, 2); + /* begin IBM chip init */ + set_out_format(saa, CurrentMode); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 4, 2); + saaread(SAA7146_PSR); /* wait for reset */ + mdelay(5); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0, 2); + debiread(saa, debNormal, IBM_MP2_CHIP_CONTROL, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, ChipControl, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_MODE, 0x2e, 2); + if (NewCard) { + mdelay(5); + /* set i2s rate converter to 48KHz */ + debiwrite(saa, debNormal, 0x80c0, 6, 2); + /* we must init CS8420 first since rev b pulls i2s */ + /* master clock low and CS4341 needs i2s master to */ + /* run the i2c port. */ + if (HaveCS8420) { + /* 0=consumer, 1=pro */ + initialize_cs8420(saa, 1); + } + mdelay(5); + if (HaveCS4341) + initialize_cs4341(saa); + } + debiwrite(saa, debNormal, IBM_MP2_INFC_CTL, 0x48, 2); + debiwrite(saa, debNormal, IBM_MP2_BEEP_CTL, 0xa000, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_LBOR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_TBOR, 0, 2); + if (NewCard) + set_genlock_offset(saa, 0); + debiwrite(saa, debNormal, IBM_MP2_FRNT_ATTEN, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0x2000, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4552, 2); + if (ibm_send_command(saa, IBM_MP2_CONFIG_DECODER, + (ChipControl == 0x43 ? 0xe800 : 0xe000), 1)) { + printk(KERN_ERR "stradis%d: IBM config failed\n", saa->nr); + } + if (HaveCS3310) { + int i = CS3310MaxLvl; + debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, ((i<<8)|i), 2); + } + /* start video decoder */ + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, ChipControl, 2); + /* 256k vid, 3520 bytes aud */ + debiwrite(saa, debNormal, IBM_MP2_RB_THRESHOLD, 0x4037, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4573, 2); + ibm_send_command(saa, IBM_MP2_PLAY, 0, 0); + /* enable buffer threshold irq */ + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2); + /* clear pending interrupts */ + debiread(saa, debNormal, IBM_MP2_HOST_INT, 2); + debiwrite(saa, debNormal, XILINX_CTL0, 0x1711, 2); + return 0; +} + +/* load the decoder microcode */ +static int initialize_ibmmpeg2(struct video_code *microcode) +{ + int i, num; + struct saa7146 *saa; + + for (num = 0; num < saa_num; num++) { + saa = &saa7146s[num]; + /* check that FPGA is loaded */ + debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0xa55a, 2); + if ((i = debiread(saa, debNormal, IBM_MP2_OSD_SIZE, 2)) != + 0xa55a) { + printk(KERN_INFO "stradis%d: %04x != 0xa55a\n", + saa->nr, i); +#if 0 + return -1; +#endif + } + if (!strncmp(microcode->loadwhat, "decoder.vid", 11)) { + if (saa->boardcfg[0] > 27) + continue; /* skip to next card */ + /* load video control store */ + saa->boardcfg[1] = 0x13; /* no-sync default */ + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 1, 2); + debiwrite(saa, debNormal, IBM_MP2_PROC_IADDR, 0, 2); + for (i = 0; i < microcode->datasize / 2; i++) + debiwrite(saa, debNormal, IBM_MP2_PROC_IDATA, + (microcode->data[i * 2] << 8) | + microcode->data[i * 2 + 1], 2); + debiwrite(saa, debNormal, IBM_MP2_PROC_IADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + saa->boardcfg[0] = 28; + } + if (!strncmp(microcode->loadwhat, "decoder.aud", 11)) { + if (saa->boardcfg[0] > 35) + continue; /* skip to next card */ + /* load audio control store */ + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 1, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_IADDR, 0, 2); + for (i = 0; i < microcode->datasize; i++) + debiwrite(saa, debNormal, IBM_MP2_AUD_IDATA, + microcode->data[i], 1); + debiwrite(saa, debNormal, IBM_MP2_AUD_IADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0x2000, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4552, 2); + if (ibm_send_command(saa, IBM_MP2_CONFIG_DECODER, + 0xe000, 1)) { + printk(KERN_ERR + "stradis%d: IBM config failed\n", + saa->nr); + return -1; + } + /* set PWM to center value */ + if (NewCard) { + debiwrite(saa, debNormal, XILINX_PWM, + saa->boardcfg[14] + + (saa->boardcfg[13]<<8), 2); + } else + debiwrite(saa, debNormal, XILINX_PWM, + 0x46, 2); + if (HaveCS3310) { + i = CS3310MaxLvl; + debiwrite(saa, debNormal, + XILINX_CS3310_CMPLT, ((i<<8)|i), 2); + } + printk(KERN_INFO + "stradis%d: IBM MPEGCD%d Initialized\n", + saa->nr, 18 + (debiread(saa, debNormal, + IBM_MP2_CHIP_CONTROL, 2) >> 12)); + /* start video decoder */ + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + debiwrite(saa, debNormal, IBM_MP2_RB_THRESHOLD, + 0x4037, 2); /* 256k vid, 3520 bytes aud */ + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4573, 2); + ibm_send_command(saa, IBM_MP2_PLAY, 0, 0); + /* enable buffer threshold irq */ + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2); + debiread(saa, debNormal, IBM_MP2_HOST_INT, 2); + /* enable gpio irq */ + saawrite(0x00002000, SAA7146_GPIO_CTRL); + /* enable decoder output to HPS */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x1711, 2); + saa->boardcfg[0] = 37; + } + } + return 0; +} + +static u32 palette2fmt[] = +{ /* some of these YUV translations are wrong */ + 0xffffffff, 0x86000000, 0x87000000, 0x80000000, 0x8100000, 0x82000000, + 0x83000000, 0x00000000, 0x03000000, 0x03000000, 0x0a00000, 0x03000000, + 0x06000000, 0x00000000, 0x03000000, 0x0a000000, 0x0300000 +}; +static int bpp2fmt[4] = +{ + VIDEO_PALETTE_HI240, VIDEO_PALETTE_RGB565, VIDEO_PALETTE_RGB24, + VIDEO_PALETTE_RGB32 +}; + +/* I wish I could find a formula to calculate these... */ +static u32 h_prescale[64] = +{ + 0x10000000, 0x18040202, 0x18080000, 0x380c0606, 0x38100204, 0x38140808, + 0x38180000, 0x381c0000, 0x3820161c, 0x38242a3b, 0x38281230, 0x382c4460, + 0x38301040, 0x38340080, 0x38380000, 0x383c0000, 0x3840fefe, 0x3844ee9f, + 0x3848ee9f, 0x384cee9f, 0x3850ee9f, 0x38542a3b, 0x38581230, 0x385c0000, + 0x38600000, 0x38640000, 0x38680000, 0x386c0000, 0x38700000, 0x38740000, + 0x38780000, 0x387c0000, 0x30800000, 0x38840000, 0x38880000, 0x388c0000, + 0x38900000, 0x38940000, 0x38980000, 0x389c0000, 0x38a00000, 0x38a40000, + 0x38a80000, 0x38ac0000, 0x38b00000, 0x38b40000, 0x38b80000, 0x38bc0000, + 0x38c00000, 0x38c40000, 0x38c80000, 0x38cc0000, 0x38d00000, 0x38d40000, + 0x38d80000, 0x38dc0000, 0x38e00000, 0x38e40000, 0x38e80000, 0x38ec0000, + 0x38f00000, 0x38f40000, 0x38f80000, 0x38fc0000, +}; +static u32 v_gain[64] = +{ + 0x016000ff, 0x016100ff, 0x016100ff, 0x016200ff, 0x016200ff, 0x016200ff, + 0x016200ff, 0x016300ff, 0x016300ff, 0x016300ff, 0x016300ff, 0x016300ff, + 0x016300ff, 0x016300ff, 0x016300ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, +}; + + +static void saa7146_set_winsize(struct saa7146 *saa) +{ + u32 format; + int offset, yacl, ysci; + saa->win.color_fmt = format = + (saa->win.depth == 15) ? palette2fmt[VIDEO_PALETTE_RGB555] : + palette2fmt[bpp2fmt[(saa->win.bpp - 1) & 3]]; + offset = saa->win.x * saa->win.bpp + saa->win.y * saa->win.bpl; + saawrite(saa->win.vidadr + offset, SAA7146_BASE_EVEN1); + saawrite(saa->win.vidadr + offset + saa->win.bpl, SAA7146_BASE_ODD1); + saawrite(saa->win.bpl * 2, SAA7146_PITCH1); + saawrite(saa->win.vidadr + saa->win.bpl * saa->win.sheight, + SAA7146_PROT_ADDR1); + saawrite(0, SAA7146_PAGE1); + saawrite(format|0x60, SAA7146_CLIP_FORMAT_CTRL); + offset = (704 / (saa->win.width - 1)) & 0x3f; + saawrite(h_prescale[offset], SAA7146_HPS_H_PRESCALE); + offset = (720896 / saa->win.width) / (offset + 1); + saawrite((offset<<12)|0x0c, SAA7146_HPS_H_SCALE); + if (CurrentMode == VIDEO_MODE_NTSC) { + yacl = /*(480 / saa->win.height - 1) & 0x3f*/ 0; + ysci = 1024 - (saa->win.height * 1024 / 480); + } else { + yacl = /*(576 / saa->win.height - 1) & 0x3f*/ 0; + ysci = 1024 - (saa->win.height * 1024 / 576); + } + saawrite((1<<31)|(ysci<<21)|(yacl<<15), SAA7146_HPS_V_SCALE); + saawrite(v_gain[yacl], SAA7146_HPS_V_GAIN); + saawrite(((SAA7146_MC2_UPLD_DMA1 | SAA7146_MC2_UPLD_HPS_V | + SAA7146_MC2_UPLD_HPS_H) << 16) | (SAA7146_MC2_UPLD_DMA1 | + SAA7146_MC2_UPLD_HPS_V | SAA7146_MC2_UPLD_HPS_H), + SAA7146_MC2); +} + +/* clip_draw_rectangle(cm,x,y,w,h) -- handle clipping an area + * bitmap is fixed width, 128 bytes (1024 pixels represented) + * arranged most-sigificant-bit-left in 32-bit words + * based on saa7146 clipping hardware, it swaps bytes if LE + * much of this makes up for egcs brain damage -- so if you + * are wondering "why did he do this?" it is because the C + * was adjusted to generate the optimal asm output without + * writing non-portable __asm__ directives. + */ + +static void clip_draw_rectangle(u32 *clipmap, int x, int y, int w, int h) +{ + register int startword, endword; + register u32 bitsleft, bitsright; + u32 *temp; + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + if (w <= 0 || h <= 0 || x > 1023 || y > 639) + return; /* throw away bad clips */ + if (x + w > 1024) + w = 1024 - x; + if (y + h > 640) + h = 640 - y; + startword = (x >> 5); + endword = ((x + w) >> 5); + bitsleft = (0xffffffff >> (x & 31)); + bitsright = (0xffffffff << (~((x + w) - (endword<<5)))); + temp = &clipmap[(y<<5) + startword]; + w = endword - startword; + if (!w) { + bitsleft |= bitsright; + for (y = 0; y < h; y++) { + *temp |= bitsleft; + temp += 32; + } + } else { + for (y = 0; y < h; y++) { + *temp++ |= bitsleft; + for (x = 1; x < w; x++) + *temp++ = 0xffffffff; + *temp |= bitsright; + temp += (32 - w); + } + } +} + +static void make_clip_tab(struct saa7146 *saa, struct video_clip *cr, int ncr) +{ + int i, width, height; + u32 *clipmap; + + clipmap = saa->dmavid2; + if((width=saa->win.width)>1023) + width = 1023; /* sanity check */ + if((height=saa->win.height)>640) + height = 639; /* sanity check */ + if (ncr > 0) { /* rectangles pased */ + /* convert rectangular clips to a bitmap */ + memset(clipmap, 0, VIDEO_CLIPMAP_SIZE); /* clear map */ + for (i = 0; i < ncr; i++) + clip_draw_rectangle(clipmap, cr[i].x, cr[i].y, + cr[i].width, cr[i].height); + } + /* clip against viewing window AND screen + so we do not have to rely on the user program + */ + clip_draw_rectangle(clipmap,(saa->win.x+width>saa->win.swidth) ? + (saa->win.swidth-saa->win.x) : width, 0, 1024, 768); + clip_draw_rectangle(clipmap,0,(saa->win.y+height>saa->win.sheight) ? + (saa->win.sheight-saa->win.y) : height,1024,768); + if (saa->win.x<0) + clip_draw_rectangle(clipmap, 0, 0, -(saa->win.x), 768); + if (saa->win.y<0) + clip_draw_rectangle(clipmap, 0, 0, 1024, -(saa->win.y)); +} + +static int saa_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long argl) +{ + struct saa7146 *saa = file->private_data; + void __user *arg = (void __user *)argl; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability b; + strcpy(b.name, saa->video_dev.name); + b.type = VID_TYPE_CAPTURE | + VID_TYPE_OVERLAY | + VID_TYPE_CLIPPING | + VID_TYPE_FRAMERAM | + VID_TYPE_SCALES; + b.channels = 1; + b.audios = 1; + b.maxwidth = 768; + b.maxheight = 576; + b.minwidth = 32; + b.minheight = 32; + if (copy_to_user(arg, &b, sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p = saa->picture; + if (saa->win.depth == 8) + p.palette = VIDEO_PALETTE_HI240; + if (saa->win.depth == 15) + p.palette = VIDEO_PALETTE_RGB555; + if (saa->win.depth == 16) + p.palette = VIDEO_PALETTE_RGB565; + if (saa->win.depth == 24) + p.palette = VIDEO_PALETTE_RGB24; + if (saa->win.depth == 32) + p.palette = VIDEO_PALETTE_RGB32; + if (copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + u32 format; + if (copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + if (p.palette < sizeof(palette2fmt) / sizeof(u32)) { + format = palette2fmt[p.palette]; + saa->win.color_fmt = format; + saawrite(format|0x60, SAA7146_CLIP_FORMAT_CTRL); + } + saawrite(((p.brightness & 0xff00) << 16) | + ((p.contrast & 0xfe00) << 7) | + ((p.colour & 0xfe00) >> 9), SAA7146_BCS_CTRL); + saa->picture = p; + /* upload changed registers */ + saawrite(((SAA7146_MC2_UPLD_HPS_H | + SAA7146_MC2_UPLD_HPS_V) << 16) | + SAA7146_MC2_UPLD_HPS_H | SAA7146_MC2_UPLD_HPS_V, + SAA7146_MC2); + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip *vcp = NULL; + + if (copy_from_user(&vw, arg, sizeof(vw))) + return -EFAULT; + + if (vw.flags || vw.width < 16 || vw.height < 16) { /* stop capture */ + saawrite((SAA7146_MC1_TR_E_1 << 16), SAA7146_MC1); + return -EINVAL; + } + if (saa->win.bpp < 4) { /* 32-bit align start and adjust width */ + int i = vw.x; + vw.x = (vw.x + 3) & ~3; + i = vw.x - i; + vw.width -= i; + } + saa->win.x = vw.x; + saa->win.y = vw.y; + saa->win.width = vw.width; + if (saa->win.width > 768) + saa->win.width = 768; + saa->win.height = vw.height; + if (CurrentMode == VIDEO_MODE_NTSC) { + if (saa->win.height > 480) + saa->win.height = 480; + } else { + if (saa->win.height > 576) + saa->win.height = 576; + } + + /* stop capture */ + saawrite((SAA7146_MC1_TR_E_1 << 16), SAA7146_MC1); + saa7146_set_winsize(saa); + + /* + * Do any clips. + */ + if (vw.clipcount < 0) { + if (copy_from_user(saa->dmavid2, vw.clips, + VIDEO_CLIPMAP_SIZE)) + return -EFAULT; + } + else if (vw.clipcount > 16384) { + return -EINVAL; + } else if (vw.clipcount > 0) { + if ((vcp = vmalloc(sizeof(struct video_clip) * + (vw.clipcount))) == NULL) + return -ENOMEM; + if (copy_from_user(vcp, vw.clips, + sizeof(struct video_clip) * + vw.clipcount)) { + vfree(vcp); + return -EFAULT; + } + } else /* nothing clipped */ + memset(saa->dmavid2, 0, VIDEO_CLIPMAP_SIZE); + make_clip_tab(saa, vcp, vw.clipcount); + if (vw.clipcount > 0) + vfree(vcp); + + /* start capture & clip dma if we have an address */ + if ((saa->cap & 3) && saa->win.vidadr != 0) + saawrite(((SAA7146_MC1_TR_E_1 | + SAA7146_MC1_TR_E_2) << 16) | 0xffff, + SAA7146_MC1); + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + vw.x = saa->win.x; + vw.y = saa->win.y; + vw.width = saa->win.width; + vw.height = saa->win.height; + vw.chromakey = 0; + vw.flags = 0; + if (copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + { + int v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v == 0) { + saa->cap &= ~1; + saawrite((SAA7146_MC1_TR_E_1 << 16), + SAA7146_MC1); + } else { + if (saa->win.vidadr == 0 || saa->win.width == 0 + || saa->win.height == 0) + return -EINVAL; + saa->cap |= 1; + saawrite((SAA7146_MC1_TR_E_1 << 16) | 0xffff, + SAA7146_MC1); + } + return 0; + } + case VIDIOCGFBUF: + { + struct video_buffer v; + v.base = (void *) saa->win.vidadr; + v.height = saa->win.sheight; + v.width = saa->win.swidth; + v.depth = saa->win.depth; + v.bytesperline = saa->win.bpl; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + + } + case VIDIOCSFBUF: + { + struct video_buffer v; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.depth != 8 && v.depth != 15 && v.depth != 16 && + v.depth != 24 && v.depth != 32 && v.width > 16 && + v.height > 16 && v.bytesperline > 16) + return -EINVAL; + if (v.base) + saa->win.vidadr = (unsigned long) v.base; + saa->win.sheight = v.height; + saa->win.swidth = v.width; + saa->win.bpp = ((v.depth + 7) & 0x38) / 8; + saa->win.depth = v.depth; + saa->win.bpl = v.bytesperline; + + DEBUG(printk("Display at %p is %d by %d, bytedepth %d, bpl %d\n", + v.base, v.width, v.height, saa->win.bpp, saa->win.bpl)); + saa7146_set_winsize(saa); + return 0; + } + case VIDIOCKEY: + { + /* Will be handled higher up .. */ + return 0; + } + + case VIDIOCGAUDIO: + { + struct video_audio v; + v = saa->audio_dev; + v.flags &= ~(VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); + v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; + strcpy(v.name, "MPEG"); + v.mode = VIDEO_SOUND_STEREO; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + int i; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + i = (~(v.volume>>8))&0xff; + if (!HaveCS4341) { + if (v.flags & VIDEO_AUDIO_MUTE) { + debiwrite(saa, debNormal, + IBM_MP2_FRNT_ATTEN, + 0xffff, 2); + } + if (!(v.flags & VIDEO_AUDIO_MUTE)) + debiwrite(saa, debNormal, + IBM_MP2_FRNT_ATTEN, + 0x0000, 2); + if (v.flags & VIDEO_AUDIO_VOLUME) + debiwrite(saa, debNormal, + IBM_MP2_FRNT_ATTEN, + (i<<8)|i, 2); + } else { + if (v.flags & VIDEO_AUDIO_MUTE) + cs4341_setlevel(saa, 0xff, 0xff); + if (!(v.flags & VIDEO_AUDIO_MUTE)) + cs4341_setlevel(saa, 0, 0); + if (v.flags & VIDEO_AUDIO_VOLUME) + cs4341_setlevel(saa, i, i); + } + saa->audio_dev = v; + return 0; + } + + case VIDIOCGUNIT: + { + struct video_unit vu; + vu.video = saa->video_dev.minor; + vu.vbi = VIDEO_NO_UNIT; + vu.radio = VIDEO_NO_UNIT; + vu.audio = VIDEO_NO_UNIT; + vu.teletext = VIDEO_NO_UNIT; + if (copy_to_user(arg, &vu, sizeof(vu))) + return -EFAULT; + return 0; + } + case VIDIOCSPLAYMODE: + { + struct video_play_mode pmode; + if (copy_from_user((void *) &pmode, arg, + sizeof(struct video_play_mode))) + return -EFAULT; + switch (pmode.mode) { + case VID_PLAY_VID_OUT_MODE: + if (pmode.p1 != VIDEO_MODE_NTSC && + pmode.p1 != VIDEO_MODE_PAL) + return -EINVAL; + set_out_format(saa, pmode.p1); + return 0; + case VID_PLAY_GENLOCK: + debiwrite(saa, debNormal, + XILINX_CTL0, + (pmode.p1 ? 0x8000 : 0x8080), + 2); + if (NewCard) + set_genlock_offset(saa, + pmode.p2); + return 0; + case VID_PLAY_NORMAL: + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + ibm_send_command(saa, + IBM_MP2_PLAY, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_PAUSE: + /* IBM removed the PAUSE command */ + /* they say use SINGLE_FRAME now */ + case VID_PLAY_SINGLE_FRAME: + ibm_send_command(saa, + IBM_MP2_SINGLE_FRAME, + 0, 0); + if (saa->playmode == pmode.mode) { + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + } + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_FAST_FORWARD: + ibm_send_command(saa, + IBM_MP2_FAST_FORWARD, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_SLOW_MOTION: + ibm_send_command(saa, + IBM_MP2_SLOW_MOTION, + pmode.p1, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_IMMEDIATE_NORMAL: + /* ensure transfers resume */ + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + ibm_send_command(saa, + IBM_MP2_IMED_NORM_PLAY, 0, 0); + saa->playmode = VID_PLAY_NORMAL; + return 0; + case VID_PLAY_SWITCH_CHANNELS: + saa->audhead = saa->audtail = 0; + saa->vidhead = saa->vidtail = 0; + ibm_send_command(saa, + IBM_MP2_FREEZE_FRAME, 0, 1); + ibm_send_command(saa, + IBM_MP2_RESET_AUD_RATE, 0, 1); + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, 0, 2); + ibm_send_command(saa, + IBM_MP2_CHANNEL_SWITCH, 0, 1); + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + ibm_send_command(saa, + IBM_MP2_PLAY, 0, 0); + saa->playmode = VID_PLAY_NORMAL; + return 0; + case VID_PLAY_FREEZE_FRAME: + ibm_send_command(saa, + IBM_MP2_FREEZE_FRAME, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_STILL_MODE: + ibm_send_command(saa, + IBM_MP2_SET_STILL_MODE, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_MASTER_MODE: + if (pmode.p1 == VID_PLAY_MASTER_NONE) + saa->boardcfg[1] = 0x13; + else if (pmode.p1 == + VID_PLAY_MASTER_VIDEO) + saa->boardcfg[1] = 0x23; + else if (pmode.p1 == + VID_PLAY_MASTER_AUDIO) + saa->boardcfg[1] = 0x43; + else + return -EINVAL; + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + return 0; + case VID_PLAY_ACTIVE_SCANLINES: + if (CurrentMode == VIDEO_MODE_PAL) { + if (pmode.p1 < 1 || + pmode.p2 > 625) + return -EINVAL; + saa->boardcfg[5] = pmode.p1; + saa->boardcfg[55] = (pmode.p1 + + (pmode.p2/2) - 1) & + 0xff; + } else { + if (pmode.p1 < 4 || + pmode.p2 > 525) + return -EINVAL; + saa->boardcfg[4] = pmode.p1; + saa->boardcfg[54] = (pmode.p1 + + (pmode.p2/2) - 4) & + 0xff; + } + set_out_format(saa, CurrentMode); + case VID_PLAY_RESET: + return do_ibm_reset(saa); + case VID_PLAY_END_MARK: + if (saa->endmarktail < + saa->endmarkhead) { + if (saa->endmarkhead - + saa->endmarktail < 2) + return -ENOSPC; + } else if (saa->endmarkhead <= + saa->endmarktail) { + if (saa->endmarktail - + saa->endmarkhead > + (MAX_MARKS - 2)) + return -ENOSPC; + } else + return -ENOSPC; + saa->endmark[saa->endmarktail] = + saa->audtail; + saa->endmarktail++; + if (saa->endmarktail >= MAX_MARKS) + saa->endmarktail = 0; + } + return -EINVAL; + } + case VIDIOCSWRITEMODE: + { + int mode; + if (copy_from_user((void *) &mode, arg, sizeof(int))) + return -EFAULT; + if (mode == VID_WRITE_MPEG_AUD || + mode == VID_WRITE_MPEG_VID || + mode == VID_WRITE_CC || + mode == VID_WRITE_TTX || + mode == VID_WRITE_OSD) { + saa->writemode = mode; + return 0; + } + return -EINVAL; + } + case VIDIOCSMICROCODE: + { + struct video_code ucode; + __u8 *udata; + int i; + if (copy_from_user(&ucode, arg, sizeof(ucode))) + return -EFAULT; + if (ucode.datasize > 65536 || ucode.datasize < 1024 || + strncmp(ucode.loadwhat, "dec", 3)) + return -EINVAL; + if ((udata = vmalloc(ucode.datasize)) == NULL) + return -ENOMEM; + if (copy_from_user(udata, ucode.data, ucode.datasize)) { + vfree(udata); + return -EFAULT; + } + ucode.data = udata; + if (!strncmp(ucode.loadwhat, "decoder.aud", 11) + || !strncmp(ucode.loadwhat, "decoder.vid", 11)) + i = initialize_ibmmpeg2(&ucode); + else + i = initialize_fpga(&ucode); + vfree(udata); + if (i) + return -EINVAL; + return 0; + + } + case VIDIOCGCHAN: /* this makes xawtv happy */ + { + struct video_channel v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + v.flags = VIDEO_VC_AUDIO; + v.tuners = 0; + v.type = VID_TYPE_MPEG_DECODER; + v.norm = CurrentMode; + strcpy(v.name, "MPEG2"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: /* this makes xawtv happy */ + { + struct video_channel v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + /* do nothing */ + return 0; + } + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int saa_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct saa7146 *saa = file->private_data; + printk(KERN_DEBUG "stradis%d: saa_mmap called\n", saa->nr); + return -EINVAL; +} + +static ssize_t saa_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static ssize_t saa_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct saa7146 *saa = file->private_data; + unsigned long todo = count; + int blocksize, split; + unsigned long flags; + + while (todo > 0) { + if (saa->writemode == VID_WRITE_MPEG_AUD) { + spin_lock_irqsave(&saa->lock, flags); + if (saa->audhead <= saa->audtail) + blocksize = 65536-(saa->audtail - saa->audhead); + else + blocksize = saa->audhead - saa->audtail; + spin_unlock_irqrestore(&saa->lock, flags); + if (blocksize < 16384) { + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + saawrite(SAA7146_PSR_PIN1, SAA7146_PSR); + /* wait for buffer space to open */ + interruptible_sleep_on(&saa->audq); + } + spin_lock_irqsave(&saa->lock, flags); + if (saa->audhead <= saa->audtail) { + blocksize = 65536-(saa->audtail - saa->audhead); + split = 65536 - saa->audtail; + } else { + blocksize = saa->audhead - saa->audtail; + split = 65536; + } + spin_unlock_irqrestore(&saa->lock, flags); + blocksize--; + if (blocksize > todo) + blocksize = todo; + /* double check that we really have space */ + if (!blocksize) + return -ENOSPC; + if (split < blocksize) { + if (copy_from_user(saa->audbuf + + saa->audtail, buf, split)) + return -EFAULT; + buf += split; + todo -= split; + blocksize -= split; + saa->audtail = 0; + } + if (copy_from_user(saa->audbuf + saa->audtail, buf, + blocksize)) + return -EFAULT; + saa->audtail += blocksize; + todo -= blocksize; + buf += blocksize; + saa->audtail &= 0xffff; + } else if (saa->writemode == VID_WRITE_MPEG_VID) { + spin_lock_irqsave(&saa->lock, flags); + if (saa->vidhead <= saa->vidtail) + blocksize=524288-(saa->vidtail - saa->vidhead); + else + blocksize = saa->vidhead - saa->vidtail; + spin_unlock_irqrestore(&saa->lock, flags); + if (blocksize < 65536) { + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + saawrite(SAA7146_PSR_PIN1, SAA7146_PSR); + /* wait for buffer space to open */ + interruptible_sleep_on(&saa->vidq); + } + spin_lock_irqsave(&saa->lock, flags); + if (saa->vidhead <= saa->vidtail) { + blocksize=524288-(saa->vidtail - saa->vidhead); + split = 524288 - saa->vidtail; + } else { + blocksize = saa->vidhead - saa->vidtail; + split = 524288; + } + spin_unlock_irqrestore(&saa->lock, flags); + blocksize--; + if (blocksize > todo) + blocksize = todo; + /* double check that we really have space */ + if (!blocksize) + return -ENOSPC; + if (split < blocksize) { + if (copy_from_user(saa->vidbuf + + saa->vidtail, buf, split)) + return -EFAULT; + buf += split; + todo -= split; + blocksize -= split; + saa->vidtail = 0; + } + if (copy_from_user(saa->vidbuf + saa->vidtail, buf, + blocksize)) + return -EFAULT; + saa->vidtail += blocksize; + todo -= blocksize; + buf += blocksize; + saa->vidtail &= 0x7ffff; + } else if (saa->writemode == VID_WRITE_OSD) { + if (count > 131072) + return -ENOSPC; + if (copy_from_user(saa->osdbuf, buf, count)) + return -EFAULT; + buf += count; + saa->osdhead = 0; + saa->osdtail = count; + debiwrite(saa, debNormal, IBM_MP2_OSD_ADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_OSD_LINK_ADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00d, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + debiread(saa, debNormal, + IBM_MP2_DISP_MODE, 2) | 1, 2); + /* trigger osd data transfer */ + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + saawrite(SAA7146_PSR_PIN1, SAA7146_PSR); + } + } + return count; +} + +static int saa_open(struct inode *inode, struct file *file) +{ + struct saa7146 *saa = NULL; + unsigned int minor = iminor(inode); + int i; + + for (i = 0; i < SAA7146_MAX; i++) { + if (saa7146s[i].video_dev.minor == minor) { + saa = &saa7146s[i]; + } + } + if (saa == NULL) { + return -ENODEV; + } + file->private_data = saa; + + //saa->video_dev.busy = 0; /* old hack to support multiple open */ + saa->user++; + if (saa->user > 1) + return 0; /* device open already, don't reset */ + saa->writemode = VID_WRITE_MPEG_VID; /* default to video */ + return 0; +} + +static int saa_release(struct inode *inode, struct file *file) +{ + struct saa7146 *saa = file->private_data; + saa->user--; + //saa->video_dev.busy = 0; /* old hack to support multiple open */ + if (saa->user > 0) /* still someone using device */ + return 0; + saawrite(0x007f0000, SAA7146_MC1); /* stop all overlay dma */ + return 0; +} + +static struct file_operations saa_fops = +{ + .owner = THIS_MODULE, + .open = saa_open, + .release = saa_release, + .ioctl = saa_ioctl, + .read = saa_read, + .llseek = no_llseek, + .write = saa_write, + .mmap = saa_mmap, +}; + +/* template for video_device-structure */ +static struct video_device saa_template = +{ + .name = "SAA7146A", + .type = VID_TYPE_CAPTURE | VID_TYPE_OVERLAY, + .hardware = VID_HARDWARE_SAA7146, + .fops = &saa_fops, + .minor = -1, +}; + +static int configure_saa7146(struct pci_dev *dev, int num) +{ + int result; + struct saa7146 *saa; + + saa = &saa7146s[num]; + + saa->endmarkhead = saa->endmarktail = 0; + saa->win.x = saa->win.y = 0; + saa->win.width = saa->win.cropwidth = 720; + saa->win.height = saa->win.cropheight = 480; + saa->win.cropx = saa->win.cropy = 0; + saa->win.bpp = 2; + saa->win.depth = 16; + saa->win.color_fmt = palette2fmt[VIDEO_PALETTE_RGB565]; + saa->win.bpl = 1024 * saa->win.bpp; + saa->win.swidth = 1024; + saa->win.sheight = 768; + saa->picture.brightness = 32768; + saa->picture.contrast = 38768; + saa->picture.colour = 32768; + saa->cap = 0; + saa->dev = dev; + saa->nr = num; + saa->playmode = VID_PLAY_NORMAL; + memset(saa->boardcfg, 0, 64); /* clear board config area */ + saa->saa7146_mem = NULL; + saa->dmavid1 = saa->dmavid2 = saa->dmavid3 = saa->dmaa1in = + saa->dmaa1out = saa->dmaa2in = saa->dmaa2out = + saa->pagevid1 = saa->pagevid2 = saa->pagevid3 = saa->pagea1in = + saa->pagea1out = saa->pagea2in = saa->pagea2out = + saa->pagedebi = saa->dmaRPS1 = saa->dmaRPS2 = saa->pageRPS1 = + saa->pageRPS2 = NULL; + saa->audbuf = saa->vidbuf = saa->osdbuf = saa->dmadebi = NULL; + saa->audhead = saa->vidtail = 0; + + init_waitqueue_head(&saa->i2cq); + init_waitqueue_head(&saa->audq); + init_waitqueue_head(&saa->debiq); + init_waitqueue_head(&saa->vidq); + spin_lock_init(&saa->lock); + + if (pci_enable_device(dev)) + return -EIO; + + saa->id = dev->device; + saa->irq = dev->irq; + saa->video_dev.minor = -1; + saa->saa7146_adr = pci_resource_start(dev, 0); + pci_read_config_byte(dev, PCI_CLASS_REVISION, &saa->revision); + + saa->saa7146_mem = ioremap(saa->saa7146_adr, 0x200); + if (!saa->saa7146_mem) + return -EIO; + + memcpy(&saa->video_dev, &saa_template, sizeof(saa_template)); + saawrite(0, SAA7146_IER); /* turn off all interrupts */ + result = request_irq(saa->irq, saa7146_irq, + SA_SHIRQ | SA_INTERRUPT, "stradis", (void *) saa); + if (result == -EINVAL) + printk(KERN_ERR "stradis%d: Bad irq number or handler\n", + num); + if (result == -EBUSY) + printk(KERN_ERR "stradis%d: IRQ %ld busy, change your PnP" + " config in BIOS\n", num, saa->irq); + if (result < 0) { + iounmap(saa->saa7146_mem); + return result; + } + pci_set_master(dev); + if (video_register_device(&saa->video_dev, VFL_TYPE_GRABBER, video_nr) < 0) { + iounmap(saa->saa7146_mem); + return -1; + } + return 0; +} + +static int init_saa7146(int i) +{ + struct saa7146 *saa = &saa7146s[i]; + + saa->user = 0; + /* reset the saa7146 */ + saawrite(0xffff0000, SAA7146_MC1); + mdelay(5); + /* enable debi and i2c transfers and pins */ + saawrite(((SAA7146_MC1_EDP | SAA7146_MC1_EI2C | + SAA7146_MC1_TR_E_DEBI) << 16) | 0xffff, SAA7146_MC1); + /* ensure proper state of chip */ + saawrite(0x00000000, SAA7146_PAGE1); + saawrite(0x00f302c0, SAA7146_NUM_LINE_BYTE1); + saawrite(0x00000000, SAA7146_PAGE2); + saawrite(0x01400080, SAA7146_NUM_LINE_BYTE2); + saawrite(0x00000000, SAA7146_DD1_INIT); + saawrite(0x00000000, SAA7146_DD1_STREAM_B); + saawrite(0x00000000, SAA7146_DD1_STREAM_A); + saawrite(0x00000000, SAA7146_BRS_CTRL); + saawrite(0x80400040, SAA7146_BCS_CTRL); + saawrite(0x0000e000 /*| (1<<29)*/, SAA7146_HPS_CTRL); + saawrite(0x00000060, SAA7146_CLIP_FORMAT_CTRL); + saawrite(0x00000000, SAA7146_ACON1); + saawrite(0x00000000, SAA7146_ACON2); + saawrite(0x00000600, SAA7146_I2C_STATUS); + saawrite(((SAA7146_MC2_UPLD_D1_B | SAA7146_MC2_UPLD_D1_A | + SAA7146_MC2_UPLD_BRS | SAA7146_MC2_UPLD_HPS_H | + SAA7146_MC2_UPLD_HPS_V | SAA7146_MC2_UPLD_DMA2 | + SAA7146_MC2_UPLD_DMA1 | SAA7146_MC2_UPLD_I2C) << 16) | 0xffff, + SAA7146_MC2); + /* setup arbitration control registers */ + saawrite(0x1412121a, SAA7146_PCI_BT_V1); + + /* allocate 32k dma buffer + 4k for page table */ + if ((saa->dmadebi = kmalloc(32768 + 4096, GFP_KERNEL)) == NULL) { + printk(KERN_ERR "stradis%d: debi kmalloc failed\n", i); + return -1; + } +#if 0 + saa->pagedebi = saa->dmadebi + 32768; /* top 4k is for mmu */ + saawrite(virt_to_bus(saa->pagedebi) /*|0x800 */ , SAA7146_DEBI_PAGE); + for (i = 0; i < 12; i++) /* setup mmu page table */ + saa->pagedebi[i] = virt_to_bus((saa->dmadebi + i * 4096)); +#endif + saa->audhead = saa->vidhead = saa->osdhead = 0; + saa->audtail = saa->vidtail = saa->osdtail = 0; + if (saa->vidbuf == NULL) + if ((saa->vidbuf = vmalloc(524288)) == NULL) { + printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr); + return -ENOMEM; + } + if (saa->audbuf == NULL) + if ((saa->audbuf = vmalloc(65536)) == NULL) { + printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr); + vfree(saa->vidbuf); + saa->vidbuf = NULL; + return -ENOMEM; + } + if (saa->osdbuf == NULL) + if ((saa->osdbuf = vmalloc(131072)) == NULL) { + printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr); + vfree(saa->vidbuf); + vfree(saa->audbuf); + saa->vidbuf = saa->audbuf = NULL; + return -ENOMEM; + } + /* allocate 81920 byte buffer for clipping */ + if ((saa->dmavid2 = kmalloc(VIDEO_CLIPMAP_SIZE, GFP_KERNEL)) == NULL) { + printk(KERN_ERR "stradis%d: clip kmalloc failed\n", saa->nr); + vfree(saa->vidbuf); + vfree(saa->audbuf); + vfree(saa->osdbuf); + saa->vidbuf = saa->audbuf = saa->osdbuf = NULL; + saa->dmavid2 = NULL; + return -1; + } + memset(saa->dmavid2, 0x00, VIDEO_CLIPMAP_SIZE); /* clip everything */ + /* setup clipping registers */ + saawrite(virt_to_bus(saa->dmavid2), SAA7146_BASE_EVEN2); + saawrite(virt_to_bus(saa->dmavid2) + 128, SAA7146_BASE_ODD2); + saawrite(virt_to_bus(saa->dmavid2) + VIDEO_CLIPMAP_SIZE, + SAA7146_PROT_ADDR2); + saawrite(256, SAA7146_PITCH2); + saawrite(4, SAA7146_PAGE2); /* dma direction: read, no byteswap */ + saawrite(((SAA7146_MC2_UPLD_DMA2) << 16) | SAA7146_MC2_UPLD_DMA2, + SAA7146_MC2); + I2CBusScan(saa); + return 0; +} + +static void release_saa(void) +{ + u8 command; + int i; + struct saa7146 *saa; + + for (i = 0; i < saa_num; i++) { + saa = &saa7146s[i]; + + /* turn off all capturing, DMA and IRQs */ + saawrite(0xffff0000, SAA7146_MC1); /* reset chip */ + saawrite(0, SAA7146_MC2); + saawrite(0, SAA7146_IER); + saawrite(0xffffffffUL, SAA7146_ISR); + + /* disable PCI bus-mastering */ + pci_read_config_byte(saa->dev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte(saa->dev, PCI_COMMAND, command); + + /* unmap and free memory */ + saa->audhead = saa->audtail = saa->osdhead = 0; + saa->vidhead = saa->vidtail = saa->osdtail = 0; + vfree(saa->vidbuf); + vfree(saa->audbuf); + vfree(saa->osdbuf); + if (saa->dmavid2) + kfree((void *) saa->dmavid2); + saa->audbuf = saa->vidbuf = saa->osdbuf = NULL; + saa->dmavid2 = NULL; + if (saa->dmadebi) + kfree((void *) saa->dmadebi); + if (saa->dmavid1) + kfree((void *) saa->dmavid1); + if (saa->dmavid2) + kfree((void *) saa->dmavid2); + if (saa->dmavid3) + kfree((void *) saa->dmavid3); + if (saa->dmaa1in) + kfree((void *) saa->dmaa1in); + if (saa->dmaa1out) + kfree((void *) saa->dmaa1out); + if (saa->dmaa2in) + kfree((void *) saa->dmaa2in); + if (saa->dmaa2out) + kfree((void *) saa->dmaa2out); + if (saa->dmaRPS1) + kfree((void *) saa->dmaRPS1); + if (saa->dmaRPS2) + kfree((void *) saa->dmaRPS2); + free_irq(saa->irq, saa); + if (saa->saa7146_mem) + iounmap(saa->saa7146_mem); + if (saa->video_dev.minor != -1) + video_unregister_device(&saa->video_dev); + } +} + + +static int __init stradis_init (void) +{ + struct pci_dev *dev = NULL; + int result = 0, i; + + saa_num = 0; + + while ((dev = pci_find_device(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, dev))) { + if (!dev->subsystem_vendor) + printk(KERN_INFO "stradis%d: rev1 decoder\n", saa_num); + else + printk(KERN_INFO "stradis%d: SDM2xx found\n", saa_num); + result = configure_saa7146(dev, saa_num++); + if (result) + return result; + } + if (saa_num) + printk(KERN_INFO "stradis: %d card(s) found.\n", saa_num); + else + return -EINVAL; + for (i = 0; i < saa_num; i++) + if (init_saa7146(i) < 0) { + release_saa(); + return -EIO; + } + return 0; +} + + +static void __exit stradis_exit (void) +{ + release_saa(); + printk(KERN_INFO "stradis: module cleanup complete\n"); +} + + +module_init(stradis_init); +module_exit(stradis_exit); + diff --git a/drivers/media/video/tda7432.c b/drivers/media/video/tda7432.c new file mode 100644 index 000000000000..376a4a439e9b --- /dev/null +++ b/drivers/media/video/tda7432.c @@ -0,0 +1,556 @@ +/* + * For the STS-Thompson TDA7432 audio processor chip + * + * Handles audio functions: volume, balance, tone, loudness + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Muting and tone control by Jonathan Isom <jisom@ematic.com> + * + * Copyright (c) 2000 Eric Sandeen <eric_sandeen@bigfoot.com> + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * set to 2 if you'd like to be inundated with debug messages + * + * loudness - set between 0 and 15 for varying degrees of loudness effect + * + * maxvol - set maximium volume to +20db (1), default is 0db(0) + * + * + * Revision: 0.7 - maxvol module parm to set maximium volume 0db or +20db + * store if muted so we can return it + * change balance only if flaged to + * Revision: 0.6 - added tone controls + * Revision: 0.5 - Fixed odd balance problem + * Revision: 0.4 - added muting + * Revision: 0.3 - Fixed silly reversed volume controls. :) + * Revision: 0.2 - Cleaned up #defines + * fixed volume control + * Added I2C_DRIVERID_TDA7432 + * added loudness insmod control + * Revision: 0.1 - initial version + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include <media/audiochip.h> +#include <media/id.h> + +#ifndef VIDEO_AUDIO_BALANCE +# define VIDEO_AUDIO_BALANCE 32 +#endif + +MODULE_AUTHOR("Eric Sandeen <eric_sandeen@bigfoot.com>"); +MODULE_DESCRIPTION("bttv driver for the tda7432 audio processor chip"); +MODULE_LICENSE("GPL"); + +static int maxvol; +static int loudness; /* disable loudness by default */ +static int debug; /* insmod parameter */ +module_param(debug, int, S_IRUGO | S_IWUSR); +module_param(loudness, int, S_IRUGO); +MODULE_PARM_DESC(maxvol,"Set maximium volume to +20db (0), default is 0db(1)"); +module_param(maxvol, int, S_IRUGO | S_IWUSR); + + +/* Address to scan (I2C address of this chip) */ +static unsigned short normal_i2c[] = { + I2C_TDA7432 >> 1, + I2C_CLIENT_END, +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END, I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +/* Structure of address and subaddresses for the tda7432 */ + +struct tda7432 { + int addr; + int input; + int volume; + int muted; + int bass, treble; + int lf, lr, rf, rr; + int loud; + struct i2c_client c; +}; +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk +#define d2printk if (debug > 1) printk + +/* The TDA7432 is made by STS-Thompson + * http://www.st.com + * http://us.st.com/stonline/books/pdf/docs/4056.pdf + * + * TDA7432: I2C-bus controlled basic audio processor + * + * The TDA7432 controls basic audio functions like volume, balance, + * and tone control (including loudness). It also has four channel + * output (for front and rear). Since most vidcap cards probably + * don't have 4 channel output, this driver will set front & rear + * together (no independent control). + */ + + /* Subaddresses for TDA7432 */ + +#define TDA7432_IN 0x00 /* Input select */ +#define TDA7432_VL 0x01 /* Volume */ +#define TDA7432_TN 0x02 /* Bass, Treble (Tone) */ +#define TDA7432_LF 0x03 /* Attenuation LF (Left Front) */ +#define TDA7432_LR 0x04 /* Attenuation LR (Left Rear) */ +#define TDA7432_RF 0x05 /* Attenuation RF (Right Front) */ +#define TDA7432_RR 0x06 /* Attenuation RR (Right Rear) */ +#define TDA7432_LD 0x07 /* Loudness */ + + + /* Masks for bits in TDA7432 subaddresses */ + +/* Many of these not used - just for documentation */ + +/* Subaddress 0x00 - Input selection and bass control */ + +/* Bits 0,1,2 control input: + * 0x00 - Stereo input + * 0x02 - Mono input + * 0x03 - Mute (Using Attenuators Plays better with modules) + * Mono probably isn't used - I'm guessing only the stereo + * input is connected on most cards, so we'll set it to stereo. + * + * Bit 3 controls bass cut: 0/1 is non-symmetric/symmetric bass cut + * Bit 4 controls bass range: 0/1 is extended/standard bass range + * + * Highest 3 bits not used + */ + +#define TDA7432_STEREO_IN 0 +#define TDA7432_MONO_IN 2 /* Probably won't be used */ +#define TDA7432_BASS_SYM 1 << 3 +#define TDA7432_BASS_NORM 1 << 4 + +/* Subaddress 0x01 - Volume */ + +/* Lower 7 bits control volume from -79dB to +32dB in 1dB steps + * Recommended maximum is +20 dB + * + * +32dB: 0x00 + * +20dB: 0x0c + * 0dB: 0x20 + * -79dB: 0x6f + * + * MSB (bit 7) controls loudness: 1/0 is loudness on/off + */ + +#define TDA7432_VOL_0DB 0x20 +#define TDA7432_LD_ON 1 << 7 + + +/* Subaddress 0x02 - Tone control */ + +/* Bits 0,1,2 control absolute treble gain from 0dB to 14dB + * 0x0 is 14dB, 0x7 is 0dB + * + * Bit 3 controls treble attenuation/gain (sign) + * 1 = gain (+) + * 0 = attenuation (-) + * + * Bits 4,5,6 control absolute bass gain from 0dB to 14dB + * (This is only true for normal base range, set in 0x00) + * 0x0 << 4 is 14dB, 0x7 is 0dB + * + * Bit 7 controls bass attenuation/gain (sign) + * 1 << 7 = gain (+) + * 0 << 7 = attenuation (-) + * + * Example: + * 1 1 0 1 0 1 0 1 is +4dB bass, -4dB treble + */ + +#define TDA7432_TREBLE_0DB 0xf +#define TDA7432_TREBLE 7 +#define TDA7432_TREBLE_GAIN 1 << 3 +#define TDA7432_BASS_0DB 0xf +#define TDA7432_BASS 7 << 4 +#define TDA7432_BASS_GAIN 1 << 7 + + +/* Subaddress 0x03 - Left Front attenuation */ +/* Subaddress 0x04 - Left Rear attenuation */ +/* Subaddress 0x05 - Right Front attenuation */ +/* Subaddress 0x06 - Right Rear attenuation */ + +/* Bits 0,1,2,3,4 control attenuation from 0dB to -37.5dB + * in 1.5dB steps. + * + * 0x00 is 0dB + * 0x1f is -37.5dB + * + * Bit 5 mutes that channel when set (1 = mute, 0 = unmute) + * We'll use the mute on the input, though (above) + * Bits 6,7 unused + */ + +#define TDA7432_ATTEN_0DB 0x00 +#define TDA7432_MUTE 0x1 << 5 + + +/* Subaddress 0x07 - Loudness Control */ + +/* Bits 0,1,2,3 control loudness from 0dB to -15dB in 1dB steps + * when bit 4 is NOT set + * + * 0x0 is 0dB + * 0xf is -15dB + * + * If bit 4 is set, then there is a flat attenuation according to + * the lower 4 bits, as above. + * + * Bits 5,6,7 unused + */ + + + +/* Begin code */ + +static int tda7432_write(struct i2c_client *client, int subaddr, int val) +{ + unsigned char buffer[2]; + d2printk("tda7432: In tda7432_write\n"); + dprintk("tda7432: Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda7432: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +/* I don't think we ever actually _read_ the chip... */ +#if 0 +static int tda7432_read(struct i2c_client *client) +{ + unsigned char buffer; + d2printk("tda7432: In tda7432_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda7432: I/O error, trying (read)\n"); + return -1; + } + dprintk("tda7432: Read 0x%02x\n", buffer); + return buffer; +} +#endif + +static int tda7432_set(struct i2c_client *client) +{ + struct tda7432 *t = i2c_get_clientdata(client); + unsigned char buf[16]; + d2printk("tda7432: In tda7432_set\n"); + + dprintk(KERN_INFO + "tda7432: 7432_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n", + t->input,t->volume,t->bass,t->treble,t->lf,t->lr,t->rf,t->rr,t->loud); + buf[0] = TDA7432_IN; + buf[1] = t->input; + buf[2] = t->volume; + buf[3] = t->bass; + buf[4] = t->treble; + buf[5] = t->lf; + buf[6] = t->lr; + buf[7] = t->rf; + buf[8] = t->rr; + buf[9] = t->loud; + if (10 != i2c_master_send(client,buf,10)) { + printk(KERN_WARNING "tda7432: I/O error, trying tda7432_set\n"); + return -1; + } + + return 0; +} + +static void do_tda7432_init(struct i2c_client *client) +{ + struct tda7432 *t = i2c_get_clientdata(client); + d2printk("tda7432: In tda7432_init\n"); + + t->input = TDA7432_STEREO_IN | /* Main (stereo) input */ + TDA7432_BASS_SYM | /* Symmetric bass cut */ + TDA7432_BASS_NORM; /* Normal bass range */ + t->volume = 0x3b ; /* -27dB Volume */ + if (loudness) /* Turn loudness on? */ + t->volume |= TDA7432_LD_ON; + t->muted = VIDEO_AUDIO_MUTE; + t->treble = TDA7432_TREBLE_0DB; /* 0dB Treble */ + t->bass = TDA7432_BASS_0DB; /* 0dB Bass */ + t->lf = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->lr = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->rf = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->rr = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->loud = loudness; /* insmod parameter */ + + tda7432_set(client); +} + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda7432_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct tda7432 *t; + struct i2c_client *client; + d2printk("tda7432: In tda7432_attach\n"); + + t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + + client = &t->c; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + i2c_set_clientdata(client, t); + + do_tda7432_init(client); + printk(KERN_INFO "tda7432: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda7432_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tda7432_attach); +#else + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda7432_attach); +#endif + return 0; +} + +static int tda7432_detach(struct i2c_client *client) +{ + struct tda7432 *t = i2c_get_clientdata(client); + + do_tda7432_init(client); + i2c_detach_client(client); + + kfree(t); + return 0; +} + +static int tda7432_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda7432 *t = i2c_get_clientdata(client); + d2printk("tda7432: In tda7432_command\n"); + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + + /* Query card - scale from TDA7432 settings to V4L settings */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + dprintk("tda7432: VIDIOCGAUDIO\n"); + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE | + VIDEO_AUDIO_MUTABLE; + if (t->muted) + va->flags |= VIDEO_AUDIO_MUTE; + va->mode |= VIDEO_SOUND_STEREO; + /* Master volume control + * V4L volume is min 0, max 65535 + * TDA7432 Volume: + * Min (-79dB) is 0x6f + * Max (+20dB) is 0x07 (630) + * Max (0dB) is 0x20 (829) + * (Mask out bit 7 of vol - it's for the loudness setting) + */ + if (!maxvol){ /* max +20db */ + va->volume = ( 0x6f - (t->volume & 0x7F) ) * 630; + } else { /* max 0db */ + va->volume = ( 0x6f - (t->volume & 0x7F) ) * 829; + } + + /* Balance depends on L,R attenuation + * V4L balance is 0 to 65535, middle is 32768 + * TDA7432 attenuation: min (0dB) is 0, max (-37.5dB) is 0x1f + * to scale up to V4L numbers, mult by 1057 + * attenuation exists for lf, lr, rf, rr + * we use only lf and rf (front channels) + */ + + if ( (t->lf) < (t->rf) ) + /* right is attenuated, balance shifted left */ + va->balance = (32768 - 1057*(t->rf)); + else + /* left is attenuated, balance shifted right */ + va->balance = (32768 + 1057*(t->lf)); + + /* Bass/treble 4 bits each */ + va->bass=t->bass; + if(va->bass >= 0x8) + va->bass = ~(va->bass - 0x8) & 0xf; + va->bass = (va->bass << 12)+(va->bass << 8)+(va->bass << 4)+(va->bass); + va->treble=t->treble; + if(va->treble >= 0x8) + va->treble = ~(va->treble - 0x8) & 0xf; + va->treble = (va->treble << 12)+(va->treble << 8)+(va->treble << 4)+(va->treble); + + break; /* VIDIOCGAUDIO case */ + } + + /* Set card - scale from V4L settings to TDA7432 settings */ + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + dprintk("tda7432: VIDEOCSAUDIO\n"); + + if(va->flags & VIDEO_AUDIO_VOLUME){ + if(!maxvol){ /* max +20db */ + t->volume = 0x6f - ((va->volume)/630); + } else { /* max 0db */ + t->volume = 0x6f - ((va->volume)/829); + } + + if (loudness) /* Turn on the loudness bit */ + t->volume |= TDA7432_LD_ON; + + tda7432_write(client,TDA7432_VL, t->volume); + } + + if(va->flags & VIDEO_AUDIO_BASS) + { + t->bass = va->bass >> 12; + if(t->bass>= 0x8) + t->bass = (~t->bass & 0xf) + 0x8 ; + } + if(va->flags & VIDEO_AUDIO_TREBLE) + { + t->treble= va->treble >> 12; + if(t->treble>= 0x8) + t->treble = (~t->treble & 0xf) + 0x8 ; + } + if(va->flags & (VIDEO_AUDIO_TREBLE| VIDEO_AUDIO_BASS)) + tda7432_write(client,TDA7432_TN, 0x10 | (t->bass << 4) | t->treble ); + + if(va->flags & VIDEO_AUDIO_BALANCE) { + if (va->balance < 32768) + { + /* shifted to left, attenuate right */ + t->rr = (32768 - va->balance)/1057; + t->rf = t->rr; + t->lr = TDA7432_ATTEN_0DB; + t->lf = TDA7432_ATTEN_0DB; + } + else if(va->balance > 32769) + { + /* shifted to right, attenuate left */ + t->lf = (va->balance - 32768)/1057; + t->lr = t->lf; + t->rr = TDA7432_ATTEN_0DB; + t->rf = TDA7432_ATTEN_0DB; + } + else + { + /* centered */ + t->rr = TDA7432_ATTEN_0DB; + t->rf = TDA7432_ATTEN_0DB; + t->lf = TDA7432_ATTEN_0DB; + t->lr = TDA7432_ATTEN_0DB; + } + } + + t->muted=(va->flags & VIDEO_AUDIO_MUTE); + if (t->muted) + { + /* Mute & update balance*/ + tda7432_write(client,TDA7432_LF, t->lf | TDA7432_MUTE); + tda7432_write(client,TDA7432_LR, t->lr | TDA7432_MUTE); + tda7432_write(client,TDA7432_RF, t->rf | TDA7432_MUTE); + tda7432_write(client,TDA7432_RR, t->rr | TDA7432_MUTE); + } else { + tda7432_write(client,TDA7432_LF, t->lf); + tda7432_write(client,TDA7432_LR, t->lr); + tda7432_write(client,TDA7432_RF, t->rf); + tda7432_write(client,TDA7432_RR, t->rr); + } + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + d2printk("tda7432: Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "i2c tda7432 driver", + .id = I2C_DRIVERID_TDA7432, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda7432_probe, + .detach_client = tda7432_detach, + .command = tda7432_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("tda7432"), + .driver = &driver, +}; + +static int __init tda7432_init(void) +{ + if ( (loudness < 0) || (loudness > 15) ) { + printk(KERN_ERR "tda7432: loudness parameter must be between 0 and 15\n"); + return -EINVAL; + } + + return i2c_add_driver(&driver); +} + +static void __exit tda7432_fini(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda7432_init); +module_exit(tda7432_fini); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tda8290.c b/drivers/media/video/tda8290.c new file mode 100644 index 000000000000..b27cc348d95c --- /dev/null +++ b/drivers/media/video/tda8290.c @@ -0,0 +1,224 @@ +/* + * $Id: tda8290.c,v 1.7 2005/03/07 12:01:51 kraxel Exp $ + * + * i2c tv tuner chip device driver + * controls the philips tda8290+75 tuner chip combo. + */ +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/delay.h> +#include <media/tuner.h> + +/* ---------------------------------------------------------------------- */ + +struct freq_entry { + u16 freq; + u8 value; +}; + +static struct freq_entry band_table[] = { + { 0x2DF4, 0x1C }, + { 0x2574, 0x14 }, + { 0x22B4, 0x0C }, + { 0x20D4, 0x0B }, + { 0x1E74, 0x3B }, + { 0x1C34, 0x33 }, + { 0x16F4, 0x5B }, + { 0x1454, 0x53 }, + { 0x12D4, 0x52 }, + { 0x1034, 0x4A }, + { 0x0EE4, 0x7A }, + { 0x0D34, 0x72 }, + { 0x0B54, 0x9A }, + { 0x0914, 0x91 }, + { 0x07F4, 0x89 }, + { 0x0774, 0xB9 }, + { 0x067B, 0xB1 }, + { 0x0634, 0xD9 }, + { 0x05A4, 0xD8 }, // FM radio + { 0x0494, 0xD0 }, + { 0x03BC, 0xC8 }, + { 0x0394, 0xF8 }, // 57250000 Hz + { 0x0000, 0xF0 }, // 0 +}; + +static struct freq_entry div_table[] = { + { 0x1C34, 3 }, + { 0x0D34, 2 }, + { 0x067B, 1 }, + { 0x0000, 0 }, +}; + +static struct freq_entry agc_table[] = { + { 0x22B4, 0x8F }, + { 0x0B54, 0x9F }, + { 0x09A4, 0x8F }, + { 0x0554, 0x9F }, + { 0x0000, 0xBF }, +}; + +static __u8 get_freq_entry( struct freq_entry* table, __u16 freq) +{ + while(table->freq && table->freq > freq) + table++; + return table->value; +} + +/* ---------------------------------------------------------------------- */ + +static unsigned char i2c_enable_bridge[2] = { 0x21, 0xC0 }; +static unsigned char i2c_disable_bridge[2] = { 0x21, 0x80 }; +static unsigned char i2c_init_tda8275[14] = { 0x00, 0x00, 0x00, 0x00, + 0x7C, 0x04, 0xA3, 0x3F, + 0x2A, 0x04, 0xFF, 0x00, + 0x00, 0x40 }; +static unsigned char i2c_set_VS[2] = { 0x30, 0x6F }; +static unsigned char i2c_set_GP01_CF[2] = { 0x20, 0x0B }; +static unsigned char i2c_tda8290_reset[2] = { 0x00, 0x00 }; +static unsigned char i2c_gainset_off[2] = { 0x28, 0x14 }; +static unsigned char i2c_gainset_on[2] = { 0x28, 0x54 }; +static unsigned char i2c_agc3_00[2] = { 0x80, 0x00 }; +static unsigned char i2c_agc2_BF[2] = { 0x60, 0xBF }; +static unsigned char i2c_cb1_D2[2] = { 0x30, 0xD2 }; +static unsigned char i2c_cb1_56[2] = { 0x30, 0x56 }; +static unsigned char i2c_cb1_52[2] = { 0x30, 0x52 }; +static unsigned char i2c_cb1_50[2] = { 0x30, 0x50 }; +static unsigned char i2c_agc2_7F[2] = { 0x60, 0x7F }; +static unsigned char i2c_agc3_08[2] = { 0x80, 0x08 }; + +static struct i2c_msg i2c_msg_init[] = { + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_init_tda8275), i2c_init_tda8275 }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_disable_bridge), i2c_disable_bridge }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_set_VS), i2c_set_VS }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_set_GP01_CF), i2c_set_GP01_CF }, +}; + +static struct i2c_msg i2c_msg_prolog[] = { +// { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_easy_mode), i2c_easy_mode }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_gainset_off), i2c_gainset_off }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_tda8290_reset), i2c_tda8290_reset }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_enable_bridge), i2c_enable_bridge }, +}; + +static struct i2c_msg i2c_msg_config[] = { +// { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_set_freq), i2c_set_freq }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_agc3_00), i2c_agc3_00 }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_agc2_BF), i2c_agc2_BF }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_cb1_D2), i2c_cb1_D2 }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_cb1_56), i2c_cb1_56 }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_cb1_52), i2c_cb1_52 }, +}; + +static struct i2c_msg i2c_msg_epilog[] = { + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_cb1_50), i2c_cb1_50 }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_agc2_7F), i2c_agc2_7F }, + { I2C_ADDR_TDA8275, 0, ARRAY_SIZE(i2c_agc3_08), i2c_agc3_08 }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_disable_bridge), i2c_disable_bridge }, + { I2C_ADDR_TDA8290, 0, ARRAY_SIZE(i2c_gainset_on), i2c_gainset_on }, +}; + +static int tda8290_tune(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + struct i2c_msg easy_mode = + { I2C_ADDR_TDA8290, 0, 2, t->i2c_easy_mode }; + struct i2c_msg set_freq = + { I2C_ADDR_TDA8275, 0, 8, t->i2c_set_freq }; + + i2c_transfer(c->adapter, &easy_mode, 1); + i2c_transfer(c->adapter, i2c_msg_prolog, ARRAY_SIZE(i2c_msg_prolog)); + + i2c_transfer(c->adapter, &set_freq, 1); + i2c_transfer(c->adapter, i2c_msg_config, ARRAY_SIZE(i2c_msg_config)); + + msleep(550); + i2c_transfer(c->adapter, i2c_msg_epilog, ARRAY_SIZE(i2c_msg_epilog)); + return 0; +} + +static void set_frequency(struct tuner *t, u16 ifc) +{ + u32 N = (((t->freq<<3)+ifc)&0x3fffc); + + N = N >> get_freq_entry(div_table, t->freq); + t->i2c_set_freq[0] = 0; + t->i2c_set_freq[1] = (unsigned char)(N>>8); + t->i2c_set_freq[2] = (unsigned char) N; + t->i2c_set_freq[3] = 0x40; + t->i2c_set_freq[4] = 0x52; + t->i2c_set_freq[5] = get_freq_entry(band_table, t->freq); + t->i2c_set_freq[6] = get_freq_entry(agc_table, t->freq); + t->i2c_set_freq[7] = 0x8f; +} + +#define V4L2_STD_MN (V4L2_STD_PAL_M|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc|V4L2_STD_NTSC) +#define V4L2_STD_B (V4L2_STD_PAL_B|V4L2_STD_PAL_B1|V4L2_STD_SECAM_B) +#define V4L2_STD_GH (V4L2_STD_PAL_G|V4L2_STD_PAL_H|V4L2_STD_SECAM_G|V4L2_STD_SECAM_H) +#define V4L2_STD_DK (V4L2_STD_PAL_DK|V4L2_STD_SECAM_DK) + +static void set_audio(struct tuner *t) +{ + t->i2c_easy_mode[0] = 0x01; + + if (t->std & V4L2_STD_MN) + t->i2c_easy_mode[1] = 0x01; + else if (t->std & V4L2_STD_B) + t->i2c_easy_mode[1] = 0x02; + else if (t->std & V4L2_STD_GH) + t->i2c_easy_mode[1] = 0x04; + else if (t->std & V4L2_STD_PAL_I) + t->i2c_easy_mode[1] = 0x08; + else if (t->std & V4L2_STD_DK) + t->i2c_easy_mode[1] = 0x10; + else if (t->std & V4L2_STD_SECAM_L) + t->i2c_easy_mode[1] = 0x20; +} + +static void set_tv_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + + set_audio(t); + set_frequency(t, 864); + tda8290_tune(c); +} + +static void set_radio_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + set_frequency(t, 704); + tda8290_tune(c); +} + +static int has_signal(struct i2c_client *c) +{ + unsigned char i2c_get_afc[1] = { 0x1B }; + unsigned char afc = 0; + + i2c_master_send(c, i2c_get_afc, ARRAY_SIZE(i2c_get_afc)); + i2c_master_recv(c, &afc, 1); + return (afc & 0x80)? 65535:0; +} + +int tda8290_init(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + + strlcpy(c->name, "tda8290+75", sizeof(c->name)); + tuner_info("tuner: type set to %s\n", c->name); + t->tv_freq = set_tv_freq; + t->radio_freq = set_radio_freq; + t->has_signal = has_signal; + + i2c_master_send(c, i2c_enable_bridge, ARRAY_SIZE(i2c_enable_bridge)); + i2c_transfer(c->adapter, i2c_msg_init, ARRAY_SIZE(i2c_msg_init)); + return 0; +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tda9840.c b/drivers/media/video/tda9840.c new file mode 100644 index 000000000000..b5177c6f54f6 --- /dev/null +++ b/drivers/media/video/tda9840.c @@ -0,0 +1,254 @@ + /* + tda9840 - i2c-driver for the tda9840 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + The tda9840 is a stereo/dual sound processor with digital + identification. It can be found at address 0x84 on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> + +#include "tda9840.h" + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); +#define dprintk(args...) \ + do { if (debug) { printk("%s: %s()[%d]: ",__stringify(KBUILD_MODNAME), __FUNCTION__, __LINE__); printk(args); } } while (0) + +#define SWITCH 0x00 +#define LEVEL_ADJUST 0x02 +#define STEREO_ADJUST 0x03 +#define TEST 0x04 + +/* addresses to scan, found only at 0x42 (7-Bit) */ +static unsigned short normal_i2c[] = { I2C_TDA9840, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +static int command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + int result; + int byte = *(int *)arg; + + switch (cmd) { + case TDA9840_SWITCH: + + dprintk("TDA9840_SWITCH: 0x%02x\n", byte); + + if (byte != TDA9840_SET_MONO + && byte != TDA9840_SET_MUTE + && byte != TDA9840_SET_STEREO + && byte != TDA9840_SET_LANG1 + && byte != TDA9840_SET_LANG2 + && byte != TDA9840_SET_BOTH + && byte != TDA9840_SET_BOTH_R + && byte != TDA9840_SET_EXTERNAL) { + return -EINVAL; + } + + result = i2c_smbus_write_byte_data(client, SWITCH, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + + case TDA9840_LEVEL_ADJUST: + + dprintk("TDA9840_LEVEL_ADJUST: %d\n", byte); + + /* check for correct range */ + if (byte > 25 || byte < -20) + return -EINVAL; + + /* calculate actual value to set, see specs, page 18 */ + byte /= 5; + if (0 < byte) + byte += 0x8; + else + byte = -byte; + + result = i2c_smbus_write_byte_data(client, LEVEL_ADJUST, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + + case TDA9840_STEREO_ADJUST: + + dprintk("TDA9840_STEREO_ADJUST: %d\n", byte); + + /* check for correct range */ + if (byte > 25 || byte < -24) + return -EINVAL; + + /* calculate actual value to set */ + byte /= 5; + if (0 < byte) + byte += 0x20; + else + byte = -byte; + + result = i2c_smbus_write_byte_data(client, STEREO_ADJUST, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + + case TDA9840_DETECT: { + int *ret = (int *)arg; + + byte = i2c_smbus_read_byte_data(client, STEREO_ADJUST); + if (byte == -1) { + dprintk("i2c_smbus_read_byte_data() failed\n"); + return -EIO; + } + + if (0 != (byte & 0x80)) { + dprintk("TDA9840_DETECT: register contents invalid\n"); + return -EINVAL; + } + + dprintk("TDA9840_DETECT: byte: 0x%02x\n", byte); + *ret = ((byte & 0x60) >> 5); + result = 0; + break; + } + case TDA9840_TEST: + dprintk("TDA9840_TEST: 0x%02x\n", byte); + + /* mask out irrelevant bits */ + byte &= 0x3; + + result = i2c_smbus_write_byte_data(client, TEST, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + default: + return -ENOIOCTLCMD; + } + + if (result) + return -EIO; + + return 0; +} + +static int detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + int result = 0; + + int byte = 0x0; + + /* let's see whether this adapter can support what we need */ + if (0 == i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + return 0; + } + + /* allocate memory for client structure */ + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (0 == client) { + printk("not enough kernel memory\n"); + return -ENOMEM; + } + + /* fill client structure */ + memcpy(client, &client_template, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + + /* tell the i2c layer a new client has arrived */ + if (0 != (result = i2c_attach_client(client))) { + kfree(client); + return result; + } + + /* set initial values for level & stereo - adjustment, mode */ + byte = 0; + result = command(client, TDA9840_LEVEL_ADJUST, &byte); + result += command(client, TDA9840_STEREO_ADJUST, &byte); + byte = TDA9840_SET_MONO; + result = command(client, TDA9840_SWITCH, &byte); + if (result) { + dprintk("could not initialize tda9840\n"); + return -ENODEV; + } + + printk("tda9840: detected @ 0x%02x on adapter %s\n", address, &client->adapter->name[0]); + return 0; +} + +static int attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if (adapter->id != I2C_ALGO_SAA7146) { + dprintk("refusing to probe on unknown adapter [name='%s',id=0x%x]\n", adapter->name, adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter, &addr_data, &detect); +} + +static int detach(struct i2c_client *client) +{ + int ret = i2c_detach_client(client); + kfree(client); + return ret; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tda9840", + .id = I2C_DRIVERID_TDA9840, + .flags = I2C_DF_NOTIFY, + .attach_adapter = attach, + .detach_client = detach, + .command = command, +}; + +static struct i2c_client client_template = { + I2C_DEVNAME("tda9840"), + .driver = &driver, +}; + +static int __init this_module_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit this_module_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(this_module_init); +module_exit(this_module_exit); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tda9840 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tda9840.h b/drivers/media/video/tda9840.h new file mode 100644 index 000000000000..28021053bd09 --- /dev/null +++ b/drivers/media/video/tda9840.h @@ -0,0 +1,35 @@ +#ifndef __INCLUDED_TDA9840__ +#define __INCLUDED_TDA9840__ + +#define I2C_TDA9840 0x42 + +#define TDA9840_DETECT _IOR('v',1,int) +/* return values for TDA9840_DETCT */ +#define TDA9840_MONO_DETECT 0x0 +#define TDA9840_DUAL_DETECT 0x1 +#define TDA9840_STEREO_DETECT 0x2 +#define TDA9840_INCORRECT_DETECT 0x3 + +#define TDA9840_SWITCH _IOW('v',2,int) +/* modes than can be set with TDA9840_SWITCH */ +#define TDA9840_SET_MUTE 0x00 +#define TDA9840_SET_MONO 0x10 +#define TDA9840_SET_STEREO 0x2a +#define TDA9840_SET_LANG1 0x12 +#define TDA9840_SET_LANG2 0x1e +#define TDA9840_SET_BOTH 0x1a +#define TDA9840_SET_BOTH_R 0x16 +#define TDA9840_SET_EXTERNAL 0x7a + +/* values may range between +2.5 and -2.0; + the value has to be multiplied with 10 */ +#define TDA9840_LEVEL_ADJUST _IOW('v',3,int) + +/* values may range between +2.5 and -2.4; + the value has to be multiplied with 10 */ +#define TDA9840_STEREO_ADJUST _IOW('v',4,int) + +/* currently not implemented */ +#define TDA9840_TEST _IOW('v',5,int) + +#endif diff --git a/drivers/media/video/tda9875.c b/drivers/media/video/tda9875.c new file mode 100644 index 000000000000..4f1114c033a1 --- /dev/null +++ b/drivers/media/video/tda9875.c @@ -0,0 +1,423 @@ +/* + * For the TDA9875 chip + * (The TDA9875 is used on the Diamond DTV2000 french version + * Other cards probably use these chips as well.) + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Copyright (c) 2000 Guillaume Delvit based on Gerd Knorr source and + * Eric Sandeen + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + * Revision: 0.1 - original version + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/init.h> + +#include "bttv.h" +#include <media/audiochip.h> +#include <media/id.h> + +static int debug; /* insmod parameter */ +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_LICENSE("GPL"); + + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_TDA9875 >> 1, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* This is a superset of the TDA9875 */ +struct tda9875 { + int mode; + int rvol, lvol; + int bass, treble; + struct i2c_client c; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk + +/* The TDA9875 is made by Philips Semiconductor + * http://www.semiconductors.philips.com + * TDA9875: I2C-bus controlled DSP audio processor, FM demodulator + * + */ + + /* subaddresses for TDA9875 */ +#define TDA9875_MUT 0x12 /*General mute (value --> 0b11001100*/ +#define TDA9875_CFG 0x01 /* Config register (value --> 0b00000000 */ +#define TDA9875_DACOS 0x13 /*DAC i/o select (ADC) 0b0000100*/ +#define TDA9875_LOSR 0x16 /*Line output select regirter 0b0100 0001*/ + +#define TDA9875_CH1V 0x0c /*Channel 1 volume (mute)*/ +#define TDA9875_CH2V 0x0d /*Channel 2 volume (mute)*/ +#define TDA9875_SC1 0x14 /*SCART 1 in (mono)*/ +#define TDA9875_SC2 0x15 /*SCART 2 in (mono)*/ + +#define TDA9875_ADCIS 0x17 /*ADC input select (mono) 0b0110 000*/ +#define TDA9875_AER 0x19 /*Audio effect (AVL+Pseudo) 0b0000 0110*/ +#define TDA9875_MCS 0x18 /*Main channel select (DAC) 0b0000100*/ +#define TDA9875_MVL 0x1a /* Main volume gauche */ +#define TDA9875_MVR 0x1b /* Main volume droite */ +#define TDA9875_MBA 0x1d /* Main Basse */ +#define TDA9875_MTR 0x1e /* Main treble */ +#define TDA9875_ACS 0x1f /* Auxilary channel select (FM) 0b0000000*/ +#define TDA9875_AVL 0x20 /* Auxilary volume gauche */ +#define TDA9875_AVR 0x21 /* Auxilary volume droite */ +#define TDA9875_ABA 0x22 /* Auxilary Basse */ +#define TDA9875_ATR 0x23 /* Auxilary treble */ + +#define TDA9875_MSR 0x02 /* Monitor select register */ +#define TDA9875_C1MSB 0x03 /* Carrier 1 (FM) frequency register MSB */ +#define TDA9875_C1MIB 0x04 /* Carrier 1 (FM) frequency register (16-8]b */ +#define TDA9875_C1LSB 0x05 /* Carrier 1 (FM) frequency register LSB */ +#define TDA9875_C2MSB 0x06 /* Carrier 2 (nicam) frequency register MSB */ +#define TDA9875_C2MIB 0x07 /* Carrier 2 (nicam) frequency register (16-8]b */ +#define TDA9875_C2LSB 0x08 /* Carrier 2 (nicam) frequency register LSB */ +#define TDA9875_DCR 0x09 /* Demodulateur configuration regirter*/ +#define TDA9875_DEEM 0x0a /* FM de-emphasis regirter*/ +#define TDA9875_FMAT 0x0b /* FM Matrix regirter*/ + +/* values */ +#define TDA9875_MUTE_ON 0xff /* general mute */ +#define TDA9875_MUTE_OFF 0xcc /* general no mute */ + + + +/* Begin code */ + +static int tda9875_write(struct i2c_client *client, int subaddr, unsigned char val) +{ + unsigned char buffer[2]; + dprintk("In tda9875_write\n"); + dprintk("Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda9875: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +#if 0 +static int tda9875_read(struct i2c_client *client) +{ + unsigned char buffer; + dprintk("In tda9875_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda9875: I/O error, trying (read)\n"); + return -1; + } + dprintk("Read 0x%02x\n", buffer); + return buffer; +} +#endif + +static int i2c_read_register(struct i2c_adapter *adap, int addr, int reg) +{ + unsigned char write[1]; + unsigned char read[1]; + struct i2c_msg msgs[2] = { + { addr, 0, 1, write }, + { addr, I2C_M_RD, 1, read } + }; + write[0] = reg; + + if (2 != i2c_transfer(adap,msgs,2)) { + printk(KERN_WARNING "tda9875: I/O error (read2)\n"); + return -1; + } + dprintk("tda9875: chip_read2: reg%d=0x%x\n",reg,read[0]); + return read[0]; +} + +static void tda9875_set(struct i2c_client *client) +{ + struct tda9875 *tda = i2c_get_clientdata(client); + unsigned char a; + + dprintk(KERN_DEBUG "tda9875_set(%04x,%04x,%04x,%04x)\n", + tda->lvol,tda->rvol,tda->bass,tda->treble); + + + a = tda->lvol & 0xff; + tda9875_write(client, TDA9875_MVL, a); + a =tda->rvol & 0xff; + tda9875_write(client, TDA9875_MVR, a); + a =tda->bass & 0xff; + tda9875_write(client, TDA9875_MBA, a); + a =tda->treble & 0xff; + tda9875_write(client, TDA9875_MTR, a); +} + +static void do_tda9875_init(struct i2c_client *client) +{ + struct tda9875 *t = i2c_get_clientdata(client); + dprintk("In tda9875_init\n"); + tda9875_write(client, TDA9875_CFG, 0xd0 ); /*reg de config 0 (reset)*/ + tda9875_write(client, TDA9875_MSR, 0x03 ); /* Monitor 0b00000XXX*/ + tda9875_write(client, TDA9875_C1MSB, 0x00 ); /*Car1(FM) MSB XMHz*/ + tda9875_write(client, TDA9875_C1MIB, 0x00 ); /*Car1(FM) MIB XMHz*/ + tda9875_write(client, TDA9875_C1LSB, 0x00 ); /*Car1(FM) LSB XMHz*/ + tda9875_write(client, TDA9875_C2MSB, 0x00 ); /*Car2(NICAM) MSB XMHz*/ + tda9875_write(client, TDA9875_C2MIB, 0x00 ); /*Car2(NICAM) MIB XMHz*/ + tda9875_write(client, TDA9875_C2LSB, 0x00 ); /*Car2(NICAM) LSB XMHz*/ + tda9875_write(client, TDA9875_DCR, 0x00 ); /*Demod config 0x00*/ + tda9875_write(client, TDA9875_DEEM, 0x44 ); /*DE-Emph 0b0100 0100*/ + tda9875_write(client, TDA9875_FMAT, 0x00 ); /*FM Matrix reg 0x00*/ + tda9875_write(client, TDA9875_SC1, 0x00 ); /* SCART 1 (SC1)*/ + tda9875_write(client, TDA9875_SC2, 0x01 ); /* SCART 2 (sc2)*/ + + tda9875_write(client, TDA9875_CH1V, 0x10 ); /* Channel volume 1 mute*/ + tda9875_write(client, TDA9875_CH2V, 0x10 ); /* Channel volume 2 mute */ + tda9875_write(client, TDA9875_DACOS, 0x02 ); /* sig DAC i/o(in:nicam)*/ + tda9875_write(client, TDA9875_ADCIS, 0x6f ); /* sig ADC input(in:mono)*/ + tda9875_write(client, TDA9875_LOSR, 0x00 ); /* line out (in:mono)*/ + tda9875_write(client, TDA9875_AER, 0x00 ); /*06 Effect (AVL+PSEUDO) */ + tda9875_write(client, TDA9875_MCS, 0x44 ); /* Main ch select (DAC) */ + tda9875_write(client, TDA9875_MVL, 0x03 ); /* Vol Main left 10dB */ + tda9875_write(client, TDA9875_MVR, 0x03 ); /* Vol Main right 10dB*/ + tda9875_write(client, TDA9875_MBA, 0x00 ); /* Main Bass Main 0dB*/ + tda9875_write(client, TDA9875_MTR, 0x00 ); /* Main Treble Main 0dB*/ + tda9875_write(client, TDA9875_ACS, 0x44 ); /* Aux chan select (dac)*/ + tda9875_write(client, TDA9875_AVL, 0x00 ); /* Vol Aux left 0dB*/ + tda9875_write(client, TDA9875_AVR, 0x00 ); /* Vol Aux right 0dB*/ + tda9875_write(client, TDA9875_ABA, 0x00 ); /* Aux Bass Main 0dB*/ + tda9875_write(client, TDA9875_ATR, 0x00 ); /* Aux Aigus Main 0dB*/ + + tda9875_write(client, TDA9875_MUT, 0xcc ); /* General mute */ + + t->mode=AUDIO_UNMUTE; + t->lvol=t->rvol =0; /* 0dB */ + t->bass=0; /* 0dB */ + t->treble=0; /* 0dB */ + tda9875_set(client); + +} + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda9875_checkit(struct i2c_adapter *adap, int addr) +{ + int dic,rev; + + dic=i2c_read_register(adap,addr,254); + rev=i2c_read_register(adap,addr,255); + + if(dic==0 || dic==2) { // tda9875 and tda9875A + printk("tda9875: TDA9875%s Rev.%d detected at 0x%x\n", + dic==0?"":"A", rev,addr<<1); + return 1; + } + printk("tda9875: no such chip at 0x%x (dic=0x%x rev=0x%x)\n",addr<<1,dic,rev); + return(0); +} + +static int tda9875_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct tda9875 *t; + struct i2c_client *client; + dprintk("In tda9875_attach\n"); + + t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + + client = &t->c; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + i2c_set_clientdata(client, t); + + if(!tda9875_checkit(adap,addr)) { + kfree(t); + return 1; + } + + do_tda9875_init(client); + printk(KERN_INFO "tda9875: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda9875_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tda9875_attach); +#else + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda9875_attach); +#endif + return 0; +} + +static int tda9875_detach(struct i2c_client *client) +{ + struct tda9875 *t = i2c_get_clientdata(client); + + do_tda9875_init(client); + i2c_detach_client(client); + + kfree(t); + return 0; +} + +static int tda9875_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda9875 *t = i2c_get_clientdata(client); + + dprintk("In tda9875_command...\n"); + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + int left,right; + + dprintk("VIDIOCGAUDIO\n"); + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + + /* min is -84 max is 24 */ + left = (t->lvol+84)*606; + right = (t->rvol+84)*606; + va->volume=max(left,right); + va->balance=(32768*min(left,right))/ + (va->volume ? va->volume : 1); + va->balance=(left<right)? + (65535-va->balance) : va->balance; + va->bass = (t->bass+12)*2427; /* min -12 max +15 */ + va->treble = (t->treble+12)*2730;/* min -12 max +12 */ + va->mode |= VIDEO_SOUND_MONO; + + break; /* VIDIOCGAUDIO case */ + } + + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + int left,right; + + dprintk("VIDEOCSAUDIO...\n"); + left = (min(65536 - va->balance,32768) * + va->volume) / 32768; + right = (min(va->balance,(__u16)32768) * + va->volume) / 32768; + t->lvol = ((left/606)-84) & 0xff; + if (t->lvol > 24) + t->lvol = 24; + if (t->lvol < -84) + t->lvol = -84 & 0xff; + + t->rvol = ((right/606)-84) & 0xff; + if (t->rvol > 24) + t->rvol = 24; + if (t->rvol < -84) + t->rvol = -84 & 0xff; + + t->bass = ((va->bass/2400)-12) & 0xff; + if (t->bass > 15) + t->bass = 15; + if (t->bass < -12) + t->bass = -12 & 0xff; + + t->treble = ((va->treble/2700)-12) & 0xff; + if (t->treble > 12) + t->treble = 12; + if (t->treble < -12) + t->treble = -12 & 0xff; + + + +//printk("tda9875 bal:%04x vol:%04x bass:%04x treble:%04x\n",va->balance,va->volume,va->bass,va->treble); + + + tda9875_set(client); + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + dprintk("Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "i2c tda9875 driver", + .id = I2C_DRIVERID_TDA9875, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda9875_probe, + .detach_client = tda9875_detach, + .command = tda9875_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("tda9875"), + .driver = &driver, +}; + +static int __init tda9875_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit tda9875_fini(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda9875_init); +module_exit(tda9875_fini); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/media/video/tda9887.c b/drivers/media/video/tda9887.c new file mode 100644 index 000000000000..7fb063a27961 --- /dev/null +++ b/drivers/media/video/tda9887.c @@ -0,0 +1,801 @@ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <media/audiochip.h> +#include <media/tuner.h> +#include <media/id.h> + +/* Chips: + TDA9885 (PAL, NTSC) + TDA9886 (PAL, SECAM, NTSC) + TDA9887 (PAL, SECAM, NTSC, FM Radio) + + found on: + - Pinnacle PCTV (Jul.2002 Version with MT2032, bttv) + TDA9887 (world), TDA9885 (USA) + Note: OP2 of tda988x must be set to 1, else MT2032 is disabled! + - KNC One TV-Station RDS (saa7134) +*/ + + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + 0x84 >>1, + 0x86 >>1, + 0x96 >>1, + I2C_CLIENT_END, +}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +/* insmod options */ +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_LICENSE("GPL"); + +/* ---------------------------------------------------------------------- */ + +#define UNSET (-1U) +#define PREFIX "tda9885/6/7: " +#define dprintk if (debug) printk + +struct tda9887 { + struct i2c_client client; + v4l2_std_id std; + unsigned int radio; + unsigned int config; + unsigned int pinnacle_id; + unsigned int using_v4l2; +}; + +struct tvnorm { + v4l2_std_id std; + char *name; + unsigned char b; + unsigned char c; + unsigned char e; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* ---------------------------------------------------------------------- */ + +// +// TDA defines +// + +//// first reg (b) +#define cVideoTrapBypassOFF 0x00 // bit b0 +#define cVideoTrapBypassON 0x01 // bit b0 + +#define cAutoMuteFmInactive 0x00 // bit b1 +#define cAutoMuteFmActive 0x02 // bit b1 + +#define cIntercarrier 0x00 // bit b2 +#define cQSS 0x04 // bit b2 + +#define cPositiveAmTV 0x00 // bit b3:4 +#define cFmRadio 0x08 // bit b3:4 +#define cNegativeFmTV 0x10 // bit b3:4 + + +#define cForcedMuteAudioON 0x20 // bit b5 +#define cForcedMuteAudioOFF 0x00 // bit b5 + +#define cOutputPort1Active 0x00 // bit b6 +#define cOutputPort1Inactive 0x40 // bit b6 + +#define cOutputPort2Active 0x00 // bit b7 +#define cOutputPort2Inactive 0x80 // bit b7 + + +//// second reg (c) +#define cDeemphasisOFF 0x00 // bit c5 +#define cDeemphasisON 0x20 // bit c5 + +#define cDeemphasis75 0x00 // bit c6 +#define cDeemphasis50 0x40 // bit c6 + +#define cAudioGain0 0x00 // bit c7 +#define cAudioGain6 0x80 // bit c7 + + +//// third reg (e) +#define cAudioIF_4_5 0x00 // bit e0:1 +#define cAudioIF_5_5 0x01 // bit e0:1 +#define cAudioIF_6_0 0x02 // bit e0:1 +#define cAudioIF_6_5 0x03 // bit e0:1 + + +#define cVideoIF_58_75 0x00 // bit e2:4 +#define cVideoIF_45_75 0x04 // bit e2:4 +#define cVideoIF_38_90 0x08 // bit e2:4 +#define cVideoIF_38_00 0x0C // bit e2:4 +#define cVideoIF_33_90 0x10 // bit e2:4 +#define cVideoIF_33_40 0x14 // bit e2:4 +#define cRadioIF_45_75 0x18 // bit e2:4 +#define cRadioIF_38_90 0x1C // bit e2:4 + + +#define cTunerGainNormal 0x00 // bit e5 +#define cTunerGainLow 0x20 // bit e5 + +#define cGating_18 0x00 // bit e6 +#define cGating_36 0x40 // bit e6 + +#define cAgcOutON 0x80 // bit e7 +#define cAgcOutOFF 0x00 // bit e7 + +/* ---------------------------------------------------------------------- */ + +static struct tvnorm tvnorms[] = { + { + .std = V4L2_STD_PAL_BG, + .name = "PAL-BG", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cAudioIF_5_5 | + cVideoIF_38_90 ), + },{ + .std = V4L2_STD_PAL_I, + .name = "PAL-I", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cAudioIF_6_0 | + cVideoIF_38_90 ), + },{ + .std = V4L2_STD_PAL_DK, + .name = "PAL-DK", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cAudioIF_6_5 | + cVideoIF_38_00 ), + },{ + .std = V4L2_STD_PAL_M | V4L2_STD_PAL_N, + .name = "PAL-M/N", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis75 ), + .e = ( cAudioIF_4_5 | + cVideoIF_45_75 ), + },{ + .std = V4L2_STD_SECAM_L, + .name = "SECAM-L", + .b = ( cPositiveAmTV | + cQSS ), + .e = ( cAudioIF_6_5 | + cVideoIF_38_90 ), + },{ + .std = V4L2_STD_SECAM_DK, + .name = "SECAM-DK", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cAudioIF_6_5 | + cVideoIF_38_00 ), + },{ + .std = V4L2_STD_NTSC_M, + .name = "NTSC-M", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cGating_36 | + cAudioIF_4_5 | + cVideoIF_45_75 ), + },{ + .std = V4L2_STD_NTSC_M_JP, + .name = "NTSC-JP", + .b = ( cNegativeFmTV | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cGating_36 | + cAudioIF_4_5 | + cVideoIF_58_75 ), + } +}; + +static struct tvnorm radio = { + .name = "radio", + .b = ( cFmRadio | + cQSS ), + .c = ( cDeemphasisON | + cDeemphasis50 ), + .e = ( cAudioIF_5_5 | + cRadioIF_38_90 ), +}; + +/* ---------------------------------------------------------------------- */ + +static void dump_read_message(unsigned char *buf) +{ + static char *afc[16] = { + "- 12.5 kHz", + "- 37.5 kHz", + "- 62.5 kHz", + "- 87.5 kHz", + "-112.5 kHz", + "-137.5 kHz", + "-162.5 kHz", + "-187.5 kHz [min]", + "+187.5 kHz [max]", + "+162.5 kHz", + "+137.5 kHz", + "+112.5 kHz", + "+ 87.5 kHz", + "+ 62.5 kHz", + "+ 37.5 kHz", + "+ 12.5 kHz", + }; + printk(PREFIX "read: 0x%2x\n", buf[0]); + printk(" after power on : %s\n", (buf[0] & 0x01) ? "yes" : "no"); + printk(" afc : %s\n", afc[(buf[0] >> 1) & 0x0f]); + printk(" fmif level : %s\n", (buf[0] & 0x20) ? "high" : "low"); + printk(" afc window : %s\n", (buf[0] & 0x40) ? "in" : "out"); + printk(" vfi level : %s\n", (buf[0] & 0x80) ? "high" : "low"); +} + +static void dump_write_message(unsigned char *buf) +{ + static char *sound[4] = { + "AM/TV", + "FM/radio", + "FM/TV", + "FM/radio" + }; + static char *adjust[32] = { + "-16", "-15", "-14", "-13", "-12", "-11", "-10", "-9", + "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", + "0", "+1", "+2", "+3", "+4", "+5", "+6", "+7", + "+8", "+9", "+10", "+11", "+12", "+13", "+14", "+15" + }; + static char *deemph[4] = { + "no", "no", "75", "50" + }; + static char *carrier[4] = { + "4.5 MHz", + "5.5 MHz", + "6.0 MHz", + "6.5 MHz / AM" + }; + static char *vif[8] = { + "58.75 MHz", + "45.75 MHz", + "38.9 MHz", + "38.0 MHz", + "33.9 MHz", + "33.4 MHz", + "45.75 MHz + pin13", + "38.9 MHz + pin13", + }; + static char *rif[4] = { + "44 MHz", + "52 MHz", + "52 MHz", + "44 MHz", + }; + + printk(PREFIX "write: byte B 0x%02x\n",buf[1]); + printk(" B0 video mode : %s\n", + (buf[1] & 0x01) ? "video trap" : "sound trap"); + printk(" B1 auto mute fm : %s\n", + (buf[1] & 0x02) ? "yes" : "no"); + printk(" B2 carrier mode : %s\n", + (buf[1] & 0x04) ? "QSS" : "Intercarrier"); + printk(" B3-4 tv sound/radio : %s\n", + sound[(buf[1] & 0x18) >> 3]); + printk(" B5 force mute audio: %s\n", + (buf[1] & 0x20) ? "yes" : "no"); + printk(" B6 output port 1 : %s\n", + (buf[1] & 0x40) ? "high (inactive)" : "low (active)"); + printk(" B7 output port 2 : %s\n", + (buf[1] & 0x80) ? "high (inactive)" : "low (active)"); + + printk(PREFIX "write: byte C 0x%02x\n",buf[2]); + printk(" C0-4 top adjustment : %s dB\n", adjust[buf[2] & 0x1f]); + printk(" C5-6 de-emphasis : %s\n", deemph[(buf[2] & 0x60) >> 5]); + printk(" C7 audio gain : %s\n", + (buf[2] & 0x80) ? "-6" : "0"); + + printk(PREFIX "write: byte E 0x%02x\n",buf[3]); + printk(" E0-1 sound carrier : %s\n", + carrier[(buf[3] & 0x03)]); + printk(" E6 l pll ganting : %s\n", + (buf[3] & 0x40) ? "36" : "13"); + + if (buf[1] & 0x08) { + /* radio */ + printk(" E2-4 video if : %s\n", + rif[(buf[3] & 0x0c) >> 2]); + printk(" E7 vif agc output : %s\n", + (buf[3] & 0x80) + ? ((buf[3] & 0x10) ? "fm-agc radio" : "sif-agc radio") + : "fm radio carrier afc"); + } else { + /* video */ + printk(" E2-4 video if : %s\n", + vif[(buf[3] & 0x1c) >> 2]); + printk(" E5 tuner gain : %s\n", + (buf[3] & 0x80) + ? ((buf[3] & 0x20) ? "external" : "normal") + : ((buf[3] & 0x20) ? "minimum" : "normal")); + printk(" E7 vif agc output : %s\n", + (buf[3] & 0x80) + ? ((buf[3] & 0x20) + ? "pin3 port, pin22 vif agc out" + : "pin22 port, pin3 vif acg ext in") + : "pin3+pin22 port"); + } + printk("--\n"); +} + +/* ---------------------------------------------------------------------- */ + +static int tda9887_set_tvnorm(struct tda9887 *t, char *buf) +{ + struct tvnorm *norm = NULL; + int i; + + if (t->radio) { + norm = &radio; + } else { + for (i = 0; i < ARRAY_SIZE(tvnorms); i++) { + if (tvnorms[i].std & t->std) { + norm = tvnorms+i; + break; + } + } + } + if (NULL == norm) { + dprintk(PREFIX "Oops: no tvnorm entry found\n"); + return -1; + } + + dprintk(PREFIX "configure for: %s\n",norm->name); + buf[1] = norm->b; + buf[2] = norm->c; + buf[3] = norm->e; + return 0; +} + +static unsigned int port1 = UNSET; +static unsigned int port2 = UNSET; +static unsigned int qss = UNSET; +static unsigned int adjust = 0x10; +module_param(port1, int, 0644); +module_param(port2, int, 0644); +module_param(qss, int, 0644); +module_param(adjust, int, 0644); + +static int tda9887_set_insmod(struct tda9887 *t, char *buf) +{ + if (UNSET != port1) { + if (port1) + buf[1] |= cOutputPort1Inactive; + else + buf[1] &= ~cOutputPort1Inactive; + } + if (UNSET != port2) { + if (port2) + buf[1] |= cOutputPort2Inactive; + else + buf[1] &= ~cOutputPort2Inactive; + } + + if (UNSET != qss) { + if (qss) + buf[1] |= cQSS; + else + buf[1] &= ~cQSS; + } + + if (adjust >= 0x00 && adjust < 0x20) + buf[2] |= adjust; + return 0; +} + +static int tda9887_set_config(struct tda9887 *t, char *buf) +{ + if (t->config & TDA9887_PORT1_ACTIVE) + buf[1] &= ~cOutputPort1Inactive; + if (t->config & TDA9887_PORT1_INACTIVE) + buf[1] |= cOutputPort1Inactive; + if (t->config & TDA9887_PORT2_ACTIVE) + buf[1] &= ~cOutputPort2Inactive; + if (t->config & TDA9887_PORT2_INACTIVE) + buf[1] |= cOutputPort2Inactive; + + if (t->config & TDA9887_QSS) + buf[1] |= cQSS; + if (t->config & TDA9887_INTERCARRIER) + buf[1] &= ~cQSS; + + if (t->config & TDA9887_AUTOMUTE) + buf[1] |= cAutoMuteFmActive; + if (t->config & TDA9887_DEEMPHASIS_MASK) { + buf[2] &= ~0x60; + switch (t->config & TDA9887_DEEMPHASIS_MASK) { + case TDA9887_DEEMPHASIS_NONE: + buf[2] |= cDeemphasisOFF; + break; + case TDA9887_DEEMPHASIS_50: + buf[2] |= cDeemphasisON | cDeemphasis50; + break; + case TDA9887_DEEMPHASIS_75: + buf[2] |= cDeemphasisON | cDeemphasis75; + break; + } + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static int tda9887_set_pinnacle(struct tda9887 *t, char *buf) +{ + unsigned int bCarrierMode = UNSET; + + if (t->std & V4L2_STD_625_50) { + if ((1 == t->pinnacle_id) || (7 == t->pinnacle_id)) { + bCarrierMode = cIntercarrier; + } else { + bCarrierMode = cQSS; + } + } + if (t->std & V4L2_STD_525_60) { + if ((5 == t->pinnacle_id) || (6 == t->pinnacle_id)) { + bCarrierMode = cIntercarrier; + } else { + bCarrierMode = cQSS; + } + } + + if (bCarrierMode != UNSET) { + buf[1] &= ~0x04; + buf[1] |= bCarrierMode; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static char pal[] = "-"; +module_param_string(pal, pal, 0644, sizeof(pal)); +static char secam[] = "-"; +module_param_string(secam, secam, 0644, sizeof(secam)); + +static int tda9887_fixup_std(struct tda9887 *t) +{ + /* get more precise norm info from insmod option */ + if ((t->std & V4L2_STD_PAL) == V4L2_STD_PAL) { + switch (pal[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + dprintk(PREFIX "insmod fixup: PAL => PAL-BG\n"); + t->std = V4L2_STD_PAL_BG; + break; + case 'i': + case 'I': + dprintk(PREFIX "insmod fixup: PAL => PAL-I\n"); + t->std = V4L2_STD_PAL_I; + break; + case 'd': + case 'D': + case 'k': + case 'K': + dprintk(PREFIX "insmod fixup: PAL => PAL-DK\n"); + t->std = V4L2_STD_PAL_DK; + break; + } + } + if ((t->std & V4L2_STD_SECAM) == V4L2_STD_SECAM) { + switch (secam[0]) { + case 'd': + case 'D': + case 'k': + case 'K': + dprintk(PREFIX "insmod fixup: SECAM => SECAM-DK\n"); + t->std = V4L2_STD_SECAM_DK; + break; + case 'l': + case 'L': + dprintk(PREFIX "insmod fixup: SECAM => SECAM-L\n"); + t->std = V4L2_STD_SECAM_L; + break; + } + } + return 0; +} + +static int tda9887_status(struct tda9887 *t) +{ + unsigned char buf[1]; + int rc; + + memset(buf,0,sizeof(buf)); + if (1 != (rc = i2c_master_recv(&t->client,buf,1))) + printk(PREFIX "i2c i/o error: rc == %d (should be 1)\n",rc); + dump_read_message(buf); + return 0; +} + +static int tda9887_configure(struct tda9887 *t) +{ + unsigned char buf[4]; + int rc; + + memset(buf,0,sizeof(buf)); + tda9887_set_tvnorm(t,buf); + buf[1] |= cOutputPort1Inactive; + buf[1] |= cOutputPort2Inactive; + if (UNSET != t->pinnacle_id) { + tda9887_set_pinnacle(t,buf); + } + tda9887_set_config(t,buf); + tda9887_set_insmod(t,buf); + +#if 0 + /* This as-is breaks some cards, must be fixed in a + * card-specific way, probably using TDA9887_SET_CONFIG to + * turn on/off port2 */ + if (t->std & V4L2_STD_SECAM_L) { + /* secam fixup (FIXME: move this to tvnorms array?) */ + buf[1] &= ~cOutputPort2Inactive; + } +#endif + + dprintk(PREFIX "writing: b=0x%02x c=0x%02x e=0x%02x\n", + buf[1],buf[2],buf[3]); + if (debug > 1) + dump_write_message(buf); + + if (4 != (rc = i2c_master_send(&t->client,buf,4))) + printk(PREFIX "i2c i/o error: rc == %d (should be 4)\n",rc); + + if (debug > 2) { + msleep_interruptible(1000); + tda9887_status(t); + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static int tda9887_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct tda9887 *t; + + client_template.adapter = adap; + client_template.addr = addr; + + printk(PREFIX "chip found @ 0x%x\n", addr<<1); + + if (NULL == (t = kmalloc(sizeof(*t), GFP_KERNEL))) + return -ENOMEM; + memset(t,0,sizeof(*t)); + t->client = client_template; + t->std = 0; + t->pinnacle_id = UNSET; + i2c_set_clientdata(&t->client, t); + i2c_attach_client(&t->client); + + return 0; +} + +static int tda9887_probe(struct i2c_adapter *adap) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tda9887_attach); +#else + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_B_BT848: + case I2C_ALGO_BIT | I2C_HW_B_RIVA: + case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, tda9887_attach); + break; + } +#endif + return 0; +} + +static int tda9887_detach(struct i2c_client *client) +{ + struct tda9887 *t = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(t); + return 0; +} + +#define SWITCH_V4L2 if (!t->using_v4l2 && debug) \ + printk(PREFIX "switching to v4l2\n"); \ + t->using_v4l2 = 1; +#define CHECK_V4L2 if (t->using_v4l2) { if (debug) \ + printk(PREFIX "ignore v4l1 call\n"); \ + return 0; } + +static int +tda9887_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tda9887 *t = i2c_get_clientdata(client); + + switch (cmd) { + + /* --- configuration --- */ + case AUDC_SET_RADIO: + t->radio = 1; + tda9887_configure(t); + break; + + case AUDC_CONFIG_PINNACLE: + { + int *i = arg; + + t->pinnacle_id = *i; + tda9887_configure(t); + break; + } + case TDA9887_SET_CONFIG: + { + int *i = arg; + + t->config = *i; + tda9887_configure(t); + break; + } + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCSCHAN: + { + static const v4l2_std_id map[] = { + [ VIDEO_MODE_PAL ] = V4L2_STD_PAL, + [ VIDEO_MODE_NTSC ] = V4L2_STD_NTSC_M, + [ VIDEO_MODE_SECAM ] = V4L2_STD_SECAM, + [ 4 /* bttv */ ] = V4L2_STD_PAL_M, + [ 5 /* bttv */ ] = V4L2_STD_PAL_N, + [ 6 /* bttv */ ] = V4L2_STD_NTSC_M_JP, + }; + struct video_channel *vc = arg; + + CHECK_V4L2; + t->radio = 0; + if (vc->norm < ARRAY_SIZE(map)) + t->std = map[vc->norm]; + tda9887_fixup_std(t); + tda9887_configure(t); + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + + SWITCH_V4L2; + t->radio = 0; + t->std = *id; + tda9887_fixup_std(t); + tda9887_configure(t); + break; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + SWITCH_V4L2; + if (V4L2_TUNER_ANALOG_TV == f->type) { + if (t->radio == 0) + return 0; + t->radio = 0; + } + if (V4L2_TUNER_RADIO == f->type) { + if (t->radio == 1) + return 0; + t->radio = 1; + } + tda9887_configure(t); + break; + } + case VIDIOC_G_TUNER: + { + static int AFC_BITS_2_kHz[] = { + -12500, -37500, -62500, -97500, + -112500, -137500, -162500, -187500, + 187500, 162500, 137500, 112500, + 97500 , 62500, 37500 , 12500 + }; + struct v4l2_tuner* tuner = arg; + + if (t->radio) { + __u8 reg = 0; + tuner->afc=0; + if (1 == i2c_master_recv(&t->client,®,1)) + tuner->afc = AFC_BITS_2_kHz[(reg>>1)&0x0f]; + } + break; + } + default: + /* nothing */ + break; + } + return 0; +} + +static int tda9887_suspend(struct device * dev, u32 state, u32 level) +{ + dprintk("tda9887: suspend\n"); + return 0; +} + +static int tda9887_resume(struct device * dev, u32 level) +{ + struct i2c_client *c = container_of(dev, struct i2c_client, dev); + struct tda9887 *t = i2c_get_clientdata(c); + + dprintk("tda9887: resume\n"); + tda9887_configure(t); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "i2c tda9887 driver", + .id = -1, /* FIXME */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda9887_probe, + .detach_client = tda9887_detach, + .command = tda9887_command, + .driver = { + .suspend = tda9887_suspend, + .resume = tda9887_resume, + }, +}; +static struct i2c_client client_template = +{ + I2C_DEVNAME("tda9887"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int __init tda9887_init_module(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit tda9887_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda9887_init_module); +module_exit(tda9887_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tea6415c.c b/drivers/media/video/tea6415c.c new file mode 100644 index 000000000000..3ec39550bf46 --- /dev/null +++ b/drivers/media/video/tea6415c.c @@ -0,0 +1,223 @@ + /* + tea6415c - i2c-driver for the tea6415c by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + The tea6415c is a bus controlled video-matrix-switch + with 8 inputs and 6 outputs. + It is cascadable, i.e. it can be found at the addresses + 0x86 and 0x06 on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License vs published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mvss Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> + +#include "tea6415c.h" + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); +#define dprintk(args...) \ + do { if (debug) { printk("%s: %s()[%d]: ",__stringify(KBUILD_MODNAME), __FUNCTION__, __LINE__); printk(args); } } while (0) + +#define TEA6415C_NUM_INPUTS 8 +#define TEA6415C_NUM_OUTPUTS 6 + +/* addresses to scan, found only at 0x03 and/or 0x43 (7-bit) */ +static unsigned short normal_i2c[] = { I2C_TEA6415C_1, I2C_TEA6415C_2, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* this function is called by i2c_probe */ +static int detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client = NULL; + int err = 0; + + /* let's see whether this adapter can support what we need */ + if (0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) { + return 0; + } + + /* allocate memory for client structure */ + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (0 == client) { + return -ENOMEM; + } + + /* fill client structure */ + memcpy(client, &client_template, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + + /* tell the i2c layer a new client has arrived */ + if (0 != (err = i2c_attach_client(client))) { + kfree(client); + return err; + } + + printk("tea6415c: detected @ 0x%02x on adapter %s\n", address, &client->adapter->name[0]); + + return 0; +} + +static int attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if (adapter->id != I2C_ALGO_SAA7146) { + dprintk("refusing to probe on unknown adapter [name='%s',id=0x%x]\n", adapter->name, adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter, &addr_data, &detect); +} + +static int detach(struct i2c_client *client) +{ + int ret = i2c_detach_client(client); + kfree(client); + return ret; +} + +/* makes a connection between the input-pin 'i' and the output-pin 'o' + for the tea6415c-client 'client' */ +static int switch_matrix(struct i2c_client *client, int i, int o) +{ + u8 byte = 0; + int ret; + + dprintk("adr:0x%02x, i:%d, o:%d\n", client->addr, i, o); + + /* check if the pins are valid */ + if (0 == ((1 == i || 3 == i || 5 == i || 6 == i || 8 == i || 10 == i || 20 == i || 11 == i) + && (18 == o || 17 == o || 16 == o || 15 == o || 14 == o || 13 == o))) + return -1; + + /* to understand this, have a look at the tea6415c-specs (p.5) */ + switch (o) { + case 18: + byte = 0x00; + break; + case 14: + byte = 0x20; + break; + case 16: + byte = 0x10; + break; + case 17: + byte = 0x08; + break; + case 15: + byte = 0x18; + break; + case 13: + byte = 0x28; + break; + }; + + switch (i) { + case 5: + byte |= 0x00; + break; + case 8: + byte |= 0x04; + break; + case 3: + byte |= 0x02; + break; + case 20: + byte |= 0x06; + break; + case 6: + byte |= 0x01; + break; + case 10: + byte |= 0x05; + break; + case 1: + byte |= 0x03; + break; + case 11: + byte |= 0x07; + break; + }; + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + + return ret; +} + +static int command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tea6415c_multiplex *v = (struct tea6415c_multiplex *)arg; + int result = 0; + + switch (cmd) { + case TEA6415C_SWITCH: + result = switch_matrix(client, v->in, v->out); + break; + default: + return -ENOIOCTLCMD; + } + + return result; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tea6415c", + .id = I2C_DRIVERID_TEA6415C, + .flags = I2C_DF_NOTIFY, + .attach_adapter = attach, + .detach_client = detach, + .command = command, +}; + +static struct i2c_client client_template = { + I2C_DEVNAME("tea6415c"), + .driver = &driver, +}; + +static int __init this_module_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit this_module_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(this_module_init); +module_exit(this_module_exit); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6415c driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tea6415c.h b/drivers/media/video/tea6415c.h new file mode 100644 index 000000000000..f84ed80050b3 --- /dev/null +++ b/drivers/media/video/tea6415c.h @@ -0,0 +1,39 @@ +#ifndef __INCLUDED_TEA6415C__ +#define __INCLUDED_TEA6415C__ + +/* possible i2c-addresses */ +#define I2C_TEA6415C_1 0x03 +#define I2C_TEA6415C_2 0x43 + +/* the tea6415c's design is quite brain-dead. although there are + 8 inputs and 6 outputs, these aren't enumerated in any way. because + I don't want to say "connect input pin 20 to output pin 17", I define + a "virtual" pin-order. */ + +/* input pins */ +#define TEA6415C_OUTPUT1 18 +#define TEA6415C_OUTPUT2 14 +#define TEA6415C_OUTPUT3 16 +#define TEA6415C_OUTPUT4 17 +#define TEA6415C_OUTPUT5 13 +#define TEA6415C_OUTPUT6 15 + +/* output pins */ +#define TEA6415C_INPUT1 5 +#define TEA6415C_INPUT2 8 +#define TEA6415C_INPUT3 3 +#define TEA6415C_INPUT4 20 +#define TEA6415C_INPUT5 6 +#define TEA6415C_INPUT6 10 +#define TEA6415C_INPUT7 1 +#define TEA6415C_INPUT8 11 + +struct tea6415c_multiplex +{ + int in; /* input-pin */ + int out; /* output-pin */ +}; + +#define TEA6415C_SWITCH _IOW('v',1,struct tea6415c_multiplex) + +#endif diff --git a/drivers/media/video/tea6420.c b/drivers/media/video/tea6420.c new file mode 100644 index 000000000000..bd10710fd909 --- /dev/null +++ b/drivers/media/video/tea6420.c @@ -0,0 +1,200 @@ + /* + tea6420 - i2c-driver for the tea6420 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + The tea6420 is a bus controlled audio-matrix with 5 stereo inputs, + 4 stereo outputs and gain control for each output. + It is cascadable, i.e. it can be found at the adresses 0x98 + and 0x9a on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> + +#include "tea6420.h" + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); +#define dprintk(args...) \ + do { if (debug) { printk("%s: %s()[%d]: ",__stringify(KBUILD_MODNAME), __FUNCTION__, __LINE__); printk(args); } } while (0) + +/* addresses to scan, found only at 0x4c and/or 0x4d (7-Bit) */ +static unsigned short normal_i2c[] = { I2C_TEA6420_1, I2C_TEA6420_2, I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* make a connection between the input 'i' and the output 'o' + with gain 'g' for the tea6420-client 'client' (note: i = 6 means 'mute') */ +static int tea6420_switch(struct i2c_client *client, int i, int o, int g) +{ + u8 byte = 0; + int ret; + + dprintk("adr:0x%02x, i:%d, o:%d, g:%d\n", client->addr, i, o, g); + + /* check if the paramters are valid */ + if (i < 1 || i > 6 || o < 1 || o > 4 || g < 0 || g > 6 || g % 2 != 0) + return -1; + + byte = ((o - 1) << 5); + byte |= (i - 1); + + /* to understand this, have a look at the tea6420-specs (p.5) */ + switch (g) { + case 0: + byte |= (3 << 3); + break; + case 2: + byte |= (2 << 3); + break; + case 4: + byte |= (1 << 3); + break; + case 6: + break; + } + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + + return 0; +} + +/* this function is called by i2c_probe */ +static int tea6420_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + int err = 0, i = 0; + + /* let's see whether this adapter can support what we need */ + if (0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) { + return 0; + } + + /* allocate memory for client structure */ + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (0 == client) { + return -ENOMEM; + } + memset(client, 0x0, sizeof(struct i2c_client)); + + /* fill client structure */ + memcpy(client, &client_template, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + + /* tell the i2c layer a new client has arrived */ + if (0 != (err = i2c_attach_client(client))) { + kfree(client); + return err; + } + + /* set initial values: set "mute"-input to all outputs at gain 0 */ + err = 0; + for (i = 1; i < 5; i++) { + err += tea6420_switch(client, 6, i, 0); + } + if (err) { + dprintk("could not initialize tea6420\n"); + kfree(client); + return -ENODEV; + } + + printk("tea6420: detected @ 0x%02x on adapter %s\n", address, &client->adapter->name[0]); + + return 0; +} + +static int attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if (adapter->id != I2C_ALGO_SAA7146) { + dprintk("refusing to probe on unknown adapter [name='%s',id=0x%x]\n", adapter->name, adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter, &addr_data, &tea6420_detect); +} + +static int detach(struct i2c_client *client) +{ + int ret = i2c_detach_client(client); + kfree(client); + return ret; +} + +static int command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tea6420_multiplex *a = (struct tea6420_multiplex *)arg; + int result = 0; + + switch (cmd) { + case TEA6420_SWITCH: + result = tea6420_switch(client, a->in, a->out, a->gain); + break; + default: + return -ENOIOCTLCMD; + } + + return result; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tea6420", + .id = I2C_DRIVERID_TEA6420, + .flags = I2C_DF_NOTIFY, + .attach_adapter = attach, + .detach_client = detach, + .command = command, +}; + +static struct i2c_client client_template = { + I2C_DEVNAME("tea6420"), + .driver = &driver, +}; + +static int __init this_module_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit this_module_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(this_module_init); +module_exit(this_module_exit); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6420 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tea6420.h b/drivers/media/video/tea6420.h new file mode 100644 index 000000000000..ea664df15ad4 --- /dev/null +++ b/drivers/media/video/tea6420.h @@ -0,0 +1,17 @@ +#ifndef __INCLUDED_TEA6420__ +#define __INCLUDED_TEA6420__ + +/* possible addresses */ +#define I2C_TEA6420_1 0x4c +#define I2C_TEA6420_2 0x4d + +struct tea6420_multiplex +{ + int in; /* input of audio switch */ + int out; /* output of audio switch */ + int gain; /* gain of connection */ +}; + +#define TEA6420_SWITCH _IOW('v',1,struct tea6420_multiplex) + +#endif diff --git a/drivers/media/video/tuner-3036.c b/drivers/media/video/tuner-3036.c new file mode 100644 index 000000000000..6b20aa902a8f --- /dev/null +++ b/drivers/media/video/tuner-3036.c @@ -0,0 +1,220 @@ +/* + * Driver for Philips SAB3036 "CITAC" tuner control chip. + * + * Author: Phil Blundell <philb@gnu.org> + * + * The SAB3036 is just about different enough from the chips that + * tuner.c copes with to make it not worth the effort to crowbar + * the support into that file. So instead we have a separate 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include <linux/i2c.h> +#include <linux/videodev.h> + +#include <media/tuner.h> + +static int debug; /* insmod parameter */ +static int this_adap; + +static struct i2c_client client_template; + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {0x60, 0x61, I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +/* ---------------------------------------------------------------------- */ + +static unsigned char +tuner_getstatus (struct i2c_client *c) +{ + unsigned char byte; + if (i2c_master_recv(c, &byte, 1) != 1) + printk(KERN_ERR "tuner-3036: I/O error.\n"); + return byte; +} + +#define TUNER_FL 0x80 + +static int +tuner_islocked (struct i2c_client *c) +{ + return (tuner_getstatus(c) & TUNER_FL); +} + +/* ---------------------------------------------------------------------- */ + +static void +set_tv_freq(struct i2c_client *c, int freq) +{ + u16 div = ((freq * 20) / 16); + unsigned long give_up = jiffies + HZ; + unsigned char buffer[2]; + + if (debug) + printk(KERN_DEBUG "tuner: setting frequency %dMHz, divisor %x\n", freq / 16, div); + + /* Select high tuning current */ + buffer[0] = 0x29; + buffer[1] = 0x3e; + + if (i2c_master_send(c, buffer, 2) != 2) + printk("tuner: i2c i/o error 1\n"); + + buffer[0] = 0x80 | ((div>>8) & 0x7f); + buffer[1] = div & 0xff; + + if (i2c_master_send(c, buffer, 2) != 2) + printk("tuner: i2c i/o error 2\n"); + + while (!tuner_islocked(c) && time_before(jiffies, give_up)) + schedule(); + + if (!tuner_islocked(c)) + printk(KERN_WARNING "tuner: failed to achieve PLL lock\n"); + + /* Select low tuning current and engage AFC */ + buffer[0] = 0x29; + buffer[1] = 0xb2; + + if (i2c_master_send(c, buffer, 2) != 2) + printk("tuner: i2c i/o error 3\n"); + + if (debug) + printk(KERN_DEBUG "tuner: status %02x\n", tuner_getstatus(c)); +} + +/* ---------------------------------------------------------------------- */ + +static int +tuner_attach(struct i2c_adapter *adap, int addr, int kind) +{ + static unsigned char buffer[] = { 0x29, 0x32, 0x2a, 0, 0x2b, 0 }; + + struct i2c_client *client; + + if (this_adap > 0) + return -1; + this_adap++; + + client_template.adapter = adap; + client_template.addr = addr; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == NULL) + return -ENOMEM; + memcpy(client, &client_template, sizeof(struct i2c_client)); + + printk("tuner: SAB3036 found, status %02x\n", tuner_getstatus(client)); + + i2c_attach_client(client); + + if (i2c_master_send(client, buffer, 2) != 2) + printk("tuner: i2c i/o error 1\n"); + if (i2c_master_send(client, buffer+2, 2) != 2) + printk("tuner: i2c i/o error 2\n"); + if (i2c_master_send(client, buffer+4, 2) != 2) + printk("tuner: i2c i/o error 3\n"); + return 0; +} + +static int +tuner_detach(struct i2c_client *c) +{ + return 0; +} + +static int +tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + int *iarg = (int*)arg; + + switch (cmd) + { + case TUNER_SET_TVFREQ: + set_tv_freq(client, *iarg); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int +tuner_probe(struct i2c_adapter *adap) +{ + this_adap = 0; + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_LP)) + return i2c_probe(adap, &addr_data, tuner_attach); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver +i2c_driver_tuner = +{ + .owner = THIS_MODULE, + .name = "sab3036", + .id = I2C_DRIVERID_SAB3036, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tuner_probe, + .detach_client = tuner_detach, + .command = tuner_command +}; + +static struct i2c_client client_template = +{ + .driver = &i2c_driver_tuner, + .name = "SAB3036", +}; + +static int __init +tuner3036_init(void) +{ + i2c_add_driver(&i2c_driver_tuner); + return 0; +} + +static void __exit +tuner3036_exit(void) +{ + i2c_del_driver(&i2c_driver_tuner); +} + +MODULE_DESCRIPTION("SAB3036 tuner driver"); +MODULE_AUTHOR("Philip Blundell <philb@gnu.org>"); +MODULE_LICENSE("GPL"); + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug,"Enable debugging output"); + +module_init(tuner3036_init); +module_exit(tuner3036_exit); diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c new file mode 100644 index 000000000000..2f4e18d20b7a --- /dev/null +++ b/drivers/media/video/tuner-core.c @@ -0,0 +1,443 @@ +/* + * $Id: tuner-core.c,v 1.5 2005/02/15 15:59:35 kraxel Exp $ + * + * i2c tv tuner chip device driver + * core core, i.e. kernel interfaces, registering and so on + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> + +#include <media/tuner.h> +#include <media/audiochip.h> + +#define UNSET (-1U) + +/* standard i2c insmod options */ +static unsigned short normal_i2c[] = { + 0x4b, /* tda8290 */ + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = { + 0x60, 0x6f, + I2C_CLIENT_END +}; +I2C_CLIENT_INSMOD; + +/* insmod options used at init time => read/only */ +static unsigned int addr = 0; +module_param(addr, int, 0444); + +/* insmod options used at runtime => read/write */ +unsigned int tuner_debug = 0; +module_param(tuner_debug, int, 0644); + +static unsigned int tv_range[2] = { 44, 958 }; +static unsigned int radio_range[2] = { 65, 108 }; + +module_param_array(tv_range, int, NULL, 0644); +module_param_array(radio_range, int, NULL, 0644); + +MODULE_DESCRIPTION("device driver for various TV and TV+FM radio tuners"); +MODULE_AUTHOR("Ralph Metzler, Gerd Knorr, Gunther Mayer"); +MODULE_LICENSE("GPL"); + +static int this_adap; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* ---------------------------------------------------------------------- */ + +// Set tuner frequency, freq in Units of 62.5kHz = 1/16MHz +static void set_tv_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + + if (t->type == UNSET) { + tuner_info("tuner type not set\n"); + return; + } + if (NULL == t->tv_freq) { + tuner_info("Huh? tv_set is NULL?\n"); + return; + } + if (freq < tv_range[0]*16 || freq > tv_range[1]*16) { + /* FIXME: better do that chip-specific, but + right now we don't have that in the config + struct and this way is still better than no + check at all */ + tuner_info("TV freq (%d.%02d) out of range (%d-%d)\n", + freq/16,freq%16*100/16,tv_range[0],tv_range[1]); + return; + } + t->tv_freq(c,freq); +} + +static void set_radio_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + + if (t->type == UNSET) { + tuner_info("tuner type not set\n"); + return; + } + if (NULL == t->radio_freq) { + tuner_info("no radio tuning for this one, sorry.\n"); + return; + } + if (freq < radio_range[0]*16 || freq > radio_range[1]*16) { + tuner_info("radio freq (%d.%02d) out of range (%d-%d)\n", + freq/16,freq%16*100/16, + radio_range[0],radio_range[1]); + return; + } + t->radio_freq(c,freq); +} + +static void set_freq(struct i2c_client *c, unsigned long freq) +{ + struct tuner *t = i2c_get_clientdata(c); + + switch (t->mode) { + case V4L2_TUNER_RADIO: + tuner_dbg("radio freq set to %lu.%02lu\n", + freq/16,freq%16*100/16); + set_radio_freq(c,freq); + break; + case V4L2_TUNER_ANALOG_TV: + case V4L2_TUNER_DIGITAL_TV: + tuner_dbg("tv freq set to %lu.%02lu\n", + freq/16,freq%16*100/16); + set_tv_freq(c, freq); + break; + } + t->freq = freq; +} + +static void set_type(struct i2c_client *c, unsigned int type) +{ + struct tuner *t = i2c_get_clientdata(c); + + /* sanity check */ + if (type == UNSET || type == TUNER_ABSENT) + return; + if (type >= tuner_count) + return; + + if (NULL == t->i2c.dev.driver) { + /* not registered yet */ + t->type = type; + return; + } + if (t->initialized) + /* run only once */ + return; + + t->initialized = 1; + t->type = type; + switch (t->type) { + case TUNER_MT2032: + microtune_init(c); + break; + case TUNER_PHILIPS_TDA8290: + tda8290_init(c); + break; + default: + default_tuner_init(c); + break; + } +} + +static char pal[] = "-"; +module_param_string(pal, pal, 0644, sizeof(pal)); + +static int tuner_fixup_std(struct tuner *t) +{ + if ((t->std & V4L2_STD_PAL) == V4L2_STD_PAL) { + /* get more precise norm info from insmod option */ + switch (pal[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + tuner_dbg("insmod fixup: PAL => PAL-BG\n"); + t->std = V4L2_STD_PAL_BG; + break; + case 'i': + case 'I': + tuner_dbg("insmod fixup: PAL => PAL-I\n"); + t->std = V4L2_STD_PAL_I; + break; + case 'd': + case 'D': + case 'k': + case 'K': + tuner_dbg("insmod fixup: PAL => PAL-DK\n"); + t->std = V4L2_STD_PAL_DK; + break; + } + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static int tuner_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct tuner *t; + + if (this_adap > 0) + return -1; + this_adap++; + + client_template.adapter = adap; + client_template.addr = addr; + + t = kmalloc(sizeof(struct tuner),GFP_KERNEL); + if (NULL == t) + return -ENOMEM; + memset(t,0,sizeof(struct tuner)); + memcpy(&t->i2c,&client_template,sizeof(struct i2c_client)); + i2c_set_clientdata(&t->i2c, t); + t->type = UNSET; + t->radio_if2 = 10700*1000; // 10.7MHz - FM radio + + i2c_attach_client(&t->i2c); + tuner_info("chip found @ 0x%x (%s)\n", + addr << 1, adap->name); + set_type(&t->i2c, t->type); + return 0; +} + +static int tuner_probe(struct i2c_adapter *adap) +{ + if (0 != addr) { + normal_i2c[0] = addr; + normal_i2c_range[0] = addr; + normal_i2c_range[1] = addr; + } + this_adap = 0; + + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, tuner_attach); + return 0; +} + +static int tuner_detach(struct i2c_client *client) +{ + struct tuner *t = i2c_get_clientdata(client); + + i2c_detach_client(&t->i2c); + kfree(t); + return 0; +} + +#define SWITCH_V4L2 if (!t->using_v4l2 && tuner_debug) \ + tuner_info("switching to v4l2\n"); \ + t->using_v4l2 = 1; +#define CHECK_V4L2 if (t->using_v4l2) { if (tuner_debug) \ + tuner_info("ignore v4l1 call\n"); \ + return 0; } + +static int +tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tuner *t = i2c_get_clientdata(client); + unsigned int *iarg = (int*)arg; + + switch (cmd) { + + /* --- configuration --- */ + case TUNER_SET_TYPE: + set_type(client,*iarg); + break; + case AUDC_SET_RADIO: + if (V4L2_TUNER_RADIO != t->mode) { + set_tv_freq(client,400 * 16); + t->mode = V4L2_TUNER_RADIO; + } + break; + case AUDC_CONFIG_PINNACLE: + switch (*iarg) { + case 2: + tuner_dbg("pinnacle pal\n"); + t->radio_if2 = 33300 * 1000; + break; + case 3: + tuner_dbg("pinnacle ntsc\n"); + t->radio_if2 = 41300 * 1000; + break; + } + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCSCHAN: + { + static const v4l2_std_id map[] = { + [ VIDEO_MODE_PAL ] = V4L2_STD_PAL, + [ VIDEO_MODE_NTSC ] = V4L2_STD_NTSC_M, + [ VIDEO_MODE_SECAM ] = V4L2_STD_SECAM, + [ 4 /* bttv */ ] = V4L2_STD_PAL_M, + [ 5 /* bttv */ ] = V4L2_STD_PAL_N, + [ 6 /* bttv */ ] = V4L2_STD_NTSC_M_JP, + }; + struct video_channel *vc = arg; + + CHECK_V4L2; + t->mode = V4L2_TUNER_ANALOG_TV; + if (vc->norm < ARRAY_SIZE(map)) + t->std = map[vc->norm]; + tuner_fixup_std(t); + if (t->freq) + set_tv_freq(client,t->freq); + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *v = arg; + + CHECK_V4L2; + set_freq(client,*v); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *vt = arg; + + CHECK_V4L2; + if (V4L2_TUNER_RADIO == t->mode && t->has_signal) + vt->signal = t->has_signal(client); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + CHECK_V4L2; + if (V4L2_TUNER_RADIO == t->mode && t->is_stereo) + va->mode = t->is_stereo(client) + ? VIDEO_SOUND_STEREO + : VIDEO_SOUND_MONO; + return 0; + } + + case VIDIOC_S_STD: + { + v4l2_std_id *id = arg; + + SWITCH_V4L2; + t->mode = V4L2_TUNER_ANALOG_TV; + t->std = *id; + tuner_fixup_std(t); + if (t->freq) + set_freq(client,t->freq); + break; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + SWITCH_V4L2; + if (V4L2_TUNER_RADIO == f->type && + V4L2_TUNER_RADIO != t->mode) + set_tv_freq(client,400*16); + t->mode = f->type; + t->freq = f->frequency; + set_freq(client,t->freq); + break; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *tuner = arg; + + SWITCH_V4L2; + if (V4L2_TUNER_RADIO == t->mode && t->has_signal) + tuner->signal = t->has_signal(client); + break; + } + default: + /* nothing */ + break; + } + + return 0; +} + +static int tuner_suspend(struct device * dev, u32 state, u32 level) +{ + struct i2c_client *c = container_of(dev, struct i2c_client, dev); + struct tuner *t = i2c_get_clientdata(c); + + tuner_dbg("suspend\n"); + /* FIXME: power down ??? */ + return 0; +} + +static int tuner_resume(struct device * dev, u32 level) +{ + struct i2c_client *c = container_of(dev, struct i2c_client, dev); + struct tuner *t = i2c_get_clientdata(c); + + tuner_dbg("resume\n"); + if (t->freq) + set_freq(c,t->freq); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tuner", + .id = I2C_DRIVERID_TUNER, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tuner_probe, + .detach_client = tuner_detach, + .command = tuner_command, + .driver = { + .suspend = tuner_suspend, + .resume = tuner_resume, + }, +}; +static struct i2c_client client_template = +{ + I2C_DEVNAME("(tuner unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int __init tuner_init_module(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit tuner_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tuner_init_module); +module_exit(tuner_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tuner-simple.c b/drivers/media/video/tuner-simple.c new file mode 100644 index 000000000000..48c6ceff1dc2 --- /dev/null +++ b/drivers/media/video/tuner-simple.c @@ -0,0 +1,474 @@ +/* + * $Id: tuner-simple.c,v 1.10 2005/03/08 08:38:00 kraxel Exp $ + * + * i2c tv tuner chip device driver + * controls all those simple 4-control-bytes style tuners. + */ +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <media/tuner.h> + +/* ---------------------------------------------------------------------- */ + +/* tv standard selection for Temic 4046 FM5 + this value takes the low bits of control byte 2 + from datasheet Rev.01, Feb.00 + standard BG I L L2 D + picture IF 38.9 38.9 38.9 33.95 38.9 + sound 1 33.4 32.9 32.4 40.45 32.4 + sound 2 33.16 + NICAM 33.05 32.348 33.05 33.05 + */ +#define TEMIC_SET_PAL_I 0x05 +#define TEMIC_SET_PAL_DK 0x09 +#define TEMIC_SET_PAL_L 0x0a // SECAM ? +#define TEMIC_SET_PAL_L2 0x0b // change IF ! +#define TEMIC_SET_PAL_BG 0x0c + +/* tv tuner system standard selection for Philips FQ1216ME + this value takes the low bits of control byte 2 + from datasheet "1999 Nov 16" (supersedes "1999 Mar 23") + standard BG DK I L L` + picture carrier 38.90 38.90 38.90 38.90 33.95 + colour 34.47 34.47 34.47 34.47 38.38 + sound 1 33.40 32.40 32.90 32.40 40.45 + sound 2 33.16 - - - - + NICAM 33.05 33.05 32.35 33.05 39.80 + */ +#define PHILIPS_SET_PAL_I 0x01 /* Bit 2 always zero !*/ +#define PHILIPS_SET_PAL_BGDK 0x09 +#define PHILIPS_SET_PAL_L2 0x0a +#define PHILIPS_SET_PAL_L 0x0b + +/* system switching for Philips FI1216MF MK2 + from datasheet "1996 Jul 09", + standard BG L L' + picture carrier 38.90 38.90 33.95 + colour 34.47 34.37 38.38 + sound 1 33.40 32.40 40.45 + sound 2 33.16 - - + NICAM 33.05 33.05 39.80 + */ +#define PHILIPS_MF_SET_BG 0x01 /* Bit 2 must be zero, Bit 3 is system output */ +#define PHILIPS_MF_SET_PAL_L 0x03 // France +#define PHILIPS_MF_SET_PAL_L2 0x02 // L' + + +/* ---------------------------------------------------------------------- */ + +struct tunertype +{ + char *name; + unsigned char Vendor; + unsigned char Type; + + unsigned short thresh1; /* band switch VHF_LO <=> VHF_HI */ + unsigned short thresh2; /* band switch VHF_HI <=> UHF */ + unsigned char VHF_L; + unsigned char VHF_H; + unsigned char UHF; + unsigned char config; + unsigned short IFPCoff; /* 622.4=16*38.90 MHz PAL, + 732 =16*45.75 NTSCi, + 940 =16*58.75 NTSC-Japan + 704 =16*44 ATSC */ +}; + +/* + * The floats in the tuner struct are computed at compile time + * by gcc and cast back to integers. Thus we don't violate the + * "no float in kernel" rule. + */ +static struct tunertype tuners[] = { + { "Temic PAL (4002 FH5)", TEMIC, PAL, + 16*140.25,16*463.25,0x02,0x04,0x01,0x8e,623}, + { "Philips PAL_I (FI1246 and compatibles)", Philips, PAL_I, + 16*140.25,16*463.25,0xa0,0x90,0x30,0x8e,623}, + { "Philips NTSC (FI1236,FM1236 and compatibles)", Philips, NTSC, + 16*157.25,16*451.25,0xA0,0x90,0x30,0x8e,732}, + { "Philips (SECAM+PAL_BG) (FI1216MF, FM1216MF, FR1216MF)", Philips, SECAM, + 16*168.25,16*447.25,0xA7,0x97,0x37,0x8e,623}, + + { "NoTuner", NoTuner, NOTUNER, + 0,0,0x00,0x00,0x00,0x00,0x00}, + { "Philips PAL_BG (FI1216 and compatibles)", Philips, PAL, + 16*168.25,16*447.25,0xA0,0x90,0x30,0x8e,623}, + { "Temic NTSC (4032 FY5)", TEMIC, NTSC, + 16*157.25,16*463.25,0x02,0x04,0x01,0x8e,732}, + { "Temic PAL_I (4062 FY5)", TEMIC, PAL_I, + 16*170.00,16*450.00,0x02,0x04,0x01,0x8e,623}, + + { "Temic NTSC (4036 FY5)", TEMIC, NTSC, + 16*157.25,16*463.25,0xa0,0x90,0x30,0x8e,732}, + { "Alps HSBH1", TEMIC, NTSC, + 16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732}, + { "Alps TSBE1",TEMIC,PAL, + 16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732}, + { "Alps TSBB5", Alps, PAL_I, /* tested (UK UHF) with Modulartech MM205 */ + 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,632}, + + { "Alps TSBE5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */ + 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,622}, + { "Alps TSBC5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */ + 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,608}, + { "Temic PAL_BG (4006FH5)", TEMIC, PAL, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + { "Alps TSCH6",Alps,NTSC, + 16*137.25,16*385.25,0x14,0x12,0x11,0x8e,732}, + + { "Temic PAL_DK (4016 FY5)",TEMIC,PAL, + 16*168.25,16*456.25,0xa0,0x90,0x30,0x8e,623}, + { "Philips NTSC_M (MK2)",Philips,NTSC, + 16*160.00,16*454.00,0xa0,0x90,0x30,0x8e,732}, + { "Temic PAL_I (4066 FY5)", TEMIC, PAL_I, + 16*169.00, 16*454.00, 0xa0,0x90,0x30,0x8e,623}, + { "Temic PAL* auto (4006 FN5)", TEMIC, PAL, + 16*169.00, 16*454.00, 0xa0,0x90,0x30,0x8e,623}, + + { "Temic PAL_BG (4009 FR5) or PAL_I (4069 FR5)", TEMIC, PAL, + 16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623}, + { "Temic NTSC (4039 FR5)", TEMIC, NTSC, + 16*158.00, 16*453.00, 0xa0,0x90,0x30,0x8e,732}, + { "Temic PAL/SECAM multi (4046 FM5)", TEMIC, PAL, + 16*169.00, 16*454.00, 0xa0,0x90,0x30,0x8e,623}, + { "Philips PAL_DK (FI1256 and compatibles)", Philips, PAL, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + + { "Philips PAL/SECAM multi (FQ1216ME)", Philips, PAL, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + { "LG PAL_I+FM (TAPC-I001D)", LGINNOTEK, PAL_I, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + { "LG PAL_I (TAPC-I701D)", LGINNOTEK, PAL_I, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + { "LG NTSC+FM (TPI8NSR01F)", LGINNOTEK, NTSC, + 16*210.00,16*497.00,0xa0,0x90,0x30,0x8e,732}, + + { "LG PAL_BG+FM (TPI8PSB01D)", LGINNOTEK, PAL, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + { "LG PAL_BG (TPI8PSB11D)", LGINNOTEK, PAL, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, + { "Temic PAL* auto + FM (4009 FN5)", TEMIC, PAL, + 16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623}, + { "SHARP NTSC_JP (2U5JF5540)", SHARP, NTSC, /* 940=16*58.75 NTSC@Japan */ + 16*137.25,16*317.25,0x01,0x02,0x08,0x8e,940 }, + + { "Samsung PAL TCPM9091PD27", Samsung, PAL, /* from sourceforge v3tv */ + 16*169,16*464,0xA0,0x90,0x30,0x8e,623}, + { "MT20xx universal", Microtune,PAL|NTSC, + /* see mt20xx.c for details */ }, + { "Temic PAL_BG (4106 FH5)", TEMIC, PAL, + 16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623}, + { "Temic PAL_DK/SECAM_L (4012 FY5)", TEMIC, PAL, + 16*140.25, 16*463.25, 0x02,0x04,0x01,0x8e,623}, + + { "Temic NTSC (4136 FY5)", TEMIC, NTSC, + 16*158.00, 16*453.00, 0xa0,0x90,0x30,0x8e,732}, + { "LG PAL (newer TAPC series)", LGINNOTEK, PAL, + 16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,623}, + { "Philips PAL/SECAM multi (FM1216ME MK3)", Philips, PAL, + 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,623 }, + { "LG NTSC (newer TAPC series)", LGINNOTEK, NTSC, + 16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,732}, + + { "HITACHI V7-J180AT", HITACHI, NTSC, + 16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,940 }, + { "Philips PAL_MK (FI1216 MK)", Philips, PAL, + 16*140.25,16*463.25,0x01,0xc2,0xcf,0x8e,623}, + { "Philips 1236D ATSC/NTSC daul in",Philips,ATSC, + 16*157.25,16*454.00,0xa0,0x90,0x30,0x8e,732}, + { "Philips NTSC MK3 (FM1236MK3 or FM1236/F)", Philips, NTSC, + 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,732}, + + { "Philips 4 in 1 (ATI TV Wonder Pro/Conexant)", Philips, NTSC, + 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,732}, + { "Microtune 4049 FM5",Microtune,PAL, + 16*141.00,16*464.00,0xa0,0x90,0x30,0x8e,623}, + { "Panasonic VP27s/ENGE4324D", Panasonic, NTSC, + 16*160.00,16*454.00,0x01,0x02,0x08,0xce,940}, + { "LG NTSC (TAPE series)", LGINNOTEK, NTSC, + 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,732 }, + + { "Tenna TNF 8831 BGFF)", Philips, PAL, + 16*161.25,16*463.25,0xa0,0x90,0x30,0x8e,623}, + { "Microtune 4042 FI5 ATSC/NTSC dual in", Microtune, NTSC, + 16*162.00,16*457.00,0xa2,0x94,0x31,0x8e,732}, + { "TCL 2002N", TCL, NTSC, + 16*172.00,16*448.00,0x01,0x02,0x08,0x8e,732}, + { "Philips PAL/SECAM_D (FM 1256 I-H3)", Philips, PAL, + 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,623 }, + + { "Thomson DDT 7610 (ATSC/NTSC)", THOMSON, ATSC, + 16*157.25,16*454.00,0x39,0x3a,0x3c,0x8e,732}, + { "Philips FQ1286", Philips, NTSC, + 16*160.00,16*454.00,0x41,0x42,0x04,0x8e,940}, // UHF band untested + { "tda8290+75", Philips,PAL|NTSC, + /* see tda8290.c for details */ }, + { "LG PAL (TAPE series)", LGINNOTEK, PAL, + 16*170.00, 16*450.00, 0x01,0x02,0x08,0xce,623}, + + { "Philips PAL/SECAM multi (FQ1216AME MK4)", Philips, PAL, + 16*160.00,16*442.00,0x01,0x02,0x04,0xce,623 }, + { "Philips FQ1236A MK4", Philips, NTSC, + 16*160.00,16*442.00,0x01,0x02,0x04,0x8e,732 }, + +}; +unsigned const int tuner_count = ARRAY_SIZE(tuners); + +/* ---------------------------------------------------------------------- */ + +static int tuner_getstatus(struct i2c_client *c) +{ + unsigned char byte; + + if (1 != i2c_master_recv(c,&byte,1)) + return 0; + return byte; +} + +#define TUNER_POR 0x80 +#define TUNER_FL 0x40 +#define TUNER_MODE 0x38 +#define TUNER_AFC 0x07 + +#define TUNER_STEREO 0x10 /* radio mode */ +#define TUNER_SIGNAL 0x07 /* radio mode */ + +static int tuner_signal(struct i2c_client *c) +{ + return (tuner_getstatus(c) & TUNER_SIGNAL)<<13; +} + +static int tuner_stereo(struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_STEREO); +} + +#if 0 /* unused */ +static int tuner_islocked (struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_FL); +} + +static int tuner_afcstatus (struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_AFC) - 2; +} + +static int tuner_mode (struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_MODE) >> 3; +} +#endif + +/* ---------------------------------------------------------------------- */ + +static void default_set_tv_freq(struct i2c_client *c, unsigned int freq) +{ + struct tuner *t = i2c_get_clientdata(c); + u8 config; + u16 div; + struct tunertype *tun; + unsigned char buffer[4]; + int rc; + + tun = &tuners[t->type]; + if (freq < tun->thresh1) { + config = tun->VHF_L; + tuner_dbg("tv: VHF lowrange\n"); + } else if (freq < tun->thresh2) { + config = tun->VHF_H; + tuner_dbg("tv: VHF high range\n"); + } else { + config = tun->UHF; + tuner_dbg("tv: UHF range\n"); + } + + + /* tv norm specific stuff for multi-norm tuners */ + switch (t->type) { + case TUNER_PHILIPS_SECAM: // FI1216MF + /* 0x01 -> ??? no change ??? */ + /* 0x02 -> PAL BDGHI / SECAM L */ + /* 0x04 -> ??? PAL others / SECAM others ??? */ + config &= ~0x02; + if (t->std & V4L2_STD_SECAM) + config |= 0x02; + break; + + case TUNER_TEMIC_4046FM5: + config &= ~0x0f; + + if (t->std & V4L2_STD_PAL_BG) { + config |= TEMIC_SET_PAL_BG; + + } else if (t->std & V4L2_STD_PAL_I) { + config |= TEMIC_SET_PAL_I; + + } else if (t->std & V4L2_STD_PAL_DK) { + config |= TEMIC_SET_PAL_DK; + + } else if (t->std & V4L2_STD_SECAM_L) { + config |= TEMIC_SET_PAL_L; + + } + break; + + case TUNER_PHILIPS_FQ1216ME: + config &= ~0x0f; + + if (t->std & (V4L2_STD_PAL_BG|V4L2_STD_PAL_DK)) { + config |= PHILIPS_SET_PAL_BGDK; + + } else if (t->std & V4L2_STD_PAL_I) { + config |= PHILIPS_SET_PAL_I; + + } else if (t->std & V4L2_STD_SECAM_L) { + config |= PHILIPS_SET_PAL_L; + + } + break; + + case TUNER_PHILIPS_ATSC: + /* 0x00 -> ATSC antenna input 1 */ + /* 0x01 -> ATSC antenna input 2 */ + /* 0x02 -> NTSC antenna input 1 */ + /* 0x03 -> NTSC antenna input 2 */ + config &= ~0x03; + if (!(t->std & V4L2_STD_ATSC)) + config |= 2; + /* FIXME: input */ + break; + + case TUNER_MICROTUNE_4042FI5: + /* Set the charge pump for fast tuning */ + tun->config |= 0x40; + break; + } + + /* + * Philips FI1216MK2 remark from specification : + * for channel selection involving band switching, and to ensure + * smooth tuning to the desired channel without causing + * unnecessary charge pump action, it is recommended to consider + * the difference between wanted channel frequency and the + * current channel frequency. Unnecessary charge pump action + * will result in very low tuning voltage which may drive the + * oscillator to extreme conditions. + * + * Progfou: specification says to send config data before + * frequency in case (wanted frequency < current frequency). + */ + + div=freq + tun->IFPCoff; + if (t->type == TUNER_PHILIPS_SECAM && freq < t->freq) { + buffer[0] = tun->config; + buffer[1] = config; + buffer[2] = (div>>8) & 0x7f; + buffer[3] = div & 0xff; + } else { + buffer[0] = (div>>8) & 0x7f; + buffer[1] = div & 0xff; + buffer[2] = tun->config; + buffer[3] = config; + } + tuner_dbg("tv 0x%02x 0x%02x 0x%02x 0x%02x\n", + buffer[0],buffer[1],buffer[2],buffer[3]); + + if (4 != (rc = i2c_master_send(c,buffer,4))) + tuner_warn("i2c i/o error: rc == %d (should be 4)\n",rc); + + if (t->type == TUNER_MICROTUNE_4042FI5) { + // FIXME - this may also work for other tuners + unsigned long timeout = jiffies + msecs_to_jiffies(1); + u8 status_byte = 0; + + /* Wait until the PLL locks */ + for (;;) { + if (time_after(jiffies,timeout)) + return; + if (1 != (rc = i2c_master_recv(c,&status_byte,1))) { + tuner_warn("i2c i/o read error: rc == %d (should be 1)\n",rc); + break; + } + /* bit 6 is PLL locked indicator */ + if (status_byte & 0x40) + break; + udelay(10); + } + + /* Set the charge pump for optimized phase noise figure */ + tun->config &= ~0x40; + buffer[0] = (div>>8) & 0x7f; + buffer[1] = div & 0xff; + buffer[2] = tun->config; + buffer[3] = config; + tuner_dbg("tv 0x%02x 0x%02x 0x%02x 0x%02x\n", + buffer[0],buffer[1],buffer[2],buffer[3]); + + if (4 != (rc = i2c_master_send(c,buffer,4))) + tuner_warn("i2c i/o error: rc == %d (should be 4)\n",rc); + } +} + +static void default_set_radio_freq(struct i2c_client *c, unsigned int freq) +{ + struct tunertype *tun; + struct tuner *t = i2c_get_clientdata(c); + unsigned char buffer[4]; + unsigned div; + int rc; + + tun=&tuners[t->type]; + div = freq + (int)(16*10.7); + buffer[2] = tun->config; + + switch (t->type) { + case TUNER_PHILIPS_FM1216ME_MK3: + case TUNER_PHILIPS_FM1236_MK3: + buffer[3] = 0x19; + break; + case TUNER_PHILIPS_FM1256_IH3: + div = (20 * freq)/16 + 333 * 2; + buffer[2] = 0x80; + buffer[3] = 0x19; + break; + case TUNER_LG_PAL_FM: + buffer[3] = 0xa5; + break; + default: + buffer[3] = 0xa4; + break; + } + buffer[0] = (div>>8) & 0x7f; + buffer[1] = div & 0xff; + + tuner_dbg("radio 0x%02x 0x%02x 0x%02x 0x%02x\n", + buffer[0],buffer[1],buffer[2],buffer[3]); + + if (4 != (rc = i2c_master_send(c,buffer,4))) + tuner_warn("i2c i/o error: rc == %d (should be 4)\n",rc); +} + +int default_tuner_init(struct i2c_client *c) +{ + struct tuner *t = i2c_get_clientdata(c); + + tuner_info("type set to %d (%s)\n", + t->type, tuners[t->type].name); + strlcpy(c->name, tuners[t->type].name, sizeof(c->name)); + + t->tv_freq = default_set_tv_freq; + t->radio_freq = default_set_radio_freq; + t->has_signal = tuner_signal; + t->is_stereo = tuner_stereo; + return 0; +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tvaudio.c b/drivers/media/video/tvaudio.c new file mode 100644 index 000000000000..065eb4007b1d --- /dev/null +++ b/drivers/media/video/tvaudio.c @@ -0,0 +1,1740 @@ +/* + * experimental driver for simple i2c audio chips. + * + * Copyright (c) 2000 Gerd Knorr + * based on code by: + * Eric Sandeen (eric_sandeen@bigfoot.com) + * Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Greg Alexander (galexand@acm.org) + * + * This code is placed under the terms of the GNU General Public License + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/init.h> +#include <linux/smp_lock.h> + +#include <media/audiochip.h> +#include <media/id.h> + +#include "tvaudio.h" + +/* ---------------------------------------------------------------------- */ +/* insmod args */ + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("device driver for various i2c TV sound decoder / audiomux chips"); +MODULE_AUTHOR("Eric Sandeen, Steve VanDeBogart, Greg Alexander, Gerd Knorr"); +MODULE_LICENSE("GPL"); + +#define UNSET (-1U) +#define dprintk if (debug) printk + +/* ---------------------------------------------------------------------- */ +/* our structs */ + +#define MAXREGS 64 + +struct CHIPSTATE; +typedef int (*getvalue)(int); +typedef int (*checkit)(struct CHIPSTATE*); +typedef int (*initialize)(struct CHIPSTATE*); +typedef int (*getmode)(struct CHIPSTATE*); +typedef void (*setmode)(struct CHIPSTATE*, int mode); +typedef void (*checkmode)(struct CHIPSTATE*); + +/* i2c command */ +typedef struct AUDIOCMD { + int count; /* # of bytes to send */ + unsigned char bytes[MAXREGS+1]; /* addr, data, data, ... */ +} audiocmd; + +/* chip description */ +struct CHIPDESC { + char *name; /* chip name */ + int id; /* ID */ + int addr_lo, addr_hi; /* i2c address range */ + int registers; /* # of registers */ + + int *insmodopt; + checkit checkit; + initialize initialize; + int flags; +#define CHIP_HAS_VOLUME 1 +#define CHIP_HAS_BASSTREBLE 2 +#define CHIP_HAS_INPUTSEL 4 + + /* various i2c command sequences */ + audiocmd init; + + /* which register has which value */ + int leftreg,rightreg,treblereg,bassreg; + + /* initialize with (defaults to 65535/65535/32768/32768 */ + int leftinit,rightinit,trebleinit,bassinit; + + /* functions to convert the values (v4l -> chip) */ + getvalue volfunc,treblefunc,bassfunc; + + /* get/set mode */ + getmode getmode; + setmode setmode; + + /* check / autoswitch audio after channel switches */ + checkmode checkmode; + + /* input switch register + values for v4l inputs */ + int inputreg; + int inputmap[8]; + int inputmute; + int inputmask; +}; +static struct CHIPDESC chiplist[]; + +/* current state of the chip */ +struct CHIPSTATE { + struct i2c_client c; + + /* index into CHIPDESC array */ + int type; + + /* shadow register set */ + audiocmd shadow; + + /* current settings */ + __u16 left,right,treble,bass,mode; + int prevmode; + int norm; + + /* thread */ + pid_t tpid; + struct completion texit; + wait_queue_head_t wq; + struct timer_list wt; + int done; + int watch_stereo; +}; + +#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */ + +/* ---------------------------------------------------------------------- */ +/* i2c addresses */ + +static unsigned short normal_i2c[] = { + I2C_TDA8425 >> 1, + I2C_TEA6300 >> 1, + I2C_TEA6420 >> 1, + I2C_TDA9840 >> 1, + I2C_TDA985x_L >> 1, + I2C_TDA985x_H >> 1, + I2C_TDA9874 >> 1, + I2C_PIC16C54 >> 1, + I2C_CLIENT_END }; +static unsigned short normal_i2c_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + + +/* ---------------------------------------------------------------------- */ +/* i2c I/O functions */ + +static int chip_write(struct CHIPSTATE *chip, int subaddr, int val) +{ + unsigned char buffer[2]; + + if (-1 == subaddr) { + dprintk("%s: chip_write: 0x%x\n", + i2c_clientname(&chip->c), val); + chip->shadow.bytes[1] = val; + buffer[0] = val; + if (1 != i2c_master_send(&chip->c,buffer,1)) { + printk(KERN_WARNING "%s: I/O error (write 0x%x)\n", + i2c_clientname(&chip->c), val); + return -1; + } + } else { + dprintk("%s: chip_write: reg%d=0x%x\n", + i2c_clientname(&chip->c), subaddr, val); + chip->shadow.bytes[subaddr+1] = val; + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(&chip->c,buffer,2)) { + printk(KERN_WARNING "%s: I/O error (write reg%d=0x%x)\n", + i2c_clientname(&chip->c), subaddr, val); + return -1; + } + } + return 0; +} + +static int chip_write_masked(struct CHIPSTATE *chip, int subaddr, int val, int mask) +{ + if (mask != 0) { + if (-1 == subaddr) { + val = (chip->shadow.bytes[1] & ~mask) | (val & mask); + } else { + val = (chip->shadow.bytes[subaddr+1] & ~mask) | (val & mask); + } + } + return chip_write(chip, subaddr, val); +} + +static int chip_read(struct CHIPSTATE *chip) +{ + unsigned char buffer; + + if (1 != i2c_master_recv(&chip->c,&buffer,1)) { + printk(KERN_WARNING "%s: I/O error (read)\n", + i2c_clientname(&chip->c)); + return -1; + } + dprintk("%s: chip_read: 0x%x\n",i2c_clientname(&chip->c),buffer); + return buffer; +} + +static int chip_read2(struct CHIPSTATE *chip, int subaddr) +{ + unsigned char write[1]; + unsigned char read[1]; + struct i2c_msg msgs[2] = { + { chip->c.addr, 0, 1, write }, + { chip->c.addr, I2C_M_RD, 1, read } + }; + write[0] = subaddr; + + if (2 != i2c_transfer(chip->c.adapter,msgs,2)) { + printk(KERN_WARNING "%s: I/O error (read2)\n", + i2c_clientname(&chip->c)); + return -1; + } + dprintk("%s: chip_read2: reg%d=0x%x\n", + i2c_clientname(&chip->c),subaddr,read[0]); + return read[0]; +} + +static int chip_cmd(struct CHIPSTATE *chip, char *name, audiocmd *cmd) +{ + int i; + + if (0 == cmd->count) + return 0; + + /* update our shadow register set; print bytes if (debug > 0) */ + dprintk("%s: chip_cmd(%s): reg=%d, data:", + i2c_clientname(&chip->c),name,cmd->bytes[0]); + for (i = 1; i < cmd->count; i++) { + dprintk(" 0x%x",cmd->bytes[i]); + chip->shadow.bytes[i+cmd->bytes[0]] = cmd->bytes[i]; + } + dprintk("\n"); + + /* send data to the chip */ + if (cmd->count != i2c_master_send(&chip->c,cmd->bytes,cmd->count)) { + printk(KERN_WARNING "%s: I/O error (%s)\n", i2c_clientname(&chip->c), name); + return -1; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* kernel thread for doing i2c stuff asyncronly + * right now it is used only to check the audio mode (mono/stereo/whatever) + * some time after switching to another TV channel, then turn on stereo + * if available, ... + */ + +static void chip_thread_wake(unsigned long data) +{ + struct CHIPSTATE *chip = (struct CHIPSTATE*)data; + wake_up_interruptible(&chip->wq); +} + +static int chip_thread(void *data) +{ + DECLARE_WAITQUEUE(wait, current); + struct CHIPSTATE *chip = data; + struct CHIPDESC *desc = chiplist + chip->type; + + daemonize("%s",i2c_clientname(&chip->c)); + allow_signal(SIGTERM); + dprintk("%s: thread started\n", i2c_clientname(&chip->c)); + + for (;;) { + add_wait_queue(&chip->wq, &wait); + if (!chip->done) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + remove_wait_queue(&chip->wq, &wait); + try_to_freeze(PF_FREEZE); + if (chip->done || signal_pending(current)) + break; + dprintk("%s: thread wakeup\n", i2c_clientname(&chip->c)); + + /* don't do anything for radio or if mode != auto */ + if (chip->norm == VIDEO_MODE_RADIO || chip->mode != 0) + continue; + + /* have a look what's going on */ + desc->checkmode(chip); + + /* schedule next check */ + mod_timer(&chip->wt, jiffies+2*HZ); + } + + dprintk("%s: thread exiting\n", i2c_clientname(&chip->c)); + complete_and_exit(&chip->texit, 0); + return 0; +} + +static void generic_checkmode(struct CHIPSTATE *chip) +{ + struct CHIPDESC *desc = chiplist + chip->type; + int mode = desc->getmode(chip); + + if (mode == chip->prevmode) + return; + + dprintk("%s: thread checkmode\n", i2c_clientname(&chip->c)); + chip->prevmode = mode; + + if (mode & VIDEO_SOUND_STEREO) + desc->setmode(chip,VIDEO_SOUND_STEREO); + else if (mode & VIDEO_SOUND_LANG1) + desc->setmode(chip,VIDEO_SOUND_LANG1); + else if (mode & VIDEO_SOUND_LANG2) + desc->setmode(chip,VIDEO_SOUND_LANG2); + else + desc->setmode(chip,VIDEO_SOUND_MONO); +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9840 */ + +#define TDA9840_SW 0x00 +#define TDA9840_LVADJ 0x02 +#define TDA9840_STADJ 0x03 +#define TDA9840_TEST 0x04 + +#define TDA9840_MONO 0x10 +#define TDA9840_STEREO 0x2a +#define TDA9840_DUALA 0x12 +#define TDA9840_DUALB 0x1e +#define TDA9840_DUALAB 0x1a +#define TDA9840_DUALBA 0x16 +#define TDA9840_EXTERNAL 0x7a + +#define TDA9840_DS_DUAL 0x20 /* Dual sound identified */ +#define TDA9840_ST_STEREO 0x40 /* Stereo sound identified */ +#define TDA9840_PONRES 0x80 /* Power-on reset detected if = 1 */ + +#define TDA9840_TEST_INT1SN 0x1 /* Integration time 0.5s when set */ +#define TDA9840_TEST_INTFU 0x02 /* Disables integrator function */ + +static int tda9840_getmode(struct CHIPSTATE *chip) +{ + int val, mode; + + val = chip_read(chip); + mode = VIDEO_SOUND_MONO; + if (val & TDA9840_DS_DUAL) + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + if (val & TDA9840_ST_STEREO) + mode |= VIDEO_SOUND_STEREO; + + dprintk ("tda9840_getmode(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9840_setmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int t = chip->shadow.bytes[TDA9840_SW + 1] & ~0x7e; + + switch (mode) { + case VIDEO_SOUND_MONO: + t |= TDA9840_MONO; + break; + case VIDEO_SOUND_STEREO: + t |= TDA9840_STEREO; + break; + case VIDEO_SOUND_LANG1: + t |= TDA9840_DUALA; + break; + case VIDEO_SOUND_LANG2: + t |= TDA9840_DUALB; + break; + default: + update = 0; + } + + if (update) + chip_write(chip, TDA9840_SW, t); +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda985x */ + +/* subaddresses for TDA9855 */ +#define TDA9855_VR 0x00 /* Volume, right */ +#define TDA9855_VL 0x01 /* Volume, left */ +#define TDA9855_BA 0x02 /* Bass */ +#define TDA9855_TR 0x03 /* Treble */ +#define TDA9855_SW 0x04 /* Subwoofer - not connected on DTV2000 */ + +/* subaddresses for TDA9850 */ +#define TDA9850_C4 0x04 /* Control 1 for TDA9850 */ + +/* subaddesses for both chips */ +#define TDA985x_C5 0x05 /* Control 2 for TDA9850, Control 1 for TDA9855 */ +#define TDA985x_C6 0x06 /* Control 3 for TDA9850, Control 2 for TDA9855 */ +#define TDA985x_C7 0x07 /* Control 4 for TDA9850, Control 3 for TDA9855 */ +#define TDA985x_A1 0x08 /* Alignment 1 for both chips */ +#define TDA985x_A2 0x09 /* Alignment 2 for both chips */ +#define TDA985x_A3 0x0a /* Alignment 3 for both chips */ + +/* Masks for bits in TDA9855 subaddresses */ +/* 0x00 - VR in TDA9855 */ +/* 0x01 - VL in TDA9855 */ +/* lower 7 bits control gain from -71dB (0x28) to 16dB (0x7f) + * in 1dB steps - mute is 0x27 */ + + +/* 0x02 - BA in TDA9855 */ +/* lower 5 bits control bass gain from -12dB (0x06) to 16.5dB (0x19) + * in .5dB steps - 0 is 0x0E */ + + +/* 0x03 - TR in TDA9855 */ +/* 4 bits << 1 control treble gain from -12dB (0x3) to 12dB (0xb) + * in 3dB steps - 0 is 0x7 */ + +/* Masks for bits in both chips' subaddresses */ +/* 0x04 - SW in TDA9855, C4/Control 1 in TDA9850 */ +/* Unique to TDA9855: */ +/* 4 bits << 2 control subwoofer/surround gain from -14db (0x1) to 14db (0xf) + * in 3dB steps - mute is 0x0 */ + +/* Unique to TDA9850: */ +/* lower 4 bits control stereo noise threshold, over which stereo turns off + * set to values of 0x00 through 0x0f for Ster1 through Ster16 */ + + +/* 0x05 - C5 - Control 1 in TDA9855 , Control 2 in TDA9850*/ +/* Unique to TDA9855: */ +#define TDA9855_MUTE 1<<7 /* GMU, Mute at outputs */ +#define TDA9855_AVL 1<<6 /* AVL, Automatic Volume Level */ +#define TDA9855_LOUD 1<<5 /* Loudness, 1==off */ +#define TDA9855_SUR 1<<3 /* Surround / Subwoofer 1==.5(L-R) 0==.5(L+R) */ + /* Bits 0 to 3 select various combinations + * of line in and line out, only the + * interesting ones are defined */ +#define TDA9855_EXT 1<<2 /* Selects inputs LIR and LIL. Pins 41 & 12 */ +#define TDA9855_INT 0 /* Selects inputs LOR and LOL. (internal) */ + +/* Unique to TDA9850: */ +/* lower 4 bits contol SAP noise threshold, over which SAP turns off + * set to values of 0x00 through 0x0f for SAP1 through SAP16 */ + + +/* 0x06 - C6 - Control 2 in TDA9855, Control 3 in TDA9850 */ +/* Common to TDA9855 and TDA9850: */ +#define TDA985x_SAP 3<<6 /* Selects SAP output, mute if not received */ +#define TDA985x_STEREO 1<<6 /* Selects Stereo ouput, mono if not received */ +#define TDA985x_MONO 0 /* Forces Mono output */ +#define TDA985x_LMU 1<<3 /* Mute (LOR/LOL for 9855, OUTL/OUTR for 9850) */ + +/* Unique to TDA9855: */ +#define TDA9855_TZCM 1<<5 /* If set, don't mute till zero crossing */ +#define TDA9855_VZCM 1<<4 /* If set, don't change volume till zero crossing*/ +#define TDA9855_LINEAR 0 /* Linear Stereo */ +#define TDA9855_PSEUDO 1 /* Pseudo Stereo */ +#define TDA9855_SPAT_30 2 /* Spatial Stereo, 30% anti-phase crosstalk */ +#define TDA9855_SPAT_50 3 /* Spatial Stereo, 52% anti-phase crosstalk */ +#define TDA9855_E_MONO 7 /* Forced mono - mono select elseware, so useless*/ + +/* 0x07 - C7 - Control 3 in TDA9855, Control 4 in TDA9850 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 4 bits control input gain from -3.5dB (0x0) to 4dB (0xF) + * in .5dB steps - 0dB is 0x7 */ + +/* 0x08, 0x09 - A1 and A2 (read/write) */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 5 bites are wideband and spectral expander alignment + * from 0x00 to 0x1f - nominal at 0x0f and 0x10 (read/write) */ +#define TDA985x_STP 1<<5 /* Stereo Pilot/detect (read-only) */ +#define TDA985x_SAPP 1<<6 /* SAP Pilot/detect (read-only) */ +#define TDA985x_STS 1<<7 /* Stereo trigger 1= <35mV 0= <30mV (write-only)*/ + +/* 0x0a - A3 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 3 bits control timing current for alignment: -30% (0x0), -20% (0x1), + * -10% (0x2), nominal (0x3), +10% (0x6), +20% (0x5), +30% (0x4) */ +#define TDA985x_ADJ 1<<7 /* Stereo adjust on/off (wideband and spectral */ + +static int tda9855_volume(int val) { return val/0x2e8+0x27; } +static int tda9855_bass(int val) { return val/0xccc+0x06; } +static int tda9855_treble(int val) { return (val/0x1c71+0x3)<<1; } + +static int tda985x_getmode(struct CHIPSTATE *chip) +{ + int mode; + + mode = ((TDA985x_STP | TDA985x_SAPP) & + chip_read(chip)) >> 4; + /* Add mono mode regardless of SAP and stereo */ + /* Allows forced mono */ + return mode | VIDEO_SOUND_MONO; +} + +static void tda985x_setmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + int c6 = chip->shadow.bytes[TDA985x_C6+1] & 0x3f; + + switch (mode) { + case VIDEO_SOUND_MONO: + c6 |= TDA985x_MONO; + break; + case VIDEO_SOUND_STEREO: + c6 |= TDA985x_STEREO; + break; + case VIDEO_SOUND_LANG1: + c6 |= TDA985x_SAP; + break; + default: + update = 0; + } + if (update) + chip_write(chip,TDA985x_C6,c6); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda9873h */ + +/* Subaddresses for TDA9873H */ + +#define TDA9873_SW 0x00 /* Switching */ +#define TDA9873_AD 0x01 /* Adjust */ +#define TDA9873_PT 0x02 /* Port */ + +/* Subaddress 0x00: Switching Data + * B7..B0: + * + * B1, B0: Input source selection + * 0, 0 internal + * 1, 0 external stereo + * 0, 1 external mono + */ +#define TDA9873_INP_MASK 3 +#define TDA9873_INTERNAL 0 +#define TDA9873_EXT_STEREO 2 +#define TDA9873_EXT_MONO 1 + +/* B3, B2: output signal select + * B4 : transmission mode + * 0, 0, 1 Mono + * 1, 0, 0 Stereo + * 1, 1, 1 Stereo (reversed channel) + * 0, 0, 0 Dual AB + * 0, 0, 1 Dual AA + * 0, 1, 0 Dual BB + * 0, 1, 1 Dual BA + */ + +#define TDA9873_TR_MASK (7 << 2) +#define TDA9873_TR_MONO 4 +#define TDA9873_TR_STEREO 1 << 4 +#define TDA9873_TR_REVERSE (1 << 3) & (1 << 2) +#define TDA9873_TR_DUALA 1 << 2 +#define TDA9873_TR_DUALB 1 << 3 + +/* output level controls + * B5: output level switch (0 = reduced gain, 1 = normal gain) + * B6: mute (1 = muted) + * B7: auto-mute (1 = auto-mute enabled) + */ + +#define TDA9873_GAIN_NORMAL 1 << 5 +#define TDA9873_MUTE 1 << 6 +#define TDA9873_AUTOMUTE 1 << 7 + +/* Subaddress 0x01: Adjust/standard */ + +/* Lower 4 bits (C3..C0) control stereo adjustment on R channel (-0.6 - +0.7 dB) + * Recommended value is +0 dB + */ + +#define TDA9873_STEREO_ADJ 0x06 /* 0dB gain */ + +/* Bits C6..C4 control FM stantard + * C6, C5, C4 + * 0, 0, 0 B/G (PAL FM) + * 0, 0, 1 M + * 0, 1, 0 D/K(1) + * 0, 1, 1 D/K(2) + * 1, 0, 0 D/K(3) + * 1, 0, 1 I + */ +#define TDA9873_BG 0 +#define TDA9873_M 1 +#define TDA9873_DK1 2 +#define TDA9873_DK2 3 +#define TDA9873_DK3 4 +#define TDA9873_I 5 + +/* C7 controls identification response time (1=fast/0=normal) + */ +#define TDA9873_IDR_NORM 0 +#define TDA9873_IDR_FAST 1 << 7 + + +/* Subaddress 0x02: Port data */ + +/* E1, E0 free programmable ports P1/P2 + 0, 0 both ports low + 0, 1 P1 high + 1, 0 P2 high + 1, 1 both ports high +*/ + +#define TDA9873_PORTS 3 + +/* E2: test port */ +#define TDA9873_TST_PORT 1 << 2 + +/* E5..E3 control mono output channel (together with transmission mode bit B4) + * + * E5 E4 E3 B4 OUTM + * 0 0 0 0 mono + * 0 0 1 0 DUAL B + * 0 1 0 1 mono (from stereo decoder) + */ +#define TDA9873_MOUT_MONO 0 +#define TDA9873_MOUT_FMONO 0 +#define TDA9873_MOUT_DUALA 0 +#define TDA9873_MOUT_DUALB 1 << 3 +#define TDA9873_MOUT_ST 1 << 4 +#define TDA9873_MOUT_EXTM (1 << 4 ) & (1 << 3) +#define TDA9873_MOUT_EXTL 1 << 5 +#define TDA9873_MOUT_EXTR (1 << 5 ) & (1 << 3) +#define TDA9873_MOUT_EXTLR (1 << 5 ) & (1 << 4) +#define TDA9873_MOUT_MUTE (1 << 5 ) & (1 << 4) & (1 << 3) + +/* Status bits: (chip read) */ +#define TDA9873_PONR 0 /* Power-on reset detected if = 1 */ +#define TDA9873_STEREO 2 /* Stereo sound is identified */ +#define TDA9873_DUAL 4 /* Dual sound is identified */ + +static int tda9873_getmode(struct CHIPSTATE *chip) +{ + int val,mode; + + val = chip_read(chip); + mode = VIDEO_SOUND_MONO; + if (val & TDA9873_STEREO) + mode |= VIDEO_SOUND_STEREO; + if (val & TDA9873_DUAL) + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + dprintk ("tda9873_getmode(): raw chip read: %d, return: %d\n", + val, mode); + return mode; +} + +static void tda9873_setmode(struct CHIPSTATE *chip, int mode) +{ + int sw_data = chip->shadow.bytes[TDA9873_SW+1] & ~ TDA9873_TR_MASK; + /* int adj_data = chip->shadow.bytes[TDA9873_AD+1] ; */ + + if ((sw_data & TDA9873_INP_MASK) != TDA9873_INTERNAL) { + dprintk("tda9873_setmode(): external input\n"); + return; + } + + dprintk("tda9873_setmode(): chip->shadow.bytes[%d] = %d\n", TDA9873_SW+1, chip->shadow.bytes[TDA9873_SW+1]); + dprintk("tda9873_setmode(): sw_data = %d\n", sw_data); + + switch (mode) { + case VIDEO_SOUND_MONO: + sw_data |= TDA9873_TR_MONO; + break; + case VIDEO_SOUND_STEREO: + sw_data |= TDA9873_TR_STEREO; + break; + case VIDEO_SOUND_LANG1: + sw_data |= TDA9873_TR_DUALA; + break; + case VIDEO_SOUND_LANG2: + sw_data |= TDA9873_TR_DUALB; + break; + default: + chip->mode = 0; + return; + } + + chip_write(chip, TDA9873_SW, sw_data); + dprintk("tda9873_setmode(): req. mode %d; chip_write: %d\n", + mode, sw_data); +} + +static int tda9873_checkit(struct CHIPSTATE *chip) +{ + int rc; + + if (-1 == (rc = chip_read2(chip,254))) + return 0; + return (rc & ~0x1f) == 0x80; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip description - defines+functions for tda9874h and tda9874a */ +/* Dariusz Kowalewski <darekk@automex.pl> */ + +/* Subaddresses for TDA9874H and TDA9874A (slave rx) */ +#define TDA9874A_AGCGR 0x00 /* AGC gain */ +#define TDA9874A_GCONR 0x01 /* general config */ +#define TDA9874A_MSR 0x02 /* monitor select */ +#define TDA9874A_C1FRA 0x03 /* carrier 1 freq. */ +#define TDA9874A_C1FRB 0x04 /* carrier 1 freq. */ +#define TDA9874A_C1FRC 0x05 /* carrier 1 freq. */ +#define TDA9874A_C2FRA 0x06 /* carrier 2 freq. */ +#define TDA9874A_C2FRB 0x07 /* carrier 2 freq. */ +#define TDA9874A_C2FRC 0x08 /* carrier 2 freq. */ +#define TDA9874A_DCR 0x09 /* demodulator config */ +#define TDA9874A_FMER 0x0a /* FM de-emphasis */ +#define TDA9874A_FMMR 0x0b /* FM dematrix */ +#define TDA9874A_C1OLAR 0x0c /* ch.1 output level adj. */ +#define TDA9874A_C2OLAR 0x0d /* ch.2 output level adj. */ +#define TDA9874A_NCONR 0x0e /* NICAM config */ +#define TDA9874A_NOLAR 0x0f /* NICAM output level adj. */ +#define TDA9874A_NLELR 0x10 /* NICAM lower error limit */ +#define TDA9874A_NUELR 0x11 /* NICAM upper error limit */ +#define TDA9874A_AMCONR 0x12 /* audio mute control */ +#define TDA9874A_SDACOSR 0x13 /* stereo DAC output select */ +#define TDA9874A_AOSR 0x14 /* analog output select */ +#define TDA9874A_DAICONR 0x15 /* digital audio interface config */ +#define TDA9874A_I2SOSR 0x16 /* I2S-bus output select */ +#define TDA9874A_I2SOLAR 0x17 /* I2S-bus output level adj. */ +#define TDA9874A_MDACOSR 0x18 /* mono DAC output select (tda9874a) */ +#define TDA9874A_ESP 0xFF /* easy standard progr. (tda9874a) */ + +/* Subaddresses for TDA9874H and TDA9874A (slave tx) */ +#define TDA9874A_DSR 0x00 /* device status */ +#define TDA9874A_NSR 0x01 /* NICAM status */ +#define TDA9874A_NECR 0x02 /* NICAM error count */ +#define TDA9874A_DR1 0x03 /* add. data LSB */ +#define TDA9874A_DR2 0x04 /* add. data MSB */ +#define TDA9874A_LLRA 0x05 /* monitor level read-out LSB */ +#define TDA9874A_LLRB 0x06 /* monitor level read-out MSB */ +#define TDA9874A_SIFLR 0x07 /* SIF level */ +#define TDA9874A_TR2 252 /* test reg. 2 */ +#define TDA9874A_TR1 253 /* test reg. 1 */ +#define TDA9874A_DIC 254 /* device id. code */ +#define TDA9874A_SIC 255 /* software id. code */ + + +static int tda9874a_mode = 1; /* 0: A2, 1: NICAM */ +static int tda9874a_GCONR = 0xc0; /* default config. input pin: SIFSEL=0 */ +static int tda9874a_NCONR = 0x01; /* default NICAM config.: AMSEL=0,AMUTE=1 */ +static int tda9874a_ESP = 0x07; /* default standard: NICAM D/K */ +static int tda9874a_dic = -1; /* device id. code */ + +/* insmod options for tda9874a */ +static unsigned int tda9874a_SIF = UNSET; +static unsigned int tda9874a_AMSEL = UNSET; +static unsigned int tda9874a_STD = UNSET; +module_param(tda9874a_SIF, int, 0444); +module_param(tda9874a_AMSEL, int, 0444); +module_param(tda9874a_STD, int, 0444); + +/* + * initialization table for tda9874 decoder: + * - carrier 1 freq. registers (3 bytes) + * - carrier 2 freq. registers (3 bytes) + * - demudulator config register + * - FM de-emphasis register (slow identification mode) + * Note: frequency registers must be written in single i2c transfer. + */ +static struct tda9874a_MODES { + char *name; + audiocmd cmd; +} tda9874a_modelist[9] = { + { "A2, B/G", + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "A2, M (Korea)", + { 9, { TDA9874A_C1FRA, 0x5D,0xC0,0x00, 0x62,0x6A,0xAA, 0x20,0x22 }} }, + { "A2, D/K (1)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x82,0x60,0x00, 0x00,0x00 }} }, + { "A2, D/K (2)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x8C,0x75,0x55, 0x00,0x00 }} }, + { "A2, D/K (3)", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x77,0xA0,0x00, 0x00,0x00 }} }, + { "NICAM, I", + { 9, { TDA9874A_C1FRA, 0x7D,0x00,0x00, 0x88,0x8A,0xAA, 0x08,0x33 }} }, + { "NICAM, B/G", + { 9, { TDA9874A_C1FRA, 0x72,0x95,0x55, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, D/K", /* default */ + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x08,0x33 }} }, + { "NICAM, L", + { 9, { TDA9874A_C1FRA, 0x87,0x6A,0xAA, 0x79,0xEA,0xAA, 0x09,0x33 }} } +}; + +static int tda9874a_setup(struct CHIPSTATE *chip) +{ + chip_write(chip, TDA9874A_AGCGR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_GCONR, tda9874a_GCONR); + chip_write(chip, TDA9874A_MSR, (tda9874a_mode) ? 0x03:0x02); + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_FMMR, 0x80); + } else { /* dic == 0x07 */ + chip_cmd(chip,"tda9874_modelist",&tda9874a_modelist[tda9874a_STD].cmd); + chip_write(chip, TDA9874A_FMMR, 0x00); + } + chip_write(chip, TDA9874A_C1OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_C2OLAR, 0x00); /* 0 dB */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + chip_write(chip, TDA9874A_NOLAR, 0x00); /* 0 dB */ + /* Note: If signal quality is poor you may want to change NICAM */ + /* error limit registers (NLELR and NUELR) to some greater values. */ + /* Then the sound would remain stereo, but won't be so clear. */ + chip_write(chip, TDA9874A_NLELR, 0x14); /* default */ + chip_write(chip, TDA9874A_NUELR, 0x50); /* default */ + + if(tda9874a_dic == 0x11) { + chip_write(chip, TDA9874A_AMCONR, 0xf9); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x80); + chip_write(chip, TDA9874A_MDACOSR, (tda9874a_mode) ? 0x82:0x80); + chip_write(chip, TDA9874A_ESP, tda9874a_ESP); + } else { /* dic == 0x07 */ + chip_write(chip, TDA9874A_AMCONR, 0xfb); + chip_write(chip, TDA9874A_SDACOSR, (tda9874a_mode) ? 0x81:0x80); + chip_write(chip, TDA9874A_AOSR, 0x00); // or 0x10 + } + dprintk("tda9874a_setup(): %s [0x%02X].\n", + tda9874a_modelist[tda9874a_STD].name,tda9874a_STD); + return 1; +} + +static int tda9874a_getmode(struct CHIPSTATE *chip) +{ + int dsr,nsr,mode; + int necr; /* just for debugging */ + + mode = VIDEO_SOUND_MONO; + + if(-1 == (dsr = chip_read2(chip,TDA9874A_DSR))) + return mode; + if(-1 == (nsr = chip_read2(chip,TDA9874A_NSR))) + return mode; + if(-1 == (necr = chip_read2(chip,TDA9874A_NECR))) + return mode; + + /* need to store dsr/nsr somewhere */ + chip->shadow.bytes[MAXREGS-2] = dsr; + chip->shadow.bytes[MAXREGS-1] = nsr; + + if(tda9874a_mode) { + /* Note: DSR.RSSF and DSR.AMSTAT bits are also checked. + * If NICAM auto-muting is enabled, DSR.AMSTAT=1 indicates + * that sound has (temporarily) switched from NICAM to + * mono FM (or AM) on 1st sound carrier due to high NICAM bit + * error count. So in fact there is no stereo in this case :-( + * But changing the mode to VIDEO_SOUND_MONO would switch + * external 4052 multiplexer in audio_hook(). + */ +#if 0 + if((nsr & 0x02) && !(dsr & 0x10)) /* NSR.S/MB=1 and DSR.AMSTAT=0 */ + mode |= VIDEO_SOUND_STEREO; +#else + if(nsr & 0x02) /* NSR.S/MB=1 */ + mode |= VIDEO_SOUND_STEREO; +#endif + if(nsr & 0x01) /* NSR.D/SB=1 */ + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } else { + if(dsr & 0x02) /* DSR.IDSTE=1 */ + mode |= VIDEO_SOUND_STEREO; + if(dsr & 0x04) /* DSR.IDDUA=1 */ + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } + + dprintk("tda9874a_getmode(): DSR=0x%X, NSR=0x%X, NECR=0x%X, return: %d.\n", + dsr, nsr, necr, mode); + return mode; +} + +static void tda9874a_setmode(struct CHIPSTATE *chip, int mode) +{ + /* Disable/enable NICAM auto-muting (based on DSR.RSSF status bit). */ + /* If auto-muting is disabled, we can hear a signal of degrading quality. */ + if(tda9874a_mode) { + if(chip->shadow.bytes[MAXREGS-2] & 0x20) /* DSR.RSSF=1 */ + tda9874a_NCONR &= 0xfe; /* enable */ + else + tda9874a_NCONR |= 0x01; /* disable */ + chip_write(chip, TDA9874A_NCONR, tda9874a_NCONR); + } + + /* Note: TDA9874A supports automatic FM dematrixing (FMMR register) + * and has auto-select function for audio output (AOSR register). + * Old TDA9874H doesn't support these features. + * TDA9874A also has additional mono output pin (OUTM), which + * on same (all?) tv-cards is not used, anyway (as well as MONOIN). + */ + if(tda9874a_dic == 0x11) { + int aosr = 0x80; + int mdacosr = (tda9874a_mode) ? 0x82:0x80; + + switch(mode) { + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_STEREO: + break; + case VIDEO_SOUND_LANG1: + aosr = 0x80; /* auto-select, dual A/A */ + mdacosr = (tda9874a_mode) ? 0x82:0x80; + break; + case VIDEO_SOUND_LANG2: + aosr = 0xa0; /* auto-select, dual B/B */ + mdacosr = (tda9874a_mode) ? 0x83:0x81; + break; + default: + chip->mode = 0; + return; + } + chip_write(chip, TDA9874A_AOSR, aosr); + chip_write(chip, TDA9874A_MDACOSR, mdacosr); + + dprintk("tda9874a_setmode(): req. mode %d; AOSR=0x%X, MDACOSR=0x%X.\n", + mode, aosr, mdacosr); + + } else { /* dic == 0x07 */ + int fmmr,aosr; + + switch(mode) { + case VIDEO_SOUND_MONO: + fmmr = 0x00; /* mono */ + aosr = 0x10; /* A/A */ + break; + case VIDEO_SOUND_STEREO: + if(tda9874a_mode) { + fmmr = 0x00; + aosr = 0x00; /* handled by NICAM auto-mute */ + } else { + fmmr = (tda9874a_ESP == 1) ? 0x05 : 0x04; /* stereo */ + aosr = 0x00; + } + break; + case VIDEO_SOUND_LANG1: + fmmr = 0x02; /* dual */ + aosr = 0x10; /* dual A/A */ + break; + case VIDEO_SOUND_LANG2: + fmmr = 0x02; /* dual */ + aosr = 0x20; /* dual B/B */ + break; + default: + chip->mode = 0; + return; + } + chip_write(chip, TDA9874A_FMMR, fmmr); + chip_write(chip, TDA9874A_AOSR, aosr); + + dprintk("tda9874a_setmode(): req. mode %d; FMMR=0x%X, AOSR=0x%X.\n", + mode, fmmr, aosr); + } +} + +static int tda9874a_checkit(struct CHIPSTATE *chip) +{ + int dic,sic; /* device id. and software id. codes */ + + if(-1 == (dic = chip_read2(chip,TDA9874A_DIC))) + return 0; + if(-1 == (sic = chip_read2(chip,TDA9874A_SIC))) + return 0; + + dprintk("tda9874a_checkit(): DIC=0x%X, SIC=0x%X.\n", dic, sic); + + if((dic == 0x11)||(dic == 0x07)) { + printk("tvaudio: found tda9874%s.\n", (dic == 0x11) ? "a":"h"); + tda9874a_dic = dic; /* remember device id. */ + return 1; + } + return 0; /* not found */ +} + +static int tda9874a_initialize(struct CHIPSTATE *chip) +{ + if (tda9874a_SIF > 2) + tda9874a_SIF = 1; + if (tda9874a_STD >= 8) + tda9874a_STD = 0; + if(tda9874a_AMSEL > 1) + tda9874a_AMSEL = 0; + + if(tda9874a_SIF == 1) + tda9874a_GCONR = 0xc0; /* sound IF input 1 */ + else + tda9874a_GCONR = 0xc1; /* sound IF input 2 */ + + tda9874a_ESP = tda9874a_STD; + tda9874a_mode = (tda9874a_STD < 5) ? 0 : 1; + + if(tda9874a_AMSEL == 0) + tda9874a_NCONR = 0x01; /* auto-mute: analog mono input */ + else + tda9874a_NCONR = 0x05; /* auto-mute: 1st carrier FM or AM */ + + tda9874a_setup(chip); + return 0; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tea6420 */ + +#define TEA6300_VL 0x00 /* volume left */ +#define TEA6300_VR 0x01 /* volume right */ +#define TEA6300_BA 0x02 /* bass */ +#define TEA6300_TR 0x03 /* treble */ +#define TEA6300_FA 0x04 /* fader control */ +#define TEA6300_S 0x05 /* switch register */ + /* values for those registers: */ +#define TEA6300_S_SA 0x01 /* stereo A input */ +#define TEA6300_S_SB 0x02 /* stereo B */ +#define TEA6300_S_SC 0x04 /* stereo C */ +#define TEA6300_S_GMU 0x80 /* general mute */ + +#define TEA6320_V 0x00 /* volume (0-5)/loudness off (6)/zero crossing mute(7) */ +#define TEA6320_FFR 0x01 /* fader front right (0-5) */ +#define TEA6320_FFL 0x02 /* fader front left (0-5) */ +#define TEA6320_FRR 0x03 /* fader rear right (0-5) */ +#define TEA6320_FRL 0x04 /* fader rear left (0-5) */ +#define TEA6320_BA 0x05 /* bass (0-4) */ +#define TEA6320_TR 0x06 /* treble (0-4) */ +#define TEA6320_S 0x07 /* switch register */ + /* values for those registers: */ +#define TEA6320_S_SA 0x07 /* stereo A input */ +#define TEA6320_S_SB 0x06 /* stereo B */ +#define TEA6320_S_SC 0x05 /* stereo C */ +#define TEA6320_S_SD 0x04 /* stereo D */ +#define TEA6320_S_GMU 0x80 /* general mute */ + +#define TEA6420_S_SA 0x00 /* stereo A input */ +#define TEA6420_S_SB 0x01 /* stereo B */ +#define TEA6420_S_SC 0x02 /* stereo C */ +#define TEA6420_S_SD 0x03 /* stereo D */ +#define TEA6420_S_SE 0x04 /* stereo E */ +#define TEA6420_S_GMU 0x05 /* general mute */ + +static int tea6300_shift10(int val) { return val >> 10; } +static int tea6300_shift12(int val) { return val >> 12; } + +/* Assumes 16bit input (values 0x3f to 0x0c are unique, values less than */ +/* 0x0c mirror those immediately higher) */ +static int tea6320_volume(int val) { return (val / (65535/(63-12)) + 12) & 0x3f; } +static int tea6320_shift11(int val) { return val >> 11; } +static int tea6320_initialize(struct CHIPSTATE * chip) +{ + chip_write(chip, TEA6320_FFR, 0x3f); + chip_write(chip, TEA6320_FFL, 0x3f); + chip_write(chip, TEA6320_FRR, 0x3f); + chip_write(chip, TEA6320_FRL, 0x3f); + + return 0; +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for tda8425 */ + +#define TDA8425_VL 0x00 /* volume left */ +#define TDA8425_VR 0x01 /* volume right */ +#define TDA8425_BA 0x02 /* bass */ +#define TDA8425_TR 0x03 /* treble */ +#define TDA8425_S1 0x08 /* switch functions */ + /* values for those registers: */ +#define TDA8425_S1_OFF 0xEE /* audio off (mute on) */ +#define TDA8425_S1_CH1 0xCE /* audio channel 1 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_CH2 0xCF /* audio channel 2 (mute off) - "linear stereo" mode */ +#define TDA8425_S1_MU 0x20 /* mute bit */ +#define TDA8425_S1_STEREO 0x18 /* stereo bits */ +#define TDA8425_S1_STEREO_SPATIAL 0x18 /* spatial stereo */ +#define TDA8425_S1_STEREO_LINEAR 0x08 /* linear stereo */ +#define TDA8425_S1_STEREO_PSEUDO 0x10 /* pseudo stereo */ +#define TDA8425_S1_STEREO_MONO 0x00 /* forced mono */ +#define TDA8425_S1_ML 0x06 /* language selector */ +#define TDA8425_S1_ML_SOUND_A 0x02 /* sound a */ +#define TDA8425_S1_ML_SOUND_B 0x04 /* sound b */ +#define TDA8425_S1_ML_STEREO 0x06 /* stereo */ +#define TDA8425_S1_IS 0x01 /* channel selector */ + + +static int tda8425_shift10(int val) { return (val >> 10) | 0xc0; } +static int tda8425_shift12(int val) { return (val >> 12) | 0xf0; } + +static int tda8425_initialize(struct CHIPSTATE *chip) +{ + struct CHIPDESC *desc = chiplist + chip->type; + int inputmap[8] = { /* tuner */ TDA8425_S1_CH2, /* radio */ TDA8425_S1_CH1, + /* extern */ TDA8425_S1_CH1, /* intern */ TDA8425_S1_OFF, + /* off */ TDA8425_S1_OFF, /* on */ TDA8425_S1_CH2}; + + if (chip->c.adapter->id == (I2C_ALGO_BIT | I2C_HW_B_RIVA)) { + memcpy (desc->inputmap, inputmap, sizeof (inputmap)); + } + return 0; +} + +static void tda8425_setmode(struct CHIPSTATE *chip, int mode) +{ + int s1 = chip->shadow.bytes[TDA8425_S1+1] & 0xe1; + + if (mode & VIDEO_SOUND_LANG1) { + s1 |= TDA8425_S1_ML_SOUND_A; + s1 |= TDA8425_S1_STEREO_PSEUDO; + + } else if (mode & VIDEO_SOUND_LANG2) { + s1 |= TDA8425_S1_ML_SOUND_B; + s1 |= TDA8425_S1_STEREO_PSEUDO; + + } else { + s1 |= TDA8425_S1_ML_STEREO; + + if (mode & VIDEO_SOUND_MONO) + s1 |= TDA8425_S1_STEREO_MONO; + if (mode & VIDEO_SOUND_STEREO) + s1 |= TDA8425_S1_STEREO_SPATIAL; + } + chip_write(chip,TDA8425_S1,s1); +} + + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for pic16c54 (PV951) */ + +/* the registers of 16C54, I2C sub address. */ +#define PIC16C54_REG_KEY_CODE 0x01 /* Not use. */ +#define PIC16C54_REG_MISC 0x02 + +/* bit definition of the RESET register, I2C data. */ +#define PIC16C54_MISC_RESET_REMOTE_CTL 0x01 /* bit 0, Reset to receive the key */ + /* code of remote controller */ +#define PIC16C54_MISC_MTS_MAIN 0x02 /* bit 1 */ +#define PIC16C54_MISC_MTS_SAP 0x04 /* bit 2 */ +#define PIC16C54_MISC_MTS_BOTH 0x08 /* bit 3 */ +#define PIC16C54_MISC_SND_MUTE 0x10 /* bit 4, Mute Audio(Line-in and Tuner) */ +#define PIC16C54_MISC_SND_NOTMUTE 0x20 /* bit 5 */ +#define PIC16C54_MISC_SWITCH_TUNER 0x40 /* bit 6 , Switch to Line-in */ +#define PIC16C54_MISC_SWITCH_LINE 0x80 /* bit 7 , Switch to Tuner */ + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - defines+functions for TA8874Z */ + +// write 1st byte +#define TA8874Z_LED_STE 0x80 +#define TA8874Z_LED_BIL 0x40 +#define TA8874Z_LED_EXT 0x20 +#define TA8874Z_MONO_SET 0x10 +#define TA8874Z_MUTE 0x08 +#define TA8874Z_F_MONO 0x04 +#define TA8874Z_MODE_SUB 0x02 +#define TA8874Z_MODE_MAIN 0x01 + +// write 2nd byte +//#define TA8874Z_TI 0x80 // test mode +#define TA8874Z_SEPARATION 0x3f +#define TA8874Z_SEPARATION_DEFAULT 0x10 + +// read +#define TA8874Z_B1 0x80 +#define TA8874Z_B0 0x40 +#define TA8874Z_CHAG_FLAG 0x20 + +// B1 B0 +// mono L H +// stereo L L +// BIL H L + +static int ta8874z_getmode(struct CHIPSTATE *chip) +{ + int val, mode; + + val = chip_read(chip); + mode = VIDEO_SOUND_MONO; + if (val & TA8874Z_B1){ + mode |= VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + }else if (!(val & TA8874Z_B0)){ + mode |= VIDEO_SOUND_STEREO; + } + //dprintk ("ta8874z_getmode(): raw chip read: 0x%02x, return: 0x%02x\n", val, mode); + return mode; +} + +static audiocmd ta8874z_stereo = { 2, {0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_mono = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_main = {2, { 0, TA8874Z_SEPARATION_DEFAULT}}; +static audiocmd ta8874z_sub = {2, { TA8874Z_MODE_SUB, TA8874Z_SEPARATION_DEFAULT}}; + +static void ta8874z_setmode(struct CHIPSTATE *chip, int mode) +{ + int update = 1; + audiocmd *t = NULL; + dprintk("ta8874z_setmode(): mode: 0x%02x\n", mode); + + switch(mode){ + case VIDEO_SOUND_MONO: + t = &ta8874z_mono; + break; + case VIDEO_SOUND_STEREO: + t = &ta8874z_stereo; + break; + case VIDEO_SOUND_LANG1: + t = &ta8874z_main; + break; + case VIDEO_SOUND_LANG2: + t = &ta8874z_sub; + break; + default: + update = 0; + } + + if(update) + chip_cmd(chip, "TA8874Z", t); +} + +static int ta8874z_checkit(struct CHIPSTATE *chip) +{ + int rc; + rc = chip_read(chip); + return ((rc & 0x1f) == 0x1f) ? 1 : 0; +} + +/* ---------------------------------------------------------------------- */ +/* audio chip descriptions - struct CHIPDESC */ + +/* insmod options to enable/disable individual audio chips */ +int tda8425 = 1; +int tda9840 = 1; +int tda9850 = 1; +int tda9855 = 1; +int tda9873 = 1; +int tda9874a = 1; +int tea6300 = 0; // address clash with msp34xx +int tea6320 = 0; // address clash with msp34xx +int tea6420 = 1; +int pic16c54 = 1; +int ta8874z = 0; // address clash with tda9840 + +module_param(tda8425, int, 0444); +module_param(tda9840, int, 0444); +module_param(tda9850, int, 0444); +module_param(tda9855, int, 0444); +module_param(tda9873, int, 0444); +module_param(tda9874a, int, 0444); +module_param(tea6300, int, 0444); +module_param(tea6320, int, 0444); +module_param(tea6420, int, 0444); +module_param(pic16c54, int, 0444); +module_param(ta8874z, int, 0444); + +static struct CHIPDESC chiplist[] = { + { + .name = "tda9840", + .id = I2C_DRIVERID_TDA9840, + .insmodopt = &tda9840, + .addr_lo = I2C_TDA9840 >> 1, + .addr_hi = I2C_TDA9840 >> 1, + .registers = 5, + + .getmode = tda9840_getmode, + .setmode = tda9840_setmode, + .checkmode = generic_checkmode, + + .init = { 2, { TDA9840_TEST, TDA9840_TEST_INT1SN + /* ,TDA9840_SW, TDA9840_MONO */} } + }, + { + .name = "tda9873h", + .id = I2C_DRIVERID_TDA9873, + .checkit = tda9873_checkit, + .insmodopt = &tda9873, + .addr_lo = I2C_TDA985x_L >> 1, + .addr_hi = I2C_TDA985x_H >> 1, + .registers = 3, + .flags = CHIP_HAS_INPUTSEL, + + .getmode = tda9873_getmode, + .setmode = tda9873_setmode, + .checkmode = generic_checkmode, + + .init = { 4, { TDA9873_SW, 0xa4, 0x06, 0x03 } }, + .inputreg = TDA9873_SW, + .inputmute = TDA9873_MUTE | TDA9873_AUTOMUTE, + .inputmap = {0xa0, 0xa2, 0xa0, 0xa0, 0xc0}, + .inputmask = TDA9873_INP_MASK|TDA9873_MUTE|TDA9873_AUTOMUTE, + + }, + { + .name = "tda9874h/a", + .id = I2C_DRIVERID_TDA9874, + .checkit = tda9874a_checkit, + .initialize = tda9874a_initialize, + .insmodopt = &tda9874a, + .addr_lo = I2C_TDA9874 >> 1, + .addr_hi = I2C_TDA9874 >> 1, + + .getmode = tda9874a_getmode, + .setmode = tda9874a_setmode, + .checkmode = generic_checkmode, + }, + { + .name = "tda9850", + .id = I2C_DRIVERID_TDA9850, + .insmodopt = &tda9850, + .addr_lo = I2C_TDA985x_L >> 1, + .addr_hi = I2C_TDA985x_H >> 1, + .registers = 11, + + .getmode = tda985x_getmode, + .setmode = tda985x_setmode, + + .init = { 8, { TDA9850_C4, 0x08, 0x08, TDA985x_STEREO, 0x07, 0x10, 0x10, 0x03 } } + }, + { + .name = "tda9855", + .id = I2C_DRIVERID_TDA9855, + .insmodopt = &tda9855, + .addr_lo = I2C_TDA985x_L >> 1, + .addr_hi = I2C_TDA985x_H >> 1, + .registers = 11, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE, + + .leftreg = TDA9855_VL, + .rightreg = TDA9855_VR, + .bassreg = TDA9855_BA, + .treblereg = TDA9855_TR, + .volfunc = tda9855_volume, + .bassfunc = tda9855_bass, + .treblefunc = tda9855_treble, + + .getmode = tda985x_getmode, + .setmode = tda985x_setmode, + + .init = { 12, { 0, 0x6f, 0x6f, 0x0e, 0x07<<1, 0x8<<2, + TDA9855_MUTE | TDA9855_AVL | TDA9855_LOUD | TDA9855_INT, + TDA985x_STEREO | TDA9855_LINEAR | TDA9855_TZCM | TDA9855_VZCM, + 0x07, 0x10, 0x10, 0x03 }} + }, + { + .name = "tea6300", + .id = I2C_DRIVERID_TEA6300, + .insmodopt = &tea6300, + .addr_lo = I2C_TEA6300 >> 1, + .addr_hi = I2C_TEA6300 >> 1, + .registers = 6, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6300_VR, + .rightreg = TEA6300_VL, + .bassreg = TEA6300_BA, + .treblereg = TEA6300_TR, + .volfunc = tea6300_shift10, + .bassfunc = tea6300_shift12, + .treblefunc = tea6300_shift12, + + .inputreg = TEA6300_S, + .inputmap = { TEA6300_S_SA, TEA6300_S_SB, TEA6300_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6320", + .id = I2C_DRIVERID_TEA6300, + .initialize = tea6320_initialize, + .insmodopt = &tea6320, + .addr_lo = I2C_TEA6300 >> 1, + .addr_hi = I2C_TEA6300 >> 1, + .registers = 8, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TEA6320_V, + .rightreg = TEA6320_V, + .bassreg = TEA6320_BA, + .treblereg = TEA6320_TR, + .volfunc = tea6320_volume, + .bassfunc = tea6320_shift11, + .treblefunc = tea6320_shift11, + + .inputreg = TEA6320_S, + .inputmap = { TEA6320_S_SA, TEA6420_S_SB, TEA6300_S_SC, TEA6320_S_SD }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tea6420", + .id = I2C_DRIVERID_TEA6420, + .insmodopt = &tea6420, + .addr_lo = I2C_TEA6420 >> 1, + .addr_hi = I2C_TEA6420 >> 1, + .registers = 1, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = -1, + .inputmap = { TEA6420_S_SA, TEA6420_S_SB, TEA6420_S_SC }, + .inputmute = TEA6300_S_GMU, + }, + { + .name = "tda8425", + .id = I2C_DRIVERID_TDA8425, + .insmodopt = &tda8425, + .addr_lo = I2C_TDA8425 >> 1, + .addr_hi = I2C_TDA8425 >> 1, + .registers = 9, + .flags = CHIP_HAS_VOLUME | CHIP_HAS_BASSTREBLE | CHIP_HAS_INPUTSEL, + + .leftreg = TDA8425_VL, + .rightreg = TDA8425_VR, + .bassreg = TDA8425_BA, + .treblereg = TDA8425_TR, + .volfunc = tda8425_shift10, + .bassfunc = tda8425_shift12, + .treblefunc = tda8425_shift12, + + .inputreg = TDA8425_S1, + .inputmap = { TDA8425_S1_CH1, TDA8425_S1_CH1, TDA8425_S1_CH1 }, + .inputmute = TDA8425_S1_OFF, + + .setmode = tda8425_setmode, + .initialize = tda8425_initialize, + }, + { + .name = "pic16c54 (PV951)", + .id = I2C_DRIVERID_PIC16C54_PV951, + .insmodopt = &pic16c54, + .addr_lo = I2C_PIC16C54 >> 1, + .addr_hi = I2C_PIC16C54>> 1, + .registers = 2, + .flags = CHIP_HAS_INPUTSEL, + + .inputreg = PIC16C54_REG_MISC, + .inputmap = {PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_TUNER, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_NOTMUTE|PIC16C54_MISC_SWITCH_LINE, + PIC16C54_MISC_SND_MUTE,PIC16C54_MISC_SND_MUTE, + PIC16C54_MISC_SND_NOTMUTE}, + .inputmute = PIC16C54_MISC_SND_MUTE, + }, + { + .name = "ta8874z", + .id = -1, + //.id = I2C_DRIVERID_TA8874Z, + .checkit = ta8874z_checkit, + .insmodopt = &ta8874z, + .addr_lo = I2C_TDA9840 >> 1, + .addr_hi = I2C_TDA9840 >> 1, + .registers = 2, + + .getmode = ta8874z_getmode, + .setmode = ta8874z_setmode, + .checkmode = generic_checkmode, + + .init = {2, { TA8874Z_MONO_SET, TA8874Z_SEPARATION_DEFAULT}}, + }, + { .name = NULL } /* EOF */ +}; + + +/* ---------------------------------------------------------------------- */ +/* i2c registration */ + +static int chip_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct CHIPSTATE *chip; + struct CHIPDESC *desc; + + chip = kmalloc(sizeof(*chip),GFP_KERNEL); + if (!chip) + return -ENOMEM; + memset(chip,0,sizeof(*chip)); + memcpy(&chip->c,&client_template,sizeof(struct i2c_client)); + chip->c.adapter = adap; + chip->c.addr = addr; + i2c_set_clientdata(&chip->c, chip); + + /* find description for the chip */ + dprintk("tvaudio: chip found @ i2c-addr=0x%x\n", addr<<1); + for (desc = chiplist; desc->name != NULL; desc++) { + if (0 == *(desc->insmodopt)) + continue; + if (addr < desc->addr_lo || + addr > desc->addr_hi) + continue; + if (desc->checkit && !desc->checkit(chip)) + continue; + break; + } + if (desc->name == NULL) { + dprintk("tvaudio: no matching chip description found\n"); + return -EIO; + } + printk("tvaudio: found %s @ 0x%x\n", desc->name, addr<<1); + dprintk("tvaudio: matches:%s%s%s.\n", + (desc->flags & CHIP_HAS_VOLUME) ? " volume" : "", + (desc->flags & CHIP_HAS_BASSTREBLE) ? " bass/treble" : "", + (desc->flags & CHIP_HAS_INPUTSEL) ? " audiomux" : ""); + + /* fill required data structures */ + strcpy(i2c_clientname(&chip->c),desc->name); + chip->type = desc-chiplist; + chip->shadow.count = desc->registers+1; + chip->prevmode = -1; + /* register */ + i2c_attach_client(&chip->c); + + /* initialization */ + if (desc->initialize != NULL) + desc->initialize(chip); + else + chip_cmd(chip,"init",&desc->init); + + if (desc->flags & CHIP_HAS_VOLUME) { + chip->left = desc->leftinit ? desc->leftinit : 65535; + chip->right = desc->rightinit ? desc->rightinit : 65535; + chip_write(chip,desc->leftreg,desc->volfunc(chip->left)); + chip_write(chip,desc->rightreg,desc->volfunc(chip->right)); + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + chip->treble = desc->trebleinit ? desc->trebleinit : 32768; + chip->bass = desc->bassinit ? desc->bassinit : 32768; + chip_write(chip,desc->bassreg,desc->bassfunc(chip->bass)); + chip_write(chip,desc->treblereg,desc->treblefunc(chip->treble)); + } + + chip->tpid = -1; + if (desc->checkmode) { + /* start async thread */ + init_timer(&chip->wt); + chip->wt.function = chip_thread_wake; + chip->wt.data = (unsigned long)chip; + init_waitqueue_head(&chip->wq); + init_completion(&chip->texit); + chip->tpid = kernel_thread(chip_thread,(void *)chip,0); + if (chip->tpid < 0) + printk(KERN_WARNING "%s: kernel_thread() failed\n", + i2c_clientname(&chip->c)); + wake_up_interruptible(&chip->wq); + } + return 0; +} + +static int chip_probe(struct i2c_adapter *adap) +{ + /* don't attach on saa7146 based cards, + because dedicated drivers are used */ + if ((adap->id & I2C_ALGO_SAA7146)) + return 0; +#ifdef I2C_CLASS_TV_ANALOG + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, chip_attach); +#else + switch (adap->id) { + case I2C_ALGO_BIT | I2C_HW_B_BT848: + case I2C_ALGO_BIT | I2C_HW_B_RIVA: + case I2C_ALGO_SAA7134: + return i2c_probe(adap, &addr_data, chip_attach); + } +#endif + return 0; +} + +static int chip_detach(struct i2c_client *client) +{ + struct CHIPSTATE *chip = i2c_get_clientdata(client); + + del_timer_sync(&chip->wt); + if (chip->tpid >= 0) { + /* shutdown async thread */ + chip->done = 1; + wake_up_interruptible(&chip->wq); + wait_for_completion(&chip->texit); + } + + i2c_detach_client(&chip->c); + kfree(chip); + return 0; +} + +/* ---------------------------------------------------------------------- */ +/* video4linux interface */ + +static int chip_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + __u16 *sarg = arg; + struct CHIPSTATE *chip = i2c_get_clientdata(client); + struct CHIPDESC *desc = chiplist + chip->type; + + dprintk("%s: chip_command 0x%x\n",i2c_clientname(&chip->c),cmd); + + switch (cmd) { + case AUDC_SET_INPUT: + if (desc->flags & CHIP_HAS_INPUTSEL) { + if (*sarg & 0x80) + chip_write_masked(chip,desc->inputreg,desc->inputmute,desc->inputmask); + else + chip_write_masked(chip,desc->inputreg,desc->inputmap[*sarg],desc->inputmask); + } + break; + + case AUDC_SET_RADIO: + dprintk(KERN_DEBUG "tvaudio: AUDC_SET_RADIO\n"); + chip->norm = VIDEO_MODE_RADIO; + chip->watch_stereo = 0; + /* del_timer(&chip->wt); */ + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + if (desc->flags & CHIP_HAS_VOLUME) { + va->flags |= VIDEO_AUDIO_VOLUME; + va->volume = max(chip->left,chip->right); + if (va->volume) + va->balance = (32768*min(chip->left,chip->right))/ + va->volume; + else + va->balance = 32768; + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + va->flags |= VIDEO_AUDIO_BASS | VIDEO_AUDIO_TREBLE; + va->bass = chip->bass; + va->treble = chip->treble; + } + if (chip->norm != VIDEO_MODE_RADIO) { + if (desc->getmode) + va->mode = desc->getmode(chip); + else + va->mode = VIDEO_SOUND_MONO; + } + break; + } + + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + if (desc->flags & CHIP_HAS_VOLUME) { + chip->left = (min(65536 - va->balance,32768) * + va->volume) / 32768; + chip->right = (min(va->balance,(__u16)32768) * + va->volume) / 32768; + chip_write(chip,desc->leftreg,desc->volfunc(chip->left)); + chip_write(chip,desc->rightreg,desc->volfunc(chip->right)); + } + if (desc->flags & CHIP_HAS_BASSTREBLE) { + chip->bass = va->bass; + chip->treble = va->treble; + chip_write(chip,desc->bassreg,desc->bassfunc(chip->bass)); + chip_write(chip,desc->treblereg,desc->treblefunc(chip->treble)); + } + if (desc->setmode && va->mode) { + chip->watch_stereo = 0; + /* del_timer(&chip->wt); */ + chip->mode = va->mode; + desc->setmode(chip,va->mode); + } + break; + } + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + dprintk(KERN_DEBUG "tvaudio: VIDIOCSCHAN\n"); + chip->norm = vc->norm; + break; + } + case VIDIOCSFREQ: + { + chip->mode = 0; /* automatic */ + if (desc->checkmode) { + desc->setmode(chip,VIDEO_SOUND_MONO); + if (chip->prevmode != VIDEO_SOUND_MONO) + chip->prevmode = -1; /* reset previous mode */ + mod_timer(&chip->wt, jiffies+2*HZ); + /* the thread will call checkmode() later */ + } + } + } + return 0; +} + + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "generic i2c audio driver", + .id = I2C_DRIVERID_TVAUDIO, + .flags = I2C_DF_NOTIFY, + .attach_adapter = chip_probe, + .detach_client = chip_detach, + .command = chip_command, +}; + +static struct i2c_client client_template = +{ + I2C_DEVNAME("(unset)"), + .flags = I2C_CLIENT_ALLOW_USE, + .driver = &driver, +}; + +static int __init audiochip_init_module(void) +{ + struct CHIPDESC *desc; + printk(KERN_INFO "tvaudio: TV audio decoder + audio/video mux driver\n"); + printk(KERN_INFO "tvaudio: known chips: "); + for (desc = chiplist; desc->name != NULL; desc++) + printk("%s%s", (desc == chiplist) ? "" : ",",desc->name); + printk("\n"); + + return i2c_add_driver(&driver); +} + +static void __exit audiochip_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(audiochip_init_module); +module_exit(audiochip_cleanup_module); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tvaudio.h b/drivers/media/video/tvaudio.h new file mode 100644 index 000000000000..af7e116af9a9 --- /dev/null +++ b/drivers/media/video/tvaudio.h @@ -0,0 +1,14 @@ +/* + * i2c bus addresses for the chips supported by tvaudio.c + */ + +#define I2C_TDA8425 0x82 +#define I2C_TDA9840 0x84 /* also used by TA8874Z */ +#define I2C_TDA985x_L 0xb4 /* also used by 9873 */ +#define I2C_TDA985x_H 0xb6 +#define I2C_TDA9874 0xb0 /* also used by 9875 */ + +#define I2C_TEA6300 0x80 /* also used by 6320 */ +#define I2C_TEA6420 0x98 + +#define I2C_PIC16C54 0x96 /* PV951 */ diff --git a/drivers/media/video/tveeprom.c b/drivers/media/video/tveeprom.c new file mode 100644 index 000000000000..e1443a0937e3 --- /dev/null +++ b/drivers/media/video/tveeprom.c @@ -0,0 +1,587 @@ +/* + * tveeprom - eeprom decoder for tvcard configuration eeproms + * + * Data and decoding routines shamelessly borrowed from bttv-cards.c + * eeprom access routine shamelessly borrowed from bttv-if.c + * which are: + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999-2001 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + * Adjustments to fit a more general model and all bugs: + + Copyright (C) 2003 John Klar <linpvr at projectplasma.com> + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/i2c.h> + +#include <media/tuner.h> +#include <media/tveeprom.h> + +MODULE_DESCRIPTION("i2c Hauppauge eeprom decoder driver"); +MODULE_AUTHOR("John Klar"); +MODULE_LICENSE("GPL"); + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +#define STRM(array,i) (i < sizeof(array)/sizeof(char*) ? array[i] : "unknown") + +#define dprintk(num, args...) \ + do { \ + if (debug >= num) \ + printk(KERN_INFO "tveeprom: " args); \ + } while (0) + +#define TVEEPROM_KERN_ERR(args...) printk(KERN_ERR "tveeprom: " args); +#define TVEEPROM_KERN_INFO(args...) printk(KERN_INFO "tveeprom: " args); + +/* ----------------------------------------------------------------------- */ +/* some hauppauge specific stuff */ + +static struct HAUPPAUGE_TUNER_FMT +{ + int id; + char *name; +} +hauppauge_tuner_fmt[] = +{ + { 0x00000000, "unknown1" }, + { 0x00000000, "unknown2" }, + { 0x00000007, "PAL(B/G)" }, + { 0x00001000, "NTSC(M)" }, + { 0x00000010, "PAL(I)" }, + { 0x00400000, "SECAM(L/L�)" }, + { 0x00000e00, "PAL(D/K)" }, + { 0x03000000, "ATSC Digital" }, +}; + +/* This is the full list of possible tuners. Many thanks to Hauppauge for + supplying this information. Note that many tuners where only used for + testing and never made it to the outside world. So you will only see + a subset in actual produced cards. */ +static struct HAUPPAUGE_TUNER +{ + int id; + char *name; +} +hauppauge_tuner[] = +{ + /* 0-9 */ + { TUNER_ABSENT, "None" }, + { TUNER_ABSENT, "External" }, + { TUNER_ABSENT, "Unspecified" }, + { TUNER_PHILIPS_PAL, "Philips FI1216" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246" }, + { TUNER_PHILIPS_PAL_DK,"Philips FI1256" }, + { TUNER_PHILIPS_PAL, "Philips FI1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" }, + /* 10-19 */ + { TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" }, + { TUNER_PHILIPS_PAL_DK,"Philips FI1256 MK2" }, + { TUNER_TEMIC_NTSC, "Temic 4032FY5" }, + { TUNER_TEMIC_PAL, "Temic 4002FH5" }, + { TUNER_TEMIC_PAL_I, "Temic 4062FY5" }, + { TUNER_PHILIPS_PAL, "Philips FR1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" }, + /* 20-29 */ + { TUNER_PHILIPS_PAL_DK,"Philips FR1256 MK2" }, + { TUNER_PHILIPS_PAL, "Philips FM1216" }, + { TUNER_PHILIPS_SECAM, "Philips FM1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FM1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FM1246" }, + { TUNER_PHILIPS_PAL_DK,"Philips FM1256" }, + { TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" }, + { TUNER_ABSENT, "Samsung TCPN9082D" }, + { TUNER_ABSENT, "Samsung TCPM9092P" }, + { TUNER_TEMIC_4006FH5_PAL, "Temic 4006FH5" }, + /* 30-39 */ + { TUNER_ABSENT, "Samsung TCPN9085D" }, + { TUNER_ABSENT, "Samsung TCPB9085P" }, + { TUNER_ABSENT, "Samsung TCPL9091P" }, + { TUNER_TEMIC_4039FR5_NTSC, "Temic 4039FR5" }, + { TUNER_PHILIPS_FQ1216ME, "Philips FQ1216 ME" }, + { TUNER_TEMIC_4066FY5_PAL_I, "Temic 4066FY5" }, + { TUNER_PHILIPS_NTSC, "Philips TD1536" }, + { TUNER_PHILIPS_NTSC, "Philips TD1536D" }, + { TUNER_PHILIPS_NTSC, "Philips FMR1236" }, /* mono radio */ + { TUNER_ABSENT, "Philips FI1256MP" }, + /* 40-49 */ + { TUNER_ABSENT, "Samsung TCPQ9091P" }, + { TUNER_TEMIC_4006FN5_MULTI_PAL, "Temic 4006FN5" }, + { TUNER_TEMIC_4009FR5_PAL, "Temic 4009FR5" }, + { TUNER_TEMIC_4046FM5, "Temic 4046FM5" }, + { TUNER_TEMIC_4009FN5_MULTI_PAL_FM, "Temic 4009FN5" }, + { TUNER_ABSENT, "Philips TD1536D FH 44"}, + { TUNER_LG_NTSC_FM, "LG TP18NSR01F"}, + { TUNER_LG_PAL_FM, "LG TP18PSB01D"}, + { TUNER_LG_PAL, "LG TP18PSB11D"}, + { TUNER_LG_PAL_I_FM, "LG TAPC-I001D"}, + /* 50-59 */ + { TUNER_LG_PAL_I, "LG TAPC-I701D"}, + { TUNER_ABSENT, "Temic 4042FI5"}, + { TUNER_MICROTUNE_4049FM5, "Microtune 4049 FM5"}, + { TUNER_ABSENT, "LG TPI8NSR11F"}, + { TUNER_ABSENT, "Microtune 4049 FM5 Alt I2C"}, + { TUNER_ABSENT, "Philips FQ1216ME MK3"}, + { TUNER_ABSENT, "Philips FI1236 MK3"}, + { TUNER_PHILIPS_FM1216ME_MK3, "Philips FM1216 ME MK3"}, + { TUNER_ABSENT, "Philips FM1236 MK3"}, + { TUNER_ABSENT, "Philips FM1216MP MK3"}, + /* 60-69 */ + { TUNER_ABSENT, "LG S001D MK3"}, + { TUNER_ABSENT, "LG M001D MK3"}, + { TUNER_ABSENT, "LG S701D MK3"}, + { TUNER_ABSENT, "LG M701D MK3"}, + { TUNER_ABSENT, "Temic 4146FM5"}, + { TUNER_ABSENT, "Temic 4136FY5"}, + { TUNER_ABSENT, "Temic 4106FH5"}, + { TUNER_ABSENT, "Philips FQ1216LMP MK3"}, + { TUNER_LG_NTSC_TAPE, "LG TAPE H001F MK3"}, + { TUNER_ABSENT, "LG TAPE H701F MK3"}, + /* 70-79 */ + { TUNER_ABSENT, "LG TALN H200T"}, + { TUNER_ABSENT, "LG TALN H250T"}, + { TUNER_ABSENT, "LG TALN M200T"}, + { TUNER_ABSENT, "LG TALN Z200T"}, + { TUNER_ABSENT, "LG TALN S200T"}, + { TUNER_ABSENT, "Thompson DTT7595"}, + { TUNER_ABSENT, "Thompson DTT7592"}, + { TUNER_ABSENT, "Silicon TDA8275C1 8290"}, + { TUNER_ABSENT, "Silicon TDA8275C1 8290 FM"}, + { TUNER_ABSENT, "Thompson DTT757"}, + /* 80-89 */ + { TUNER_ABSENT, "Philips FQ1216LME MK3"}, + { TUNER_ABSENT, "LG TAPC G701D"}, + { TUNER_LG_NTSC_NEW_TAPC, "LG TAPC H791F"}, + { TUNER_ABSENT, "TCL 2002MB 3"}, + { TUNER_ABSENT, "TCL 2002MI 3"}, + { TUNER_TCL_2002N, "TCL 2002N 6A"}, + { TUNER_ABSENT, "Philips FQ1236 MK3"}, + { TUNER_ABSENT, "Samsung TCPN 2121P30A"}, + { TUNER_ABSENT, "Samsung TCPE 4121P30A"}, + { TUNER_ABSENT, "TCL MFPE05 2"}, + /* 90-99 */ + { TUNER_ABSENT, "LG TALN H202T"}, + { TUNER_PHILIPS_FQ1216AME_MK4, "Philips FQ1216AME MK4"}, + { TUNER_PHILIPS_FQ1236A_MK4, "Philips FQ1236A MK4"}, + { TUNER_ABSENT, "Philips FQ1286A MK4"}, + { TUNER_ABSENT, "Philips FQ1216ME MK5"}, + { TUNER_ABSENT, "Philips FQ1236 MK5"}, + { TUNER_ABSENT, "Unspecified"}, + { TUNER_LG_PAL_TAPE, "LG PAL (TAPE Series)"}, +}; + +static char *sndtype[] = { + "None", "TEA6300", "TEA6320", "TDA9850", "MSP3400C", "MSP3410D", + "MSP3415", "MSP3430", "MSP3438", "CS5331", "MSP3435", "MSP3440", + "MSP3445", "MSP3411", "MSP3416", "MSP3425", + + "Type 0x10","Type 0x11","Type 0x12","Type 0x13", + "Type 0x14","Type 0x15","Type 0x16","Type 0x17", + "Type 0x18","MSP4418","Type 0x1a","MSP4448", + "Type 0x1c","Type 0x1d","Type 0x1e","Type 0x1f", +}; + +static int hasRadioTuner(int tunerType) +{ + switch (tunerType) { + case 18: //PNPEnv_TUNER_FR1236_MK2: + case 23: //PNPEnv_TUNER_FM1236: + case 38: //PNPEnv_TUNER_FMR1236: + case 16: //PNPEnv_TUNER_FR1216_MK2: + case 19: //PNPEnv_TUNER_FR1246_MK2: + case 21: //PNPEnv_TUNER_FM1216: + case 24: //PNPEnv_TUNER_FM1246: + case 17: //PNPEnv_TUNER_FR1216MF_MK2: + case 22: //PNPEnv_TUNER_FM1216MF: + case 20: //PNPEnv_TUNER_FR1256_MK2: + case 25: //PNPEnv_TUNER_FM1256: + case 33: //PNPEnv_TUNER_4039FR5: + case 42: //PNPEnv_TUNER_4009FR5: + case 52: //PNPEnv_TUNER_4049FM5: + case 54: //PNPEnv_TUNER_4049FM5_AltI2C: + case 44: //PNPEnv_TUNER_4009FN5: + case 31: //PNPEnv_TUNER_TCPB9085P: + case 30: //PNPEnv_TUNER_TCPN9085D: + case 46: //PNPEnv_TUNER_TP18NSR01F: + case 47: //PNPEnv_TUNER_TP18PSB01D: + case 49: //PNPEnv_TUNER_TAPC_I001D: + case 60: //PNPEnv_TUNER_TAPE_S001D_MK3: + case 57: //PNPEnv_TUNER_FM1216ME_MK3: + case 59: //PNPEnv_TUNER_FM1216MP_MK3: + case 58: //PNPEnv_TUNER_FM1236_MK3: + case 68: //PNPEnv_TUNER_TAPE_H001F_MK3: + case 61: //PNPEnv_TUNER_TAPE_M001D_MK3: + case 78: //PNPEnv_TUNER_TDA8275C1_8290_FM: + case 89: //PNPEnv_TUNER_TCL_MFPE05_2: + case 92: //PNPEnv_TUNER_PHILIPS_FQ1236A_MK4: + return 1; + } + return 0; +} + +void tveeprom_hauppauge_analog(struct tveeprom *tvee, unsigned char *eeprom_data) +{ + /* ---------------------------------------------- + ** The hauppauge eeprom format is tagged + ** + ** if packet[0] == 0x84, then packet[0..1] == length + ** else length = packet[0] & 3f; + ** if packet[0] & f8 == f8, then EOD and packet[1] == checksum + ** + ** In our (ivtv) case we're interested in the following: + ** tuner type: tag [00].05 or [0a].01 (index into hauppauge_tuner) + ** tuner fmts: tag [00].04 or [0a].00 (bitmask index into hauppauge_tuner_fmt) + ** radio: tag [00].{last} or [0e].00 (bitmask. bit2=FM) + ** audio proc: tag [02].01 or [05].00 (lower nibble indexes lut?) + + ** Fun info: + ** model: tag [00].07-08 or [06].00-01 + ** revision: tag [00].09-0b or [06].04-06 + ** serial#: tag [01].05-07 or [04].04-06 + + ** # of inputs/outputs ??? + */ + + int i, j, len, done, beenhere, tag, tuner = 0, t_format = 0; + char *t_name = NULL, *t_fmt_name = NULL; + + dprintk(1, "%s\n",__FUNCTION__); + tvee->revision = done = len = beenhere = 0; + for (i = 0; !done && i < 256; i += len) { + dprintk(2, "processing pos = %02x (%02x, %02x)\n", + i, eeprom_data[i], eeprom_data[i + 1]); + + if (eeprom_data[i] == 0x84) { + len = eeprom_data[i + 1] + (eeprom_data[i + 2] << 8); + i+=3; + } else if ((eeprom_data[i] & 0xf0) == 0x70) { + if ((eeprom_data[i] & 0x08)) { + /* verify checksum! */ + done = 1; + break; + } + len = eeprom_data[i] & 0x07; + ++i; + } else { + TVEEPROM_KERN_ERR("Encountered bad packet header [%02x]. " + "Corrupt or not a Hauppauge eeprom.\n", eeprom_data[i]); + return; + } + + dprintk(1, "%3d [%02x] ", len, eeprom_data[i]); + for(j = 1; j < len; j++) { + dprintk(1, "%02x ", eeprom_data[i + j]); + } + dprintk(1, "\n"); + + /* process by tag */ + tag = eeprom_data[i]; + switch (tag) { + case 0x00: + tuner = eeprom_data[i+6]; + t_format = eeprom_data[i+5]; + tvee->has_radio = eeprom_data[i+len-1]; + tvee->model = + eeprom_data[i+8] + + (eeprom_data[i+9] << 8); + tvee->revision = eeprom_data[i+10] + + (eeprom_data[i+11] << 8) + + (eeprom_data[i+12] << 16); + break; + case 0x01: + tvee->serial_number = + eeprom_data[i+6] + + (eeprom_data[i+7] << 8) + + (eeprom_data[i+8] << 16); + break; + case 0x02: + tvee->audio_processor = eeprom_data[i+2] & 0x0f; + break; + case 0x04: + tvee->serial_number = + eeprom_data[i+5] + + (eeprom_data[i+6] << 8) + + (eeprom_data[i+7] << 16); + break; + case 0x05: + tvee->audio_processor = eeprom_data[i+1] & 0x0f; + break; + case 0x06: + tvee->model = + eeprom_data[i+1] + + (eeprom_data[i+2] << 8); + tvee->revision = eeprom_data[i+5] + + (eeprom_data[i+6] << 8) + + (eeprom_data[i+7] << 16); + break; + case 0x0a: + if(beenhere == 0) { + tuner = eeprom_data[i+2]; + t_format = eeprom_data[i+1]; + beenhere = 1; + break; + } else { + break; + } + case 0x0e: + tvee->has_radio = eeprom_data[i+1]; + break; + default: + dprintk(1, "Not sure what to do with tag [%02x]\n", tag); + /* dump the rest of the packet? */ + } + + } + + if (!done) { + TVEEPROM_KERN_ERR("Ran out of data!\n"); + return; + } + + if (tvee->revision != 0) { + tvee->rev_str[0] = 32 + ((tvee->revision >> 18) & 0x3f); + tvee->rev_str[1] = 32 + ((tvee->revision >> 12) & 0x3f); + tvee->rev_str[2] = 32 + ((tvee->revision >> 6) & 0x3f); + tvee->rev_str[3] = 32 + ( tvee->revision & 0x3f); + tvee->rev_str[4] = 0; + } + + if (hasRadioTuner(tuner) && !tvee->has_radio) { + TVEEPROM_KERN_INFO("The eeprom says no radio is present, but the tuner type\n"); + TVEEPROM_KERN_INFO("indicates otherwise. I will assume that radio is present.\n"); + tvee->has_radio = 1; + } + + if (tuner < sizeof(hauppauge_tuner)/sizeof(struct HAUPPAUGE_TUNER)) { + tvee->tuner_type = hauppauge_tuner[tuner].id; + t_name = hauppauge_tuner[tuner].name; + } else { + t_name = "<unknown>"; + } + + tvee->tuner_formats = 0; + t_fmt_name = "<none>"; + for (i = 0; i < 8; i++) { + if (t_format & (1<<i)) { + tvee->tuner_formats |= hauppauge_tuner_fmt[i].id; + /* yuck */ + t_fmt_name = hauppauge_tuner_fmt[i].name; + } + } + +#if 0 + if (t_format < sizeof(hauppauge_tuner_fmt)/sizeof(struct HAUPPAUGE_TUNER_FMT)) { + tvee->tuner_formats = hauppauge_tuner_fmt[t_format].id; + t_fmt_name = hauppauge_tuner_fmt[t_format].name; + } else { + t_fmt_name = "<unknown>"; + } +#endif + + TVEEPROM_KERN_INFO("Hauppauge: model = %d, rev = %s, serial# = %d\n", + tvee->model, + tvee->rev_str, + tvee->serial_number); + TVEEPROM_KERN_INFO("tuner = %s (idx = %d, type = %d)\n", + t_name, + tuner, + tvee->tuner_type); + TVEEPROM_KERN_INFO("tuner fmt = %s (eeprom = 0x%02x, v4l2 = 0x%08x)\n", + t_fmt_name, + t_format, + tvee->tuner_formats); + + TVEEPROM_KERN_INFO("audio_processor = %s (type = %d)\n", + STRM(sndtype,tvee->audio_processor), + tvee->audio_processor); + +} +EXPORT_SYMBOL(tveeprom_hauppauge_analog); + +/* ----------------------------------------------------------------------- */ +/* generic helper functions */ + +int tveeprom_read(struct i2c_client *c, unsigned char *eedata, int len) +{ + unsigned char buf; + int err; + + dprintk(1, "%s\n",__FUNCTION__); + buf = 0; + if (1 != (err = i2c_master_send(c,&buf,1))) { + printk(KERN_INFO "tveeprom(%s): Huh, no eeprom present (err=%d)?\n", + c->name,err); + return -1; + } + if (len != (err = i2c_master_recv(c,eedata,len))) { + printk(KERN_WARNING "tveeprom(%s): i2c eeprom read error (err=%d)\n", + c->name,err); + return -1; + } + return 0; +} +EXPORT_SYMBOL(tveeprom_read); + +int tveeprom_dump(unsigned char *eedata, int len) +{ + int i; + + dprintk(1, "%s\n",__FUNCTION__); + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "tveeprom: %02x:",i); + printk(" %02x",eedata[i]); + if (15 == (i % 16)) + printk("\n"); + } + return 0; +} +EXPORT_SYMBOL(tveeprom_dump); + +/* ----------------------------------------------------------------------- */ +/* needed for ivtv.sf.net at the moment. Should go away in the long */ +/* run, just call the exported tveeprom_* directly, there is no point in */ +/* using the indirect way via i2c_driver->command() */ + +#ifndef I2C_DRIVERID_TVEEPROM +# define I2C_DRIVERID_TVEEPROM I2C_DRIVERID_EXP2 +#endif + +static unsigned short normal_i2c[] = { + 0xa0 >> 1, + I2C_CLIENT_END, +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +struct i2c_driver i2c_driver_tveeprom; + +static int +tveeprom_command(struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct tveeprom eeprom; + u32 *eeprom_props = arg; + u8 *buf; + + switch (cmd) { + case 0: + buf = kmalloc(256,GFP_KERNEL); + memset(buf,0,256); + tveeprom_read(client,buf,256); + tveeprom_hauppauge_analog(&eeprom,buf); + kfree(buf); + eeprom_props[0] = eeprom.tuner_type; + eeprom_props[1] = eeprom.tuner_formats; + eeprom_props[2] = eeprom.model; + eeprom_props[3] = eeprom.revision; + break; + default: + return -EINVAL; + } + return 0; +} + +static int +tveeprom_detect_client(struct i2c_adapter *adapter, + int address, + int kind) +{ + struct i2c_client *client; + + dprintk(1,"%s: id 0x%x @ 0x%x\n",__FUNCTION__, + adapter->id, address << 1); + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (NULL == client) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_tveeprom; + client->flags = I2C_CLIENT_ALLOW_USE; + snprintf(client->name, sizeof(client->name), "tveeprom"); + i2c_attach_client(client); + return 0; +} + +static int +tveeprom_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1,"%s: id 0x%x\n",__FUNCTION__,adapter->id); + if (adapter->id != (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return 0; + return i2c_probe(adapter, &addr_data, tveeprom_detect_client); +} + +static int +tveeprom_detach_client (struct i2c_client *client) +{ + int err; + + err = i2c_detach_client(client); + if (err < 0) + return err; + kfree(client); + return 0; +} + +struct i2c_driver i2c_driver_tveeprom = { + .owner = THIS_MODULE, + .name = "tveeprom", + .id = I2C_DRIVERID_TVEEPROM, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tveeprom_attach_adapter, + .detach_client = tveeprom_detach_client, + .command = tveeprom_command, +}; + +static int __init tveeprom_init(void) +{ + return i2c_add_driver(&i2c_driver_tveeprom); +} + +static void __exit tveeprom_exit(void) +{ + i2c_del_driver(&i2c_driver_tveeprom); +} + +module_init(tveeprom_init); +module_exit(tveeprom_exit); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tvmixer.c b/drivers/media/video/tvmixer.c new file mode 100644 index 000000000000..eafd7061b310 --- /dev/null +++ b/drivers/media/video/tvmixer.c @@ -0,0 +1,367 @@ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <linux/init.h> +#include <linux/kdev_t.h> +#include <linux/sound.h> +#include <linux/soundcard.h> + +#include <asm/semaphore.h> +#include <asm/uaccess.h> + + +#define DEV_MAX 4 + +static int devnr = -1; +module_param(devnr, int, 0644); + +MODULE_AUTHOR("Gerd Knorr"); +MODULE_LICENSE("GPL"); + +/* ----------------------------------------------------------------------- */ + +struct TVMIXER { + struct i2c_client *dev; + int minor; + int count; +}; + +static struct TVMIXER devices[DEV_MAX]; + +static int tvmixer_adapters(struct i2c_adapter *adap); +static int tvmixer_clients(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ + +static int mix_to_v4l(int i) +{ + int r; + + r = ((i & 0xff) * 65536 + 50) / 100; + if (r > 65535) r = 65535; + if (r < 0) r = 0; + return r; +} + +static int v4l_to_mix(int i) +{ + int r; + + r = (i * 100 + 32768) / 65536; + if (r > 100) r = 100; + if (r < 0) r = 0; + return r | (r << 8); +} + +static int v4l_to_mix2(int l, int r) +{ + r = (r * 100 + 32768) / 65536; + if (r > 100) r = 100; + if (r < 0) r = 0; + l = (l * 100 + 32768) / 65536; + if (l > 100) l = 100; + if (l < 0) l = 0; + return (r << 8) | l; +} + +static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video_audio va; + int left,right,ret,val = 0; + struct TVMIXER *mix = file->private_data; + struct i2c_client *client = mix->dev; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + if (NULL == client) + return -ENODEV; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strlcpy(info.id, "tv card", sizeof(info.id)); + strlcpy(info.name, i2c_clientname(client), sizeof(info.name)); + info.modify_counter = 42 /* FIXME */; + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strlcpy(info.id, "tv card", sizeof(info.id)); + strlcpy(info.name, i2c_clientname(client), sizeof(info.name)); + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, p); + + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + if (get_user(val, p)) + return -EFAULT; + + /* read state */ + memset(&va,0,sizeof(va)); + client->driver->command(client,VIDIOCGAUDIO,&va); + + switch (cmd) { + case MIXER_READ(SOUND_MIXER_RECMASK): + case MIXER_READ(SOUND_MIXER_CAPS): + case MIXER_READ(SOUND_MIXER_RECSRC): + case MIXER_WRITE(SOUND_MIXER_RECSRC): + ret = 0; + break; + + case MIXER_READ(SOUND_MIXER_STEREODEVS): + ret = SOUND_MASK_VOLUME; + break; + case MIXER_READ(SOUND_MIXER_DEVMASK): + ret = SOUND_MASK_VOLUME; + if (va.flags & VIDEO_AUDIO_BASS) + ret |= SOUND_MASK_BASS; + if (va.flags & VIDEO_AUDIO_TREBLE) + ret |= SOUND_MASK_TREBLE; + break; + + case MIXER_WRITE(SOUND_MIXER_VOLUME): + left = mix_to_v4l(val); + right = mix_to_v4l(val >> 8); + va.volume = max(left,right); + va.balance = (32768*min(left,right)) / (va.volume ? va.volume : 1); + va.balance = (left<right) ? (65535-va.balance) : va.balance; + if (va.volume) + va.flags &= ~VIDEO_AUDIO_MUTE; + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_VOLUME): + left = (min(65536 - va.balance,32768) * + va.volume) / 32768; + right = (min(va.balance,(u16)32768) * + va.volume) / 32768; + ret = v4l_to_mix2(left,right); + break; + + case MIXER_WRITE(SOUND_MIXER_BASS): + va.bass = mix_to_v4l(val); + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_BASS): + ret = v4l_to_mix(va.bass); + break; + + case MIXER_WRITE(SOUND_MIXER_TREBLE): + va.treble = mix_to_v4l(val); + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_TREBLE): + ret = v4l_to_mix(va.treble); + break; + + default: + return -EINVAL; + } + if (put_user(ret, p)) + return -EFAULT; + return 0; +} + +static int tvmixer_open(struct inode *inode, struct file *file) +{ + int i, minor = iminor(inode); + struct TVMIXER *mix = NULL; + struct i2c_client *client = NULL; + + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].minor == minor) { + mix = devices+i; + client = mix->dev; + break; + } + } + + if (NULL == client) + return -ENODEV; + + /* lock bttv in memory while the mixer is in use */ + file->private_data = mix; +#ifndef I2C_PEC + if (client->adapter->inc_use) + client->adapter->inc_use(client->adapter); +#endif + if (client->adapter->owner) + try_module_get(client->adapter->owner); + return 0; +} + +static int tvmixer_release(struct inode *inode, struct file *file) +{ + struct TVMIXER *mix = file->private_data; + struct i2c_client *client; + + client = mix->dev; + if (NULL == client) { + return -ENODEV; + } + +#ifndef I2C_PEC + if (client->adapter->dec_use) + client->adapter->dec_use(client->adapter); +#endif + if (client->adapter->owner) + module_put(client->adapter->owner); + return 0; +} + +static struct i2c_driver driver = { +#ifdef I2C_PEC + .owner = THIS_MODULE, +#endif + .name = "tv card mixer driver", + .id = I2C_DRIVERID_TVMIXER, +#ifdef I2C_DF_DUMMY + .flags = I2C_DF_DUMMY, +#else + .flags = I2C_DF_NOTIFY, + .detach_adapter = tvmixer_adapters, +#endif + .attach_adapter = tvmixer_adapters, + .detach_client = tvmixer_clients, +}; + +static struct file_operations tvmixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = tvmixer_ioctl, + .open = tvmixer_open, + .release = tvmixer_release, +}; + +/* ----------------------------------------------------------------------- */ + +static int tvmixer_adapters(struct i2c_adapter *adap) +{ + struct list_head *item; + struct i2c_client *client; + + list_for_each(item,&adap->clients) { + client = list_entry(item, struct i2c_client, list); + tvmixer_clients(client); + } + return 0; +} + +static int tvmixer_clients(struct i2c_client *client) +{ + struct video_audio va; + int i,minor; + +#ifdef I2C_CLASS_TV_ANALOG + if (!(client->adapter->class & I2C_CLASS_TV_ANALOG)) + return -1; +#else + /* TV card ??? */ + switch (client->adapter->id) { + case I2C_ALGO_BIT | I2C_HW_SMBUS_VOODOO3: + case I2C_ALGO_BIT | I2C_HW_B_BT848: + case I2C_ALGO_BIT | I2C_HW_B_RIVA: + /* ok, have a look ... */ + break; + default: + /* ignore that one */ + return -1; + } +#endif + + /* unregister ?? */ + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].dev == client) { + /* unregister */ + unregister_sound_mixer(devices[i].minor); + devices[i].dev = NULL; + devices[i].minor = -1; + printk("tvmixer: %s unregistered (#1)\n", + i2c_clientname(client)); + return 0; + } + } + + /* look for a free slot */ + for (i = 0; i < DEV_MAX; i++) + if (NULL == devices[i].dev) + break; + if (i == DEV_MAX) { + printk(KERN_WARNING "tvmixer: DEV_MAX too small\n"); + return -1; + } + + /* audio chip with mixer ??? */ + if (NULL == client->driver->command) + return -1; + memset(&va,0,sizeof(va)); + if (0 != client->driver->command(client,VIDIOCGAUDIO,&va)) + return -1; + if (0 == (va.flags & VIDEO_AUDIO_VOLUME)) + return -1; + + /* everything is fine, register */ + if ((minor = register_sound_mixer(&tvmixer_fops,devnr)) < 0) { + printk(KERN_ERR "tvmixer: cannot allocate mixer device\n"); + return -1; + } + + devices[i].minor = minor; + devices[i].count = 0; + devices[i].dev = client; + printk("tvmixer: %s (%s) registered with minor %d\n", + client->name,client->adapter->name,minor); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int __init tvmixer_init_module(void) +{ + int i; + + for (i = 0; i < DEV_MAX; i++) + devices[i].minor = -1; + + return i2c_add_driver(&driver); +} + +static void __exit tvmixer_cleanup_module(void) +{ + int i; + + i2c_del_driver(&driver); + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].minor != -1) { + unregister_sound_mixer(devices[i].minor); + printk("tvmixer: %s unregistered (#2)\n", + i2c_clientname(devices[i].dev)); + } + } +} + +module_init(tvmixer_init_module); +module_exit(tvmixer_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/v4l1-compat.c b/drivers/media/video/v4l1-compat.c new file mode 100644 index 000000000000..b0d4bcb027d0 --- /dev/null +++ b/drivers/media/video/v4l1-compat.c @@ -0,0 +1,1036 @@ +/* + * Video for Linux Two + * Backward Compatibility Layer + * + * Support subroutines for providing V4L2 drivers with backward + * compatibility with applications using the old API. + * + * 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. + * + * Author: Bill Dirks <bdirks@pacbell.net> + * et al. + * + */ + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#include <linux/config.h> + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/videodev.h> + +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/pgtable.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages"); +MODULE_AUTHOR("Bill Dirks"); +MODULE_DESCRIPTION("v4l(1) compatibility layer for v4l2 drivers."); +MODULE_LICENSE("GPL"); + +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG "v4l1-compat: " fmt , ## arg) + +/* + * I O C T L T R A N S L A T I O N + * + * From here on down is the code for translating the numerous + * ioctl commands from the old API to the new API. + */ + +static int +get_v4l_control(struct inode *inode, + struct file *file, + int cid, + v4l2_kioctl drv) +{ + struct v4l2_queryctrl qctrl2; + struct v4l2_control ctrl2; + int err; + + qctrl2.id = cid; + err = drv(inode, file, VIDIOC_QUERYCTRL, &qctrl2); + if (err < 0) + dprintk("VIDIOC_QUERYCTRL: %d\n",err); + if (err == 0 && + !(qctrl2.flags & V4L2_CTRL_FLAG_DISABLED)) + { + ctrl2.id = qctrl2.id; + err = drv(inode, file, VIDIOC_G_CTRL, &ctrl2); + if (err < 0) { + dprintk("VIDIOC_G_CTRL: %d\n",err); + return 0; + } + return ((ctrl2.value - qctrl2.minimum) * 65535 + + (qctrl2.maximum - qctrl2.minimum) / 2) + / (qctrl2.maximum - qctrl2.minimum); + } + return 0; +} + +static int +set_v4l_control(struct inode *inode, + struct file *file, + int cid, + int value, + v4l2_kioctl drv) +{ + struct v4l2_queryctrl qctrl2; + struct v4l2_control ctrl2; + int err; + + qctrl2.id = cid; + err = drv(inode, file, VIDIOC_QUERYCTRL, &qctrl2); + if (err < 0) + dprintk("VIDIOC_QUERYCTRL: %d\n",err); + if (err == 0 && + !(qctrl2.flags & V4L2_CTRL_FLAG_DISABLED) && + !(qctrl2.flags & V4L2_CTRL_FLAG_GRABBED)) + { + if (value < 0) + value = 0; + if (value > 65535) + value = 65535; + if (value && qctrl2.type == V4L2_CTRL_TYPE_BOOLEAN) + value = 65535; + ctrl2.id = qctrl2.id; + ctrl2.value = + (value * (qctrl2.maximum - qctrl2.minimum) + + 32767) + / 65535; + ctrl2.value += qctrl2.minimum; + err = drv(inode, file, VIDIOC_S_CTRL, &ctrl2); + if (err < 0) + dprintk("VIDIOC_S_CTRL: %d\n",err); + } + return 0; +} + +/* ----------------------------------------------------------------- */ + +static int palette2pixelformat[] = { + [VIDEO_PALETTE_GREY] = V4L2_PIX_FMT_GREY, + [VIDEO_PALETTE_RGB555] = V4L2_PIX_FMT_RGB555, + [VIDEO_PALETTE_RGB565] = V4L2_PIX_FMT_RGB565, + [VIDEO_PALETTE_RGB24] = V4L2_PIX_FMT_BGR24, + [VIDEO_PALETTE_RGB32] = V4L2_PIX_FMT_BGR32, + /* yuv packed pixel */ + [VIDEO_PALETTE_YUYV] = V4L2_PIX_FMT_YUYV, + [VIDEO_PALETTE_YUV422] = V4L2_PIX_FMT_YUYV, + [VIDEO_PALETTE_UYVY] = V4L2_PIX_FMT_UYVY, + /* yuv planar */ + [VIDEO_PALETTE_YUV410P] = V4L2_PIX_FMT_YUV410, + [VIDEO_PALETTE_YUV420] = V4L2_PIX_FMT_YUV420, + [VIDEO_PALETTE_YUV420P] = V4L2_PIX_FMT_YUV420, + [VIDEO_PALETTE_YUV411P] = V4L2_PIX_FMT_YUV411P, + [VIDEO_PALETTE_YUV422P] = V4L2_PIX_FMT_YUV422P, +}; + +static unsigned int +palette_to_pixelformat(unsigned int palette) +{ + if (palette < ARRAY_SIZE(palette2pixelformat)) + return palette2pixelformat[palette]; + else + return 0; +} + +static unsigned int +pixelformat_to_palette(int pixelformat) +{ + int palette = 0; + switch (pixelformat) + { + case V4L2_PIX_FMT_GREY: + palette = VIDEO_PALETTE_GREY; + break; + case V4L2_PIX_FMT_RGB555: + palette = VIDEO_PALETTE_RGB555; + break; + case V4L2_PIX_FMT_RGB565: + palette = VIDEO_PALETTE_RGB565; + break; + case V4L2_PIX_FMT_BGR24: + palette = VIDEO_PALETTE_RGB24; + break; + case V4L2_PIX_FMT_BGR32: + palette = VIDEO_PALETTE_RGB32; + break; + /* yuv packed pixel */ + case V4L2_PIX_FMT_YUYV: + palette = VIDEO_PALETTE_YUYV; + break; + case V4L2_PIX_FMT_UYVY: + palette = VIDEO_PALETTE_UYVY; + break; + /* yuv planar */ + case V4L2_PIX_FMT_YUV410: + palette = VIDEO_PALETTE_YUV420; + break; + case V4L2_PIX_FMT_YUV420: + palette = VIDEO_PALETTE_YUV420; + break; + case V4L2_PIX_FMT_YUV411P: + palette = VIDEO_PALETTE_YUV411P; + break; + case V4L2_PIX_FMT_YUV422P: + palette = VIDEO_PALETTE_YUV422P; + break; + } + return palette; +} + +/* ----------------------------------------------------------------- */ + +static int poll_one(struct file *file) +{ + int retval = 1; + poll_table *table; + struct poll_wqueues pwq; + + poll_initwait(&pwq); + table = &pwq.pt; + for (;;) { + int mask; + set_current_state(TASK_INTERRUPTIBLE); + mask = file->f_op->poll(file, table); + if (mask & POLLIN) + break; + table = NULL; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } + set_current_state(TASK_RUNNING); + poll_freewait(&pwq); + return retval; +} + +static int count_inputs(struct inode *inode, + struct file *file, + v4l2_kioctl drv) +{ + struct v4l2_input input2; + int i; + + for (i = 0;; i++) { + memset(&input2,0,sizeof(input2)); + input2.index = i; + if (0 != drv(inode,file,VIDIOC_ENUMINPUT, &input2)) + break; + } + return i; +} + +static int check_size(struct inode *inode, + struct file *file, + v4l2_kioctl drv, + int *maxw, int *maxh) +{ + struct v4l2_fmtdesc desc2; + struct v4l2_format fmt2; + + memset(&desc2,0,sizeof(desc2)); + memset(&fmt2,0,sizeof(fmt2)); + + desc2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (0 != drv(inode,file,VIDIOC_ENUM_FMT, &desc2)) + goto done; + + fmt2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt2.fmt.pix.width = 10000; + fmt2.fmt.pix.height = 10000; + fmt2.fmt.pix.pixelformat = desc2.pixelformat; + if (0 != drv(inode,file,VIDIOC_TRY_FMT, &fmt2)) + goto done; + + *maxw = fmt2.fmt.pix.width; + *maxh = fmt2.fmt.pix.height; + + done: + return 0; +} + +/* ----------------------------------------------------------------- */ + +/* + * This function is exported. + */ +int +v4l_compat_translate_ioctl(struct inode *inode, + struct file *file, + int cmd, + void *arg, + v4l2_kioctl drv) +{ + struct v4l2_capability *cap2 = NULL; + struct v4l2_format *fmt2 = NULL; + enum v4l2_buf_type captype = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + struct v4l2_framebuffer fbuf2; + struct v4l2_input input2; + struct v4l2_tuner tun2; + struct v4l2_standard std2; + struct v4l2_frequency freq2; + struct v4l2_audio aud2; + struct v4l2_queryctrl qctrl2; + struct v4l2_buffer buf2; + v4l2_std_id sid; + int i, err = 0; + + switch (cmd) { + case VIDIOCGCAP: /* capability */ + { + struct video_capability *cap = arg; + + cap2 = kmalloc(sizeof(*cap2),GFP_KERNEL); + memset(cap, 0, sizeof(*cap)); + memset(cap2, 0, sizeof(*cap2)); + memset(&fbuf2, 0, sizeof(fbuf2)); + + err = drv(inode, file, VIDIOC_QUERYCAP, cap2); + if (err < 0) { + dprintk("VIDIOCGCAP / VIDIOC_QUERYCAP: %d\n",err); + break; + } + if (cap2->capabilities & V4L2_CAP_VIDEO_OVERLAY) { + err = drv(inode, file, VIDIOC_G_FBUF, &fbuf2); + if (err < 0) { + dprintk("VIDIOCGCAP / VIDIOC_G_FBUF: %d\n",err); + memset(&fbuf2, 0, sizeof(fbuf2)); + } + err = 0; + } + + memcpy(cap->name, cap2->card, + min(sizeof(cap->name), sizeof(cap2->card))); + cap->name[sizeof(cap->name) - 1] = 0; + if (cap2->capabilities & V4L2_CAP_VIDEO_CAPTURE) + cap->type |= VID_TYPE_CAPTURE; + if (cap2->capabilities & V4L2_CAP_TUNER) + cap->type |= VID_TYPE_TUNER; + if (cap2->capabilities & V4L2_CAP_VBI_CAPTURE) + cap->type |= VID_TYPE_TELETEXT; + if (cap2->capabilities & V4L2_CAP_VIDEO_OVERLAY) + cap->type |= VID_TYPE_OVERLAY; + if (fbuf2.capability & V4L2_FBUF_CAP_LIST_CLIPPING) + cap->type |= VID_TYPE_CLIPPING; + + cap->channels = count_inputs(inode,file,drv); + check_size(inode,file,drv, + &cap->maxwidth,&cap->maxheight); + cap->audios = 0; /* FIXME */ + cap->minwidth = 48; /* FIXME */ + cap->minheight = 32; /* FIXME */ + break; + } + case VIDIOCGFBUF: /* get frame buffer */ + { + struct video_buffer *buffer = arg; + + err = drv(inode, file, VIDIOC_G_FBUF, &fbuf2); + if (err < 0) { + dprintk("VIDIOCGFBUF / VIDIOC_G_FBUF: %d\n",err); + break; + } + buffer->base = fbuf2.base; + buffer->height = fbuf2.fmt.height; + buffer->width = fbuf2.fmt.width; + + switch (fbuf2.fmt.pixelformat) { + case V4L2_PIX_FMT_RGB332: + buffer->depth = 8; + break; + case V4L2_PIX_FMT_RGB555: + buffer->depth = 15; + break; + case V4L2_PIX_FMT_RGB565: + buffer->depth = 16; + break; + case V4L2_PIX_FMT_BGR24: + buffer->depth = 24; + break; + case V4L2_PIX_FMT_BGR32: + buffer->depth = 32; + break; + default: + buffer->depth = 0; + } + if (0 != fbuf2.fmt.bytesperline) + buffer->bytesperline = fbuf2.fmt.bytesperline; + else { + buffer->bytesperline = + (buffer->width * buffer->depth + 7) & 7; + buffer->bytesperline >>= 3; + } + break; + } + case VIDIOCSFBUF: /* set frame buffer */ + { + struct video_buffer *buffer = arg; + + memset(&fbuf2, 0, sizeof(fbuf2)); + fbuf2.base = buffer->base; + fbuf2.fmt.height = buffer->height; + fbuf2.fmt.width = buffer->width; + switch (buffer->depth) { + case 8: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB332; + break; + case 15: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB555; + break; + case 16: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB565; + break; + case 24: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_BGR24; + break; + case 32: + fbuf2.fmt.pixelformat = V4L2_PIX_FMT_BGR32; + break; + } + fbuf2.fmt.bytesperline = buffer->bytesperline; + err = drv(inode, file, VIDIOC_S_FBUF, &fbuf2); + if (err < 0) + dprintk("VIDIOCSFBUF / VIDIOC_S_FBUF: %d\n",err); + break; + } + case VIDIOCGWIN: /* get window or capture dimensions */ + { + struct video_window *win = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(win,0,sizeof(*win)); + memset(fmt2,0,sizeof(*fmt2)); + + fmt2->type = V4L2_BUF_TYPE_VIDEO_OVERLAY; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCGWIN / VIDIOC_G_WIN: %d\n",err); + if (err == 0) { + win->x = fmt2->fmt.win.w.left; + win->y = fmt2->fmt.win.w.top; + win->width = fmt2->fmt.win.w.width; + win->height = fmt2->fmt.win.w.height; + win->chromakey = fmt2->fmt.win.chromakey; + win->clips = NULL; + win->clipcount = 0; + break; + } + + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCGWIN / VIDIOC_G_FMT: %d\n",err); + break; + } + win->x = 0; + win->y = 0; + win->width = fmt2->fmt.pix.width; + win->height = fmt2->fmt.pix.height; + win->chromakey = 0; + win->clips = NULL; + win->clipcount = 0; + break; + } + case VIDIOCSWIN: /* set window and/or capture dimensions */ + { + struct video_window *win = arg; + int err1,err2; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + drv(inode, file, VIDIOC_STREAMOFF, &fmt2->type); + err1 = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err1 < 0) + dprintk("VIDIOCSWIN / VIDIOC_G_FMT: %d\n",err); + if (err1 == 0) { + fmt2->fmt.pix.width = win->width; + fmt2->fmt.pix.height = win->height; + fmt2->fmt.pix.field = V4L2_FIELD_ANY; + fmt2->fmt.pix.bytesperline = 0; + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSWIN / VIDIOC_S_FMT #1: %d\n", + err); + win->width = fmt2->fmt.pix.width; + win->height = fmt2->fmt.pix.height; + } + + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_OVERLAY; + fmt2->fmt.win.w.left = win->x; + fmt2->fmt.win.w.top = win->y; + fmt2->fmt.win.w.width = win->width; + fmt2->fmt.win.w.height = win->height; + fmt2->fmt.win.chromakey = win->chromakey; + fmt2->fmt.win.clips = (void __user *)win->clips; + fmt2->fmt.win.clipcount = win->clipcount; + err2 = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err2 < 0) + dprintk("VIDIOCSWIN / VIDIOC_S_FMT #2: %d\n",err); + + if (err1 != 0 && err2 != 0) + err = err1; + break; + } + case VIDIOCCAPTURE: /* turn on/off preview */ + { + int *on = arg; + + if (0 == *on) { + /* dirty hack time. But v4l1 has no STREAMOFF + * equivalent in the API, and this one at + * least comes close ... */ + drv(inode, file, VIDIOC_STREAMOFF, &captype); + } + err = drv(inode, file, VIDIOC_OVERLAY, arg); + if (err < 0) + dprintk("VIDIOCCAPTURE / VIDIOC_PREVIEW: %d\n",err); + break; + } + case VIDIOCGCHAN: /* get input information */ + { + struct video_channel *chan = arg; + + memset(&input2,0,sizeof(input2)); + input2.index = chan->channel; + err = drv(inode, file, VIDIOC_ENUMINPUT, &input2); + if (err < 0) { + dprintk("VIDIOCGCHAN / VIDIOC_ENUMINPUT: " + "channel=%d err=%d\n",chan->channel,err); + break; + } + chan->channel = input2.index; + memcpy(chan->name, input2.name, + min(sizeof(chan->name), sizeof(input2.name))); + chan->name[sizeof(chan->name) - 1] = 0; + chan->tuners = (input2.type == V4L2_INPUT_TYPE_TUNER) ? 1 : 0; + chan->flags = (chan->tuners) ? VIDEO_VC_TUNER : 0; + switch (input2.type) { + case V4L2_INPUT_TYPE_TUNER: + chan->type = VIDEO_TYPE_TV; + break; + default: + case V4L2_INPUT_TYPE_CAMERA: + chan->type = VIDEO_TYPE_CAMERA; + break; + } + chan->norm = 0; + err = drv(inode, file, VIDIOC_G_STD, &sid); + if (err < 0) + dprintk("VIDIOCGCHAN / VIDIOC_G_STD: %d\n",err); + if (err == 0) { + if (sid & V4L2_STD_PAL) + chan->norm = VIDEO_MODE_PAL; + if (sid & V4L2_STD_NTSC) + chan->norm = VIDEO_MODE_NTSC; + if (sid & V4L2_STD_SECAM) + chan->norm = VIDEO_MODE_SECAM; + } + break; + } + case VIDIOCSCHAN: /* set input */ + { + struct video_channel *chan = arg; + + sid = 0; + err = drv(inode, file, VIDIOC_S_INPUT, &chan->channel); + if (err < 0) + dprintk("VIDIOCSCHAN / VIDIOC_S_INPUT: %d\n",err); + switch (chan->norm) { + case VIDEO_MODE_PAL: + sid = V4L2_STD_PAL; + break; + case VIDEO_MODE_NTSC: + sid = V4L2_STD_NTSC; + break; + case VIDEO_MODE_SECAM: + sid = V4L2_STD_SECAM; + break; + } + if (0 != sid) { + err = drv(inode, file, VIDIOC_S_STD, &sid); + if (err < 0) + dprintk("VIDIOCSCHAN / VIDIOC_S_STD: %d\n",err); + } + break; + } + case VIDIOCGPICT: /* get tone controls & partial capture format */ + { + struct video_picture *pict = arg; + + pict->brightness = get_v4l_control(inode, file, + V4L2_CID_BRIGHTNESS,drv); + pict->hue = get_v4l_control(inode, file, + V4L2_CID_HUE, drv); + pict->contrast = get_v4l_control(inode, file, + V4L2_CID_CONTRAST, drv); + pict->colour = get_v4l_control(inode, file, + V4L2_CID_SATURATION, drv); + pict->whiteness = get_v4l_control(inode, file, + V4L2_CID_WHITENESS, drv); + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCGPICT / VIDIOC_G_FMT: %d\n",err); + break; + } +#if 0 /* FIXME */ + pict->depth = fmt2->fmt.pix.depth; +#endif + pict->palette = pixelformat_to_palette( + fmt2->fmt.pix.pixelformat); + break; + } + case VIDIOCSPICT: /* set tone controls & partial capture format */ + { + struct video_picture *pict = arg; + + set_v4l_control(inode, file, + V4L2_CID_BRIGHTNESS, pict->brightness, drv); + set_v4l_control(inode, file, + V4L2_CID_HUE, pict->hue, drv); + set_v4l_control(inode, file, + V4L2_CID_CONTRAST, pict->contrast, drv); + set_v4l_control(inode, file, + V4L2_CID_SATURATION, pict->colour, drv); + set_v4l_control(inode, file, + V4L2_CID_WHITENESS, pict->whiteness, drv); + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2,0,sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_G_FMT: %d\n",err); + if (fmt2->fmt.pix.pixelformat != + palette_to_pixelformat(pict->palette)) { + fmt2->fmt.pix.pixelformat = palette_to_pixelformat( + pict->palette); + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_S_FMT: %d\n",err); + } + + err = drv(inode, file, VIDIOC_G_FBUF, &fbuf2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_G_FBUF: %d\n",err); + if (fbuf2.fmt.pixelformat != + palette_to_pixelformat(pict->palette)) { + fbuf2.fmt.pixelformat = palette_to_pixelformat( + pict->palette); + err = drv(inode, file, VIDIOC_S_FBUF, &fbuf2); + if (err < 0) + dprintk("VIDIOCSPICT / VIDIOC_S_FBUF: %d\n",err); + err = 0; /* likely fails for non-root */ + } + break; + } + case VIDIOCGTUNER: /* get tuner information */ + { + struct video_tuner *tun = arg; + + memset(&tun2,0,sizeof(tun2)); + err = drv(inode, file, VIDIOC_G_TUNER, &tun2); + if (err < 0) { + dprintk("VIDIOCGTUNER / VIDIOC_G_TUNER: %d\n",err); + break; + } + memcpy(tun->name, tun2.name, + min(sizeof(tun->name), sizeof(tun2.name))); + tun->name[sizeof(tun->name) - 1] = 0; + tun->rangelow = tun2.rangelow; + tun->rangehigh = tun2.rangehigh; + tun->flags = 0; + tun->mode = VIDEO_MODE_AUTO; + + for (i = 0; i < 64; i++) { + memset(&std2,0,sizeof(std2)); + std2.index = i; + if (0 != drv(inode, file, VIDIOC_ENUMSTD, &std2)) + break; + if (std2.id & V4L2_STD_PAL) + tun->flags |= VIDEO_TUNER_PAL; + if (std2.id & V4L2_STD_NTSC) + tun->flags |= VIDEO_TUNER_NTSC; + if (std2.id & V4L2_STD_SECAM) + tun->flags |= VIDEO_TUNER_SECAM; + } + + err = drv(inode, file, VIDIOC_G_STD, &sid); + if (err < 0) + dprintk("VIDIOCGTUNER / VIDIOC_G_STD: %d\n",err); + if (err == 0) { + if (sid & V4L2_STD_PAL) + tun->mode = VIDEO_MODE_PAL; + if (sid & V4L2_STD_NTSC) + tun->mode = VIDEO_MODE_NTSC; + if (sid & V4L2_STD_SECAM) + tun->mode = VIDEO_MODE_SECAM; + } + + if (tun2.capability & V4L2_TUNER_CAP_LOW) + tun->flags |= VIDEO_TUNER_LOW; + if (tun2.rxsubchans & V4L2_TUNER_SUB_STEREO) + tun->flags |= VIDEO_TUNER_STEREO_ON; + tun->signal = tun2.signal; + break; + } + case VIDIOCSTUNER: /* select a tuner input */ + { +#if 0 /* FIXME */ + err = drv(inode, file, VIDIOC_S_INPUT, &i); + if (err < 0) + dprintk("VIDIOCSTUNER / VIDIOC_S_INPUT: %d\n",err); +#else + err = 0; +#endif + break; + } + case VIDIOCGFREQ: /* get frequency */ + { + int *freq = arg; + + freq2.tuner = 0; + err = drv(inode, file, VIDIOC_G_FREQUENCY, &freq2); + if (err < 0) + dprintk("VIDIOCGFREQ / VIDIOC_G_FREQUENCY: %d\n",err); + if (0 == err) + *freq = freq2.frequency; + break; + } + case VIDIOCSFREQ: /* set frequency */ + { + int *freq = arg; + + freq2.tuner = 0; + drv(inode, file, VIDIOC_G_FREQUENCY, &freq2); + freq2.frequency = *freq; + err = drv(inode, file, VIDIOC_S_FREQUENCY, &freq2); + if (err < 0) + dprintk("VIDIOCSFREQ / VIDIOC_S_FREQUENCY: %d\n",err); + break; + } + case VIDIOCGAUDIO: /* get audio properties/controls */ + { + struct video_audio *aud = arg; + + err = drv(inode, file, VIDIOC_G_AUDIO, &aud2); + if (err < 0) { + dprintk("VIDIOCGAUDIO / VIDIOC_G_AUDIO: %d\n",err); + break; + } + memcpy(aud->name, aud2.name, + min(sizeof(aud->name), sizeof(aud2.name))); + aud->name[sizeof(aud->name) - 1] = 0; + aud->audio = aud2.index; + aud->flags = 0; + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_VOLUME, drv); + if (i >= 0) { + aud->volume = i; + aud->flags |= VIDEO_AUDIO_VOLUME; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_BASS, drv); + if (i >= 0) { + aud->bass = i; + aud->flags |= VIDEO_AUDIO_BASS; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_TREBLE, drv); + if (i >= 0) { + aud->treble = i; + aud->flags |= VIDEO_AUDIO_TREBLE; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_BALANCE, drv); + if (i >= 0) { + aud->balance = i; + aud->flags |= VIDEO_AUDIO_BALANCE; + } + i = get_v4l_control(inode, file, V4L2_CID_AUDIO_MUTE, drv); + if (i >= 0) { + if (i) + aud->flags |= VIDEO_AUDIO_MUTE; + aud->flags |= VIDEO_AUDIO_MUTABLE; + } + aud->step = 1; + qctrl2.id = V4L2_CID_AUDIO_VOLUME; + if (drv(inode, file, VIDIOC_QUERYCTRL, &qctrl2) == 0 && + !(qctrl2.flags & V4L2_CTRL_FLAG_DISABLED)) + aud->step = qctrl2.step; + aud->mode = 0; + err = drv(inode, file, VIDIOC_G_TUNER, &tun2); + if (err < 0) { + dprintk("VIDIOCGAUDIO / VIDIOC_G_TUNER: %d\n",err); + err = 0; + break; + } + if (tun2.rxsubchans & V4L2_TUNER_SUB_LANG2) + aud->mode = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + else if (tun2.rxsubchans & V4L2_TUNER_SUB_STEREO) + aud->mode = VIDEO_SOUND_STEREO; + else if (tun2.rxsubchans & V4L2_TUNER_SUB_MONO) + aud->mode = VIDEO_SOUND_MONO; + break; + } + case VIDIOCSAUDIO: /* set audio controls */ + { + struct video_audio *aud = arg; + + memset(&aud2,0,sizeof(aud2)); + memset(&tun2,0,sizeof(tun2)); + + aud2.index = aud->audio; + err = drv(inode, file, VIDIOC_S_AUDIO, &aud2); + if (err < 0) { + dprintk("VIDIOCSAUDIO / VIDIOC_S_AUDIO: %d\n",err); + break; + } + + set_v4l_control(inode, file, V4L2_CID_AUDIO_VOLUME, + aud->volume, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_BASS, + aud->bass, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_TREBLE, + aud->treble, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_BALANCE, + aud->balance, drv); + set_v4l_control(inode, file, V4L2_CID_AUDIO_MUTE, + !!(aud->flags & VIDEO_AUDIO_MUTE), drv); + + err = drv(inode, file, VIDIOC_G_TUNER, &tun2); + if (err < 0) + dprintk("VIDIOCSAUDIO / VIDIOC_G_TUNER: %d\n",err); + if (err == 0) { + switch (aud->mode) { + default: + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_LANG1: + tun2.audmode = V4L2_TUNER_MODE_MONO; + break; + case VIDEO_SOUND_STEREO: + tun2.audmode = V4L2_TUNER_MODE_STEREO; + break; + case VIDEO_SOUND_LANG2: + tun2.audmode = V4L2_TUNER_MODE_LANG2; + break; + } + err = drv(inode, file, VIDIOC_S_TUNER, &tun2); + if (err < 0) + dprintk("VIDIOCSAUDIO / VIDIOC_S_TUNER: %d\n",err); + } + err = 0; + break; + } +#if 0 + case VIDIOCGMBUF: + /* v4l2 drivers must implement that themself. The + mmap() differences can't be translated fully + transparent, thus there is no point to try that */ +#endif + case VIDIOCMCAPTURE: /* capture a frame */ + { + struct video_mmap *mm = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(&buf2,0,sizeof(buf2)); + memset(fmt2,0,sizeof(*fmt2)); + + fmt2->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_G_FMT: %d\n",err); + break; + } + if (mm->width != fmt2->fmt.pix.width || + mm->height != fmt2->fmt.pix.height || + palette_to_pixelformat(mm->format) != + fmt2->fmt.pix.pixelformat) + {/* New capture format... */ + fmt2->fmt.pix.width = mm->width; + fmt2->fmt.pix.height = mm->height; + fmt2->fmt.pix.pixelformat = + palette_to_pixelformat(mm->format); + fmt2->fmt.pix.field = V4L2_FIELD_ANY; + fmt2->fmt.pix.bytesperline = 0; + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_S_FMT: %d\n",err); + break; + } + } + buf2.index = mm->frame; + buf2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_QUERYBUF, &buf2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_QUERYBUF: %d\n",err); + break; + } + err = drv(inode, file, VIDIOC_QBUF, &buf2); + if (err < 0) { + dprintk("VIDIOCMCAPTURE / VIDIOC_QBUF: %d\n",err); + break; + } + err = drv(inode, file, VIDIOC_STREAMON, &captype); + if (err < 0) + dprintk("VIDIOCMCAPTURE / VIDIOC_STREAMON: %d\n",err); + break; + } + case VIDIOCSYNC: /* wait for a frame */ + { + int *i = arg; + + buf2.index = *i; + buf2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = drv(inode, file, VIDIOC_QUERYBUF, &buf2); + if (err < 0) { + /* No such buffer */ + dprintk("VIDIOCSYNC / VIDIOC_QUERYBUF: %d\n",err); + break; + } + if (!(buf2.flags & V4L2_BUF_FLAG_MAPPED)) { + /* Buffer is not mapped */ + err = -EINVAL; + break; + } + + /* make sure capture actually runs so we don't block forever */ + err = drv(inode, file, VIDIOC_STREAMON, &captype); + if (err < 0) { + dprintk("VIDIOCSYNC / VIDIOC_STREAMON: %d\n",err); + break; + } + + /* Loop as long as the buffer is queued, but not done */ + while ((buf2.flags & + (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) + == V4L2_BUF_FLAG_QUEUED) + { + err = poll_one(file); + if (err < 0 || /* error or sleep was interrupted */ + err == 0) /* timeout? Shouldn't occur. */ + break; + err = drv(inode, file, VIDIOC_QUERYBUF, &buf2); + if (err < 0) + dprintk("VIDIOCSYNC / VIDIOC_QUERYBUF: %d\n",err); + } + if (!(buf2.flags & V4L2_BUF_FLAG_DONE)) /* not done */ + break; + do { + err = drv(inode, file, VIDIOC_DQBUF, &buf2); + if (err < 0) + dprintk("VIDIOCSYNC / VIDIOC_DQBUF: %d\n",err); + } while (err == 0 && buf2.index != *i); + break; + } + + case VIDIOCGVBIFMT: /* query VBI data capture format */ + { + struct vbi_format *fmt = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2, 0, sizeof(*fmt2)); + fmt2->type = V4L2_BUF_TYPE_VBI_CAPTURE; + + err = drv(inode, file, VIDIOC_G_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCGVBIFMT / VIDIOC_G_FMT: %d\n", err); + break; + } + memset(fmt, 0, sizeof(*fmt)); + fmt->samples_per_line = fmt2->fmt.vbi.samples_per_line; + fmt->sampling_rate = fmt2->fmt.vbi.sampling_rate; + fmt->sample_format = VIDEO_PALETTE_RAW; + fmt->start[0] = fmt2->fmt.vbi.start[0]; + fmt->count[0] = fmt2->fmt.vbi.count[0]; + fmt->start[1] = fmt2->fmt.vbi.start[1]; + fmt->count[1] = fmt2->fmt.vbi.count[1]; + fmt->flags = fmt2->fmt.vbi.flags & 0x03; + break; + } + case VIDIOCSVBIFMT: + { + struct vbi_format *fmt = arg; + + fmt2 = kmalloc(sizeof(*fmt2),GFP_KERNEL); + memset(fmt2, 0, sizeof(*fmt2)); + + fmt2->type = V4L2_BUF_TYPE_VBI_CAPTURE; + fmt2->fmt.vbi.samples_per_line = fmt->samples_per_line; + fmt2->fmt.vbi.sampling_rate = fmt->sampling_rate; + fmt2->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; + fmt2->fmt.vbi.start[0] = fmt->start[0]; + fmt2->fmt.vbi.count[0] = fmt->count[0]; + fmt2->fmt.vbi.start[1] = fmt->start[1]; + fmt2->fmt.vbi.count[1] = fmt->count[1]; + fmt2->fmt.vbi.flags = fmt->flags; + err = drv(inode, file, VIDIOC_TRY_FMT, fmt2); + if (err < 0) { + dprintk("VIDIOCSVBIFMT / VIDIOC_TRY_FMT: %d\n", err); + break; + } + + if (fmt2->fmt.vbi.samples_per_line != fmt->samples_per_line || + fmt2->fmt.vbi.sampling_rate != fmt->sampling_rate || + VIDEO_PALETTE_RAW != fmt->sample_format || + fmt2->fmt.vbi.start[0] != fmt->start[0] || + fmt2->fmt.vbi.count[0] != fmt->count[0] || + fmt2->fmt.vbi.start[1] != fmt->start[1] || + fmt2->fmt.vbi.count[1] != fmt->count[1] || + fmt2->fmt.vbi.flags != fmt->flags) { + err = -EINVAL; + break; + } + err = drv(inode, file, VIDIOC_S_FMT, fmt2); + if (err < 0) + dprintk("VIDIOCSVBIFMT / VIDIOC_S_FMT: %d\n", err); + break; + } + + default: + err = -ENOIOCTLCMD; + break; + } + + if (cap2) + kfree(cap2); + if (fmt2) + kfree(fmt2); + return err; +} + +EXPORT_SYMBOL(v4l_compat_translate_ioctl); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/v4l2-common.c b/drivers/media/video/v4l2-common.c new file mode 100644 index 000000000000..b5e0cf3448f4 --- /dev/null +++ b/drivers/media/video/v4l2-common.c @@ -0,0 +1,282 @@ +/* + * Video for Linux Two + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * This file replaces the videodev.c file that comes with the + * regular kernel distribution. + * + * 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. + * + * Author: Bill Dirks <bdirks@pacbell.net> + * based on code by Alan Cox, <alan@cymru.net> + * + */ + +/* + * Video capture interface for Linux + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * 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. + * + * Author: Alan Cox, <alan@redhat.com> + * + * Fixes: + */ + +/* + * Video4linux 1/2 integration by Justin Schoeman + * <justin@suntiger.ee.up.ac.za> + * 2.4 PROCFS support ported from 2.4 kernels by + * Iñaki García Etxebarria <garetxe@euskalnet.net> + * Makefile fix by "W. Michael Petullo" <mike@flyn.org> + * 2.4 devfs support ported from 2.4 kernels by + * Dan Merillat <dan@merillat.org> + * Added Gerd Knorrs v4l1 enhancements (Justin Schoeman) + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/div64.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +#if defined(CONFIG_UST) || defined(CONFIG_UST_MODULE) +#include <linux/ust.h> +#endif + + +#include <linux/videodev.h> + +MODULE_AUTHOR("Bill Dirks, Justin Schoeman, Gerd Knorr"); +MODULE_DESCRIPTION("misc helper functions for v4l2 device drivers"); +MODULE_LICENSE("GPL"); + +/* + * + * V 4 L 2 D R I V E R H E L P E R A P I + * + */ + +/* + * Video Standard Operations (contributed by Michael Schimek) + */ + +#if 0 /* seems to have no users */ +/* This is the recommended method to deal with the framerate fields. More + sophisticated drivers will access the fields directly. */ +unsigned int +v4l2_video_std_fps(struct v4l2_standard *vs) +{ + if (vs->frameperiod.numerator > 0) + return (((vs->frameperiod.denominator << 8) / + vs->frameperiod.numerator) + + (1 << 7)) / (1 << 8); + return 0; +} +EXPORT_SYMBOL(v4l2_video_std_fps); +#endif + +/* Fill in the fields of a v4l2_standard structure according to the + 'id' and 'transmission' parameters. Returns negative on error. */ +int v4l2_video_std_construct(struct v4l2_standard *vs, + int id, char *name) +{ + u32 index = vs->index; + + memset(vs, 0, sizeof(struct v4l2_standard)); + vs->index = index; + vs->id = id; + if (id & (V4L2_STD_NTSC | V4L2_STD_PAL_M)) { + vs->frameperiod.numerator = 1001; + vs->frameperiod.denominator = 30000; + vs->framelines = 525; + } else { + vs->frameperiod.numerator = 1; + vs->frameperiod.denominator = 25; + vs->framelines = 625; + } + strlcpy(vs->name,name,sizeof(vs->name)); + return 0; +} + + +/* ----------------------------------------------------------------- */ +/* priority handling */ + +#define V4L2_PRIO_VALID(val) (val == V4L2_PRIORITY_BACKGROUND || \ + val == V4L2_PRIORITY_INTERACTIVE || \ + val == V4L2_PRIORITY_RECORD) + +int v4l2_prio_init(struct v4l2_prio_state *global) +{ + memset(global,0,sizeof(*global)); + return 0; +} + +int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, + enum v4l2_priority new) +{ + if (!V4L2_PRIO_VALID(new)) + return -EINVAL; + if (*local == new) + return 0; + + atomic_inc(&global->prios[new]); + if (V4L2_PRIO_VALID(*local)) + atomic_dec(&global->prios[*local]); + *local = new; + return 0; +} + +int v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + return v4l2_prio_change(global,local,V4L2_PRIORITY_DEFAULT); +} + +int v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + if (V4L2_PRIO_VALID(*local)) + atomic_dec(&global->prios[*local]); + return 0; +} + +enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global) +{ + if (atomic_read(&global->prios[V4L2_PRIORITY_RECORD]) > 0) + return V4L2_PRIORITY_RECORD; + if (atomic_read(&global->prios[V4L2_PRIORITY_INTERACTIVE]) > 0) + return V4L2_PRIORITY_INTERACTIVE; + if (atomic_read(&global->prios[V4L2_PRIORITY_BACKGROUND]) > 0) + return V4L2_PRIORITY_BACKGROUND; + return V4L2_PRIORITY_UNSET; +} + +int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority *local) +{ + if (*local < v4l2_prio_max(global)) + return -EBUSY; + return 0; +} + + +/* ----------------------------------------------------------------- */ +/* some arrays for pretty-printing debug messages */ + +char *v4l2_field_names[] = { + [V4L2_FIELD_ANY] = "any", + [V4L2_FIELD_NONE] = "none", + [V4L2_FIELD_TOP] = "top", + [V4L2_FIELD_BOTTOM] = "bottom", + [V4L2_FIELD_INTERLACED] = "interlaced", + [V4L2_FIELD_SEQ_TB] = "seq-tb", + [V4L2_FIELD_SEQ_BT] = "seq-bt", + [V4L2_FIELD_ALTERNATE] = "alternate", +}; + +char *v4l2_type_names[] = { + [V4L2_BUF_TYPE_VIDEO_CAPTURE] = "video-cap", + [V4L2_BUF_TYPE_VIDEO_OVERLAY] = "video-over", + [V4L2_BUF_TYPE_VIDEO_OUTPUT] = "video-out", + [V4L2_BUF_TYPE_VBI_CAPTURE] = "vbi-cap", + [V4L2_BUF_TYPE_VBI_OUTPUT] = "vbi-out", +}; + +char *v4l2_ioctl_names[256] = { +#if __GNUC__ >= 3 + [0 ... 255] = "UNKNOWN", +#endif + [_IOC_NR(VIDIOC_QUERYCAP)] = "VIDIOC_QUERYCAP", + [_IOC_NR(VIDIOC_RESERVED)] = "VIDIOC_RESERVED", + [_IOC_NR(VIDIOC_ENUM_FMT)] = "VIDIOC_ENUM_FMT", + [_IOC_NR(VIDIOC_G_FMT)] = "VIDIOC_G_FMT", + [_IOC_NR(VIDIOC_S_FMT)] = "VIDIOC_S_FMT", +#if 0 + [_IOC_NR(VIDIOC_G_COMP)] = "VIDIOC_G_COMP", + [_IOC_NR(VIDIOC_S_COMP)] = "VIDIOC_S_COMP", +#endif + [_IOC_NR(VIDIOC_REQBUFS)] = "VIDIOC_REQBUFS", + [_IOC_NR(VIDIOC_QUERYBUF)] = "VIDIOC_QUERYBUF", + [_IOC_NR(VIDIOC_G_FBUF)] = "VIDIOC_G_FBUF", + [_IOC_NR(VIDIOC_S_FBUF)] = "VIDIOC_S_FBUF", + [_IOC_NR(VIDIOC_OVERLAY)] = "VIDIOC_OVERLAY", + [_IOC_NR(VIDIOC_QBUF)] = "VIDIOC_QBUF", + [_IOC_NR(VIDIOC_DQBUF)] = "VIDIOC_DQBUF", + [_IOC_NR(VIDIOC_STREAMON)] = "VIDIOC_STREAMON", + [_IOC_NR(VIDIOC_STREAMOFF)] = "VIDIOC_STREAMOFF", + [_IOC_NR(VIDIOC_G_PARM)] = "VIDIOC_G_PARM", + [_IOC_NR(VIDIOC_S_PARM)] = "VIDIOC_S_PARM", + [_IOC_NR(VIDIOC_G_STD)] = "VIDIOC_G_STD", + [_IOC_NR(VIDIOC_S_STD)] = "VIDIOC_S_STD", + [_IOC_NR(VIDIOC_ENUMSTD)] = "VIDIOC_ENUMSTD", + [_IOC_NR(VIDIOC_ENUMINPUT)] = "VIDIOC_ENUMINPUT", + [_IOC_NR(VIDIOC_G_CTRL)] = "VIDIOC_G_CTRL", + [_IOC_NR(VIDIOC_S_CTRL)] = "VIDIOC_S_CTRL", + [_IOC_NR(VIDIOC_G_TUNER)] = "VIDIOC_G_TUNER", + [_IOC_NR(VIDIOC_S_TUNER)] = "VIDIOC_S_TUNER", + [_IOC_NR(VIDIOC_G_AUDIO)] = "VIDIOC_G_AUDIO", + [_IOC_NR(VIDIOC_S_AUDIO)] = "VIDIOC_S_AUDIO", + [_IOC_NR(VIDIOC_QUERYCTRL)] = "VIDIOC_QUERYCTRL", + [_IOC_NR(VIDIOC_QUERYMENU)] = "VIDIOC_QUERYMENU", + [_IOC_NR(VIDIOC_G_INPUT)] = "VIDIOC_G_INPUT", + [_IOC_NR(VIDIOC_S_INPUT)] = "VIDIOC_S_INPUT", + [_IOC_NR(VIDIOC_G_OUTPUT)] = "VIDIOC_G_OUTPUT", + [_IOC_NR(VIDIOC_S_OUTPUT)] = "VIDIOC_S_OUTPUT", + [_IOC_NR(VIDIOC_ENUMOUTPUT)] = "VIDIOC_ENUMOUTPUT", + [_IOC_NR(VIDIOC_G_AUDOUT)] = "VIDIOC_G_AUDOUT", + [_IOC_NR(VIDIOC_S_AUDOUT)] = "VIDIOC_S_AUDOUT", + [_IOC_NR(VIDIOC_G_MODULATOR)] = "VIDIOC_G_MODULATOR", + [_IOC_NR(VIDIOC_S_MODULATOR)] = "VIDIOC_S_MODULATOR", + [_IOC_NR(VIDIOC_G_FREQUENCY)] = "VIDIOC_G_FREQUENCY", + [_IOC_NR(VIDIOC_S_FREQUENCY)] = "VIDIOC_S_FREQUENCY", + [_IOC_NR(VIDIOC_CROPCAP)] = "VIDIOC_CROPCAP", + [_IOC_NR(VIDIOC_G_CROP)] = "VIDIOC_G_CROP", + [_IOC_NR(VIDIOC_S_CROP)] = "VIDIOC_S_CROP", + [_IOC_NR(VIDIOC_G_JPEGCOMP)] = "VIDIOC_G_JPEGCOMP", + [_IOC_NR(VIDIOC_S_JPEGCOMP)] = "VIDIOC_S_JPEGCOMP", + [_IOC_NR(VIDIOC_QUERYSTD)] = "VIDIOC_QUERYSTD", + [_IOC_NR(VIDIOC_TRY_FMT)] = "VIDIOC_TRY_FMT", +}; + +/* ----------------------------------------------------------------- */ + +EXPORT_SYMBOL(v4l2_video_std_construct); + +EXPORT_SYMBOL(v4l2_prio_init); +EXPORT_SYMBOL(v4l2_prio_change); +EXPORT_SYMBOL(v4l2_prio_open); +EXPORT_SYMBOL(v4l2_prio_close); +EXPORT_SYMBOL(v4l2_prio_max); +EXPORT_SYMBOL(v4l2_prio_check); + +EXPORT_SYMBOL(v4l2_field_names); +EXPORT_SYMBOL(v4l2_type_names); +EXPORT_SYMBOL(v4l2_ioctl_names); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/video-buf-dvb.c b/drivers/media/video/video-buf-dvb.c new file mode 100644 index 000000000000..31cc4ed9b747 --- /dev/null +++ b/drivers/media/video/video-buf-dvb.c @@ -0,0 +1,251 @@ +/* + * $Id: video-buf-dvb.c,v 1.7 2004/12/09 12:51:35 kraxel Exp $ + * + * some helper function for simple DVB cards which simply DMA the + * complete transport stream and let the computer sort everything else + * (i.e. we are using the software demux, ...). Also uses the + * video-buf to manage DMA buffers. + * + * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs] + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kthread.h> +#include <linux/file.h> +#include <linux/suspend.h> + +#include <media/video-buf.h> +#include <media/video-buf-dvb.h> + +/* ------------------------------------------------------------------ */ + +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages"); + +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG "%s/dvb: " fmt, dvb->name , ## arg) + +/* ------------------------------------------------------------------ */ + +static int videobuf_dvb_thread(void *data) +{ + struct videobuf_dvb *dvb = data; + struct videobuf_buffer *buf; + unsigned long flags; + int err; + + dprintk("dvb thread started\n"); + videobuf_read_start(&dvb->dvbq); + + for (;;) { + /* fetch next buffer */ + buf = list_entry(dvb->dvbq.stream.next, + struct videobuf_buffer, stream); + list_del(&buf->stream); + err = videobuf_waiton(buf,0,1); + BUG_ON(0 != err); + + /* no more feeds left or stop_feed() asked us to quit */ + if (0 == dvb->nfeeds) + break; + if (kthread_should_stop()) + break; + if (current->flags & PF_FREEZE) + refrigerator(PF_FREEZE); + + /* feed buffer data to demux */ + if (buf->state == STATE_DONE) + dvb_dmx_swfilter(&dvb->demux, buf->dma.vmalloc, + buf->size); + + /* requeue buffer */ + list_add_tail(&buf->stream,&dvb->dvbq.stream); + spin_lock_irqsave(dvb->dvbq.irqlock,flags); + dvb->dvbq.ops->buf_queue(&dvb->dvbq,buf); + spin_unlock_irqrestore(dvb->dvbq.irqlock,flags); + } + + videobuf_read_stop(&dvb->dvbq); + dprintk("dvb thread stopped\n"); + + /* Hmm, linux becomes *very* unhappy without this ... */ + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + return 0; +} + +static int videobuf_dvb_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct videobuf_dvb *dvb = demux->priv; + int rc; + + if (!demux->dmx.frontend) + return -EINVAL; + + down(&dvb->lock); + dvb->nfeeds++; + rc = dvb->nfeeds; + + if (NULL != dvb->thread) + goto out; + dvb->thread = kthread_run(videobuf_dvb_thread, + dvb, "%s dvb", dvb->name); + if (IS_ERR(dvb->thread)) { + rc = PTR_ERR(dvb->thread); + dvb->thread = NULL; + } + +out: + up(&dvb->lock); + return rc; +} + +static int videobuf_dvb_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct videobuf_dvb *dvb = demux->priv; + int err = 0; + + down(&dvb->lock); + dvb->nfeeds--; + if (0 == dvb->nfeeds && NULL != dvb->thread) { + // FIXME: cx8802_cancel_buffers(dev); + err = kthread_stop(dvb->thread); + dvb->thread = NULL; + } + up(&dvb->lock); + return err; +} + +/* ------------------------------------------------------------------ */ + +int videobuf_dvb_register(struct videobuf_dvb *dvb, + struct module *module, + void *adapter_priv) +{ + int result; + + init_MUTEX(&dvb->lock); + + /* register adapter */ + result = dvb_register_adapter(&dvb->adapter, dvb->name, module); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_register_adapter failed (errno = %d)\n", + dvb->name, result); + goto fail_adapter; + } + dvb->adapter->priv = adapter_priv; + + /* register frontend */ + result = dvb_register_frontend(dvb->adapter, dvb->frontend); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_register_frontend failed (errno = %d)\n", + dvb->name, result); + goto fail_frontend; + } + + /* register demux stuff */ + dvb->demux.dmx.capabilities = + DMX_TS_FILTERING | DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + dvb->demux.priv = dvb; + dvb->demux.filternum = 256; + dvb->demux.feednum = 256; + dvb->demux.start_feed = videobuf_dvb_start_feed; + dvb->demux.stop_feed = videobuf_dvb_stop_feed; + result = dvb_dmx_init(&dvb->demux); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_dmx_init failed (errno = %d)\n", + dvb->name, result); + goto fail_dmx; + } + + dvb->dmxdev.filternum = 256; + dvb->dmxdev.demux = &dvb->demux.dmx; + dvb->dmxdev.capabilities = 0; + result = dvb_dmxdev_init(&dvb->dmxdev, dvb->adapter); + if (result < 0) { + printk(KERN_WARNING "%s: dvb_dmxdev_init failed (errno = %d)\n", + dvb->name, result); + goto fail_dmxdev; + } + + dvb->fe_hw.source = DMX_FRONTEND_0; + result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + printk(KERN_WARNING "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n", + dvb->name, result); + goto fail_fe_hw; + } + + dvb->fe_mem.source = DMX_MEMORY_FE; + result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem); + if (result < 0) { + printk(KERN_WARNING "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n", + dvb->name, result); + goto fail_fe_mem; + } + + result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw); + if (result < 0) { + printk(KERN_WARNING "%s: connect_frontend failed (errno = %d)\n", + dvb->name, result); + goto fail_fe_conn; + } + + /* register network adapter */ + dvb_net_init(dvb->adapter, &dvb->net, &dvb->demux.dmx); + return 0; + +fail_fe_conn: + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); +fail_fe_mem: + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); +fail_fe_hw: + dvb_dmxdev_release(&dvb->dmxdev); +fail_dmxdev: + dvb_dmx_release(&dvb->demux); +fail_dmx: + dvb_unregister_frontend(dvb->frontend); +fail_frontend: + dvb_unregister_adapter(dvb->adapter); +fail_adapter: + return result; +} + +void videobuf_dvb_unregister(struct videobuf_dvb *dvb) +{ + dvb_net_release(&dvb->net); + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); + dvb_dmxdev_release(&dvb->dmxdev); + dvb_dmx_release(&dvb->demux); + dvb_unregister_frontend(dvb->frontend); + dvb_unregister_adapter(dvb->adapter); +} + +EXPORT_SYMBOL(videobuf_dvb_register); +EXPORT_SYMBOL(videobuf_dvb_unregister); + +/* ------------------------------------------------------------------ */ +/* + * Local variables: + * c-basic-offset: 8 + * compile-command: "make DVB=1" + * End: + */ diff --git a/drivers/media/video/video-buf.c b/drivers/media/video/video-buf.c new file mode 100644 index 000000000000..5afdc7852610 --- /dev/null +++ b/drivers/media/video/video-buf.c @@ -0,0 +1,1290 @@ +/* + * $Id: video-buf.c,v 1.18 2005/02/24 13:32:30 kraxel Exp $ + * + * generic helper functions for video4linux capture buffers, to handle + * memory management and PCI DMA. Right now bttv + saa7134 use it. + * + * The functions expect the hardware being able to scatter gatter + * (i.e. the buffers are not linear in physical memory, but fragmented + * into PAGE_SIZE chunks). They also assume the driver does not need + * to touch the video data (thus it is probably not useful for USB 1.1 + * as data often must be uncompressed by the drivers). + * + * (c) 2001-2004 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs] + * + * 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <asm/page.h> +#include <asm/pgtable.h> + +#include <media/video-buf.h> + +#define MAGIC_DMABUF 0x19721112 +#define MAGIC_BUFFER 0x20040302 +#define MAGIC_CHECK(is,should) if (unlikely((is) != (should))) \ + { printk(KERN_ERR "magic mismatch: %x (expected %x)\n",is,should); BUG(); } + +static int debug = 0; +module_param(debug, int, 0644); + +MODULE_DESCRIPTION("helper module to manage video4linux pci dma buffers"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); +MODULE_LICENSE("GPL"); + +#define dprintk(level, fmt, arg...) if (debug >= level) \ + printk(KERN_DEBUG "vbuf: " fmt , ## arg) + +struct scatterlist* +videobuf_vmalloc_to_sg(unsigned char *virt, int nr_pages) +{ + struct scatterlist *sglist; + struct page *pg; + int i; + + sglist = kmalloc(sizeof(struct scatterlist)*nr_pages, GFP_KERNEL); + if (NULL == sglist) + return NULL; + memset(sglist,0,sizeof(struct scatterlist)*nr_pages); + for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { + pg = vmalloc_to_page(virt); + if (NULL == pg) + goto err; + if (PageHighMem(pg)) + BUG(); + sglist[i].page = pg; + sglist[i].length = PAGE_SIZE; + } + return sglist; + + err: + kfree(sglist); + return NULL; +} + +struct scatterlist* +videobuf_pages_to_sg(struct page **pages, int nr_pages, int offset) +{ + struct scatterlist *sglist; + int i = 0; + + if (NULL == pages[0]) + return NULL; + sglist = kmalloc(sizeof(*sglist) * nr_pages, GFP_KERNEL); + if (NULL == sglist) + return NULL; + memset(sglist, 0, sizeof(*sglist) * nr_pages); + + if (NULL == pages[0]) + goto nopage; + if (PageHighMem(pages[0])) + /* DMA to highmem pages might not work */ + goto highmem; + sglist[0].page = pages[0]; + sglist[0].offset = offset; + sglist[0].length = PAGE_SIZE - offset; + for (i = 1; i < nr_pages; i++) { + if (NULL == pages[i]) + goto nopage; + if (PageHighMem(pages[i])) + goto highmem; + sglist[i].page = pages[i]; + sglist[i].length = PAGE_SIZE; + } + return sglist; + + nopage: + dprintk(2,"sgl: oops - no page\n"); + kfree(sglist); + return NULL; + + highmem: + dprintk(2,"sgl: oops - highmem page\n"); + kfree(sglist); + return NULL; +} + +/* --------------------------------------------------------------------- */ + +void videobuf_dma_init(struct videobuf_dmabuf *dma) +{ + memset(dma,0,sizeof(*dma)); + dma->magic = MAGIC_DMABUF; +} + +int videobuf_dma_init_user(struct videobuf_dmabuf *dma, int direction, + unsigned long data, unsigned long size) +{ + unsigned long first,last; + int err, rw = 0; + + dma->direction = direction; + switch (dma->direction) { + case PCI_DMA_FROMDEVICE: rw = READ; break; + case PCI_DMA_TODEVICE: rw = WRITE; break; + default: BUG(); + } + + first = (data & PAGE_MASK) >> PAGE_SHIFT; + last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT; + dma->offset = data & ~PAGE_MASK; + dma->nr_pages = last-first+1; + dma->pages = kmalloc(dma->nr_pages * sizeof(struct page*), + GFP_KERNEL); + if (NULL == dma->pages) + return -ENOMEM; + dprintk(1,"init user [0x%lx+0x%lx => %d pages]\n", + data,size,dma->nr_pages); + + down_read(¤t->mm->mmap_sem); + err = get_user_pages(current,current->mm, + data & PAGE_MASK, dma->nr_pages, + rw == READ, 1, /* force */ + dma->pages, NULL); + up_read(¤t->mm->mmap_sem); + if (err != dma->nr_pages) { + dma->nr_pages = (err >= 0) ? err : 0; + dprintk(1,"get_user_pages: err=%d [%d]\n",err,dma->nr_pages); + return err < 0 ? err : -EINVAL; + } + return 0; +} + +int videobuf_dma_init_kernel(struct videobuf_dmabuf *dma, int direction, + int nr_pages) +{ + dprintk(1,"init kernel [%d pages]\n",nr_pages); + dma->direction = direction; + dma->vmalloc = vmalloc_32(nr_pages << PAGE_SHIFT); + if (NULL == dma->vmalloc) { + dprintk(1,"vmalloc_32(%d pages) failed\n",nr_pages); + return -ENOMEM; + } + memset(dma->vmalloc,0,nr_pages << PAGE_SHIFT); + dma->nr_pages = nr_pages; + return 0; +} + +int videobuf_dma_init_overlay(struct videobuf_dmabuf *dma, int direction, + dma_addr_t addr, int nr_pages) +{ + dprintk(1,"init overlay [%d pages @ bus 0x%lx]\n", + nr_pages,(unsigned long)addr); + dma->direction = direction; + if (0 == addr) + return -EINVAL; + + dma->bus_addr = addr; + dma->nr_pages = nr_pages; + return 0; +} + +int videobuf_dma_pci_map(struct pci_dev *dev, struct videobuf_dmabuf *dma) +{ + MAGIC_CHECK(dma->magic,MAGIC_DMABUF); + BUG_ON(0 == dma->nr_pages); + + if (dma->pages) { + dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages, + dma->offset); + } + if (dma->vmalloc) { + dma->sglist = videobuf_vmalloc_to_sg + (dma->vmalloc,dma->nr_pages); + } + if (dma->bus_addr) { + dma->sglist = kmalloc(sizeof(struct scatterlist), GFP_KERNEL); + if (NULL != dma->sglist) { + dma->sglen = 1; + sg_dma_address(&dma->sglist[0]) = dma->bus_addr & PAGE_MASK; + dma->sglist[0].offset = dma->bus_addr & ~PAGE_MASK; + sg_dma_len(&dma->sglist[0]) = dma->nr_pages * PAGE_SIZE; + } + } + if (NULL == dma->sglist) { + dprintk(1,"scatterlist is NULL\n"); + return -ENOMEM; + } + + if (!dma->bus_addr) { + dma->sglen = pci_map_sg(dev,dma->sglist,dma->nr_pages, + dma->direction); + if (0 == dma->sglen) { + printk(KERN_WARNING + "%s: pci_map_sg failed\n",__FUNCTION__); + kfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + return -EIO; + } + } + return 0; +} + +int videobuf_dma_pci_sync(struct pci_dev *dev, struct videobuf_dmabuf *dma) +{ + MAGIC_CHECK(dma->magic,MAGIC_DMABUF); + BUG_ON(!dma->sglen); + + if (!dma->bus_addr) + pci_dma_sync_sg_for_cpu(dev,dma->sglist,dma->nr_pages,dma->direction); + return 0; +} + +int videobuf_dma_pci_unmap(struct pci_dev *dev, struct videobuf_dmabuf *dma) +{ + MAGIC_CHECK(dma->magic,MAGIC_DMABUF); + if (!dma->sglen) + return 0; + + if (!dma->bus_addr) + pci_unmap_sg(dev,dma->sglist,dma->nr_pages,dma->direction); + kfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + return 0; +} + +int videobuf_dma_free(struct videobuf_dmabuf *dma) +{ + MAGIC_CHECK(dma->magic,MAGIC_DMABUF); + BUG_ON(dma->sglen); + + if (dma->pages) { + int i; + for (i=0; i < dma->nr_pages; i++) + page_cache_release(dma->pages[i]); + kfree(dma->pages); + dma->pages = NULL; + } + if (dma->vmalloc) { + vfree(dma->vmalloc); + dma->vmalloc = NULL; + } + if (dma->bus_addr) { + dma->bus_addr = 0; + } + dma->direction = PCI_DMA_NONE; + return 0; +} + +/* --------------------------------------------------------------------- */ + +void* videobuf_alloc(unsigned int size) +{ + struct videobuf_buffer *vb; + + vb = kmalloc(size,GFP_KERNEL); + if (NULL != vb) { + memset(vb,0,size); + videobuf_dma_init(&vb->dma); + init_waitqueue_head(&vb->done); + vb->magic = MAGIC_BUFFER; + } + return vb; +} + +int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr) +{ + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + + MAGIC_CHECK(vb->magic,MAGIC_BUFFER); + add_wait_queue(&vb->done, &wait); + while (vb->state == STATE_ACTIVE || vb->state == STATE_QUEUED) { + if (non_blocking) { + retval = -EAGAIN; + break; + } + set_current_state(intr ? TASK_INTERRUPTIBLE + : TASK_UNINTERRUPTIBLE); + if (vb->state == STATE_ACTIVE || vb->state == STATE_QUEUED) + schedule(); + set_current_state(TASK_RUNNING); + if (intr && signal_pending(current)) { + dprintk(1,"buffer waiton: -EINTR\n"); + retval = -EINTR; + break; + } + } + remove_wait_queue(&vb->done, &wait); + return retval; +} + +int +videobuf_iolock(struct pci_dev *pci, struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + int err,pages; + dma_addr_t bus; + + MAGIC_CHECK(vb->magic,MAGIC_BUFFER); + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + case V4L2_MEMORY_USERPTR: + if (0 == vb->baddr) { + /* no userspace addr -- kernel bounce buffer */ + pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT; + err = videobuf_dma_init_kernel(&vb->dma,PCI_DMA_FROMDEVICE, + pages); + if (0 != err) + return err; + } else { + /* dma directly to userspace */ + err = videobuf_dma_init_user(&vb->dma,PCI_DMA_FROMDEVICE, + vb->baddr,vb->bsize); + if (0 != err) + return err; + } + break; + case V4L2_MEMORY_OVERLAY: + if (NULL == fbuf) + return -EINVAL; + /* FIXME: need sanity checks for vb->boff */ + bus = (dma_addr_t)fbuf->base + vb->boff; + pages = PAGE_ALIGN(vb->size) >> PAGE_SHIFT; + err = videobuf_dma_init_overlay(&vb->dma,PCI_DMA_FROMDEVICE, + bus, pages); + if (0 != err) + return err; + break; + default: + BUG(); + } + err = videobuf_dma_pci_map(pci,&vb->dma); + if (0 != err) + return err; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +void videobuf_queue_init(struct videobuf_queue* q, + struct videobuf_queue_ops *ops, + struct pci_dev *pci, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv) +{ + memset(q,0,sizeof(*q)); + q->irqlock = irqlock; + q->pci = pci; + q->type = type; + q->field = field; + q->msize = msize; + q->ops = ops; + q->priv_data = priv; + + init_MUTEX(&q->lock); + INIT_LIST_HEAD(&q->stream); +} + +int +videobuf_queue_is_busy(struct videobuf_queue *q) +{ + int i; + + if (q->streaming) { + dprintk(1,"busy: streaming active\n"); + return 1; + } + if (q->reading) { + dprintk(1,"busy: pending read #1\n"); + return 1; + } + if (q->read_buf) { + dprintk(1,"busy: pending read #2\n"); + return 1; + } + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]->map) { + dprintk(1,"busy: buffer #%d mapped\n",i); + return 1; + } + if (q->bufs[i]->state == STATE_QUEUED) { + dprintk(1,"busy: buffer #%d queued\n",i); + return 1; + } + if (q->bufs[i]->state == STATE_ACTIVE) { + dprintk(1,"busy: buffer #%d avtive\n",i); + return 1; + } + } + return 0; +} + +void +videobuf_queue_cancel(struct videobuf_queue *q) +{ + unsigned long flags; + int i; + + /* remove queued buffers from list */ + spin_lock_irqsave(q->irqlock,flags); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]->state == STATE_QUEUED) { + list_del(&q->bufs[i]->queue); + q->bufs[i]->state = STATE_ERROR; + } + } + spin_unlock_irqrestore(q->irqlock,flags); + + /* free all buffers + clear queue */ + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + q->ops->buf_release(q,q->bufs[i]); + } + INIT_LIST_HEAD(&q->stream); +} + +/* --------------------------------------------------------------------- */ + +enum v4l2_field +videobuf_next_field(struct videobuf_queue *q) +{ + enum v4l2_field field = q->field; + + BUG_ON(V4L2_FIELD_ANY == field); + + if (V4L2_FIELD_ALTERNATE == field) { + if (V4L2_FIELD_TOP == q->last) { + field = V4L2_FIELD_BOTTOM; + q->last = V4L2_FIELD_BOTTOM; + } else { + field = V4L2_FIELD_TOP; + q->last = V4L2_FIELD_TOP; + } + } + return field; +} + +void +videobuf_status(struct v4l2_buffer *b, struct videobuf_buffer *vb, + enum v4l2_buf_type type) +{ + MAGIC_CHECK(vb->magic,MAGIC_BUFFER); + + b->index = vb->i; + b->type = type; + + b->memory = vb->memory; + switch (b->memory) { + case V4L2_MEMORY_MMAP: + b->m.offset = vb->boff; + b->length = vb->bsize; + break; + case V4L2_MEMORY_USERPTR: + b->m.userptr = vb->baddr; + b->length = vb->bsize; + break; + case V4L2_MEMORY_OVERLAY: + b->m.offset = vb->boff; + break; + } + + b->flags = 0; + if (vb->map) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + switch (vb->state) { + case STATE_PREPARED: + case STATE_QUEUED: + case STATE_ACTIVE: + b->flags |= V4L2_BUF_FLAG_QUEUED; + break; + case STATE_DONE: + case STATE_ERROR: + b->flags |= V4L2_BUF_FLAG_DONE; + break; + case STATE_NEEDS_INIT: + case STATE_IDLE: + /* nothing */ + break; + } + + if (vb->input != UNSET) { + b->flags |= V4L2_BUF_FLAG_INPUT; + b->input = vb->input; + } + + b->field = vb->field; + b->timestamp = vb->ts; + b->bytesused = vb->size; + b->sequence = vb->field_count >> 1; +} + +int +videobuf_reqbufs(struct videobuf_queue *q, + struct v4l2_requestbuffers *req) +{ + unsigned int size,count; + int retval; + + if (req->type != q->type) + return -EINVAL; + if (req->count < 1) + return -EINVAL; + if (req->memory != V4L2_MEMORY_MMAP && + req->memory != V4L2_MEMORY_USERPTR && + req->memory != V4L2_MEMORY_OVERLAY) + return -EINVAL; + + if (q->streaming) + return -EBUSY; + if (!list_empty(&q->stream)) + return -EBUSY; + + down(&q->lock); + count = req->count; + if (count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME; + size = 0; + q->ops->buf_setup(q,&count,&size); + size = PAGE_ALIGN(size); + dprintk(1,"reqbufs: bufs=%d, size=0x%x [%d pages total]\n", + count, size, (count*size)>>PAGE_SHIFT); + + retval = videobuf_mmap_setup(q,count,size,req->memory); + if (retval < 0) + goto done; + + req->count = count; + + done: + up(&q->lock); + return retval; +} + +int +videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b) +{ + if (unlikely(b->type != q->type)) + return -EINVAL; + if (unlikely(b->index < 0 || b->index >= VIDEO_MAX_FRAME)) + return -EINVAL; + if (unlikely(NULL == q->bufs[b->index])) + return -EINVAL; + videobuf_status(b,q->bufs[b->index],q->type); + return 0; +} + +int +videobuf_qbuf(struct videobuf_queue *q, + struct v4l2_buffer *b) +{ + struct videobuf_buffer *buf; + enum v4l2_field field; + unsigned long flags; + int retval; + + down(&q->lock); + retval = -EBUSY; + if (q->reading) + goto done; + retval = -EINVAL; + if (b->type != q->type) + goto done; + if (b->index < 0 || b->index >= VIDEO_MAX_FRAME) + goto done; + buf = q->bufs[b->index]; + if (NULL == buf) + goto done; + MAGIC_CHECK(buf->magic,MAGIC_BUFFER); + if (buf->memory != b->memory) + goto done; + if (buf->state == STATE_QUEUED || + buf->state == STATE_ACTIVE) + goto done; + + if (b->flags & V4L2_BUF_FLAG_INPUT) { + if (b->input >= q->inputs) + goto done; + buf->input = b->input; + } else { + buf->input = UNSET; + } + + switch (b->memory) { + case V4L2_MEMORY_MMAP: + if (0 == buf->baddr) + goto done; + break; + case V4L2_MEMORY_USERPTR: + if (b->length < buf->bsize) + goto done; + if (STATE_NEEDS_INIT != buf->state && buf->baddr != b->m.userptr) + q->ops->buf_release(q,buf); + buf->baddr = b->m.userptr; + break; + case V4L2_MEMORY_OVERLAY: + buf->boff = b->m.offset; + break; + default: + goto done; + } + + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(q,buf,field); + if (0 != retval) + goto done; + + list_add_tail(&buf->stream,&q->stream); + if (q->streaming) { + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(q,buf); + spin_unlock_irqrestore(q->irqlock,flags); + } + retval = 0; + + done: + up(&q->lock); + return retval; +} + +int +videobuf_dqbuf(struct videobuf_queue *q, + struct v4l2_buffer *b, int nonblocking) +{ + struct videobuf_buffer *buf; + int retval; + + down(&q->lock); + retval = -EBUSY; + if (q->reading) + goto done; + retval = -EINVAL; + if (b->type != q->type) + goto done; + if (list_empty(&q->stream)) + goto done; + buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + retval = videobuf_waiton(buf, nonblocking, 1); + if (retval < 0) + goto done; + switch (buf->state) { + case STATE_ERROR: + retval = -EIO; + /* fall through */ + case STATE_DONE: + videobuf_dma_pci_sync(q->pci,&buf->dma); + buf->state = STATE_IDLE; + break; + default: + retval = -EINVAL; + goto done; + } + list_del(&buf->stream); + memset(b,0,sizeof(*b)); + videobuf_status(b,buf,q->type); + + done: + up(&q->lock); + return retval; +} + +int videobuf_streamon(struct videobuf_queue *q) +{ + struct videobuf_buffer *buf; + struct list_head *list; + unsigned long flags; + int retval; + + down(&q->lock); + retval = -EBUSY; + if (q->reading) + goto done; + retval = 0; + if (q->streaming) + goto done; + q->streaming = 1; + spin_lock_irqsave(q->irqlock,flags); + list_for_each(list,&q->stream) { + buf = list_entry(list, struct videobuf_buffer, stream); + if (buf->state == STATE_PREPARED) + q->ops->buf_queue(q,buf); + } + spin_unlock_irqrestore(q->irqlock,flags); + + done: + up(&q->lock); + return retval; +} + +int videobuf_streamoff(struct videobuf_queue *q) +{ + int retval = -EINVAL; + + down(&q->lock); + if (!q->streaming) + goto done; + videobuf_queue_cancel(q); + q->streaming = 0; + retval = 0; + + done: + up(&q->lock); + return retval; +} + +static ssize_t +videobuf_read_zerocopy(struct videobuf_queue *q, char __user *data, + size_t count, loff_t *ppos) +{ + enum v4l2_field field; + unsigned long flags; + int retval; + + /* setup stuff */ + retval = -ENOMEM; + q->read_buf = videobuf_alloc(q->msize); + if (NULL == q->read_buf) + goto done; + + q->read_buf->memory = V4L2_MEMORY_USERPTR; + q->read_buf->baddr = (unsigned long)data; + q->read_buf->bsize = count; + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(q,q->read_buf,field); + if (0 != retval) + goto done; + + /* start capture & wait */ + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(q,q->read_buf); + spin_unlock_irqrestore(q->irqlock,flags); + retval = videobuf_waiton(q->read_buf,0,0); + if (0 == retval) { + videobuf_dma_pci_sync(q->pci,&q->read_buf->dma); + if (STATE_ERROR == q->read_buf->state) + retval = -EIO; + else + retval = q->read_buf->size; + } + + done: + /* cleanup */ + q->ops->buf_release(q,q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + return retval; +} + +ssize_t videobuf_read_one(struct videobuf_queue *q, + char __user *data, size_t count, loff_t *ppos, + int nonblocking) +{ + enum v4l2_field field; + unsigned long flags; + unsigned size, nbufs, bytes; + int retval; + + down(&q->lock); + + nbufs = 1; size = 0; + q->ops->buf_setup(q,&nbufs,&size); + if (NULL == q->read_buf && + count >= size && + !nonblocking) { + retval = videobuf_read_zerocopy(q,data,count,ppos); + if (retval >= 0 || retval == -EIO) + /* ok, all done */ + goto done; + /* fallback to kernel bounce buffer on failures */ + } + + if (NULL == q->read_buf) { + /* need to capture a new frame */ + retval = -ENOMEM; + q->read_buf = videobuf_alloc(q->msize); + if (NULL == q->read_buf) + goto done; + q->read_buf->memory = V4L2_MEMORY_USERPTR; + field = videobuf_next_field(q); + retval = q->ops->buf_prepare(q,q->read_buf,field); + if (0 != retval) + goto done; + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(q,q->read_buf); + spin_unlock_irqrestore(q->irqlock,flags); + q->read_off = 0; + } + + /* wait until capture is done */ + retval = videobuf_waiton(q->read_buf, nonblocking, 1); + if (0 != retval) + goto done; + videobuf_dma_pci_sync(q->pci,&q->read_buf->dma); + + if (STATE_ERROR == q->read_buf->state) { + /* catch I/O errors */ + q->ops->buf_release(q,q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + retval = -EIO; + goto done; + } + + /* copy to userspace */ + bytes = count; + if (bytes > q->read_buf->size - q->read_off) + bytes = q->read_buf->size - q->read_off; + retval = -EFAULT; + if (copy_to_user(data, q->read_buf->dma.vmalloc+q->read_off, bytes)) + goto done; + + retval = bytes; + q->read_off += bytes; + if (q->read_off == q->read_buf->size) { + /* all data copied, cleanup */ + q->ops->buf_release(q,q->read_buf); + kfree(q->read_buf); + q->read_buf = NULL; + } + + done: + up(&q->lock); + return retval; +} + +int videobuf_read_start(struct videobuf_queue *q) +{ + enum v4l2_field field; + unsigned long flags; + int count = 0, size = 0; + int err, i; + + q->ops->buf_setup(q,&count,&size); + if (count < 2) + count = 2; + if (count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME; + size = PAGE_ALIGN(size); + + err = videobuf_mmap_setup(q, count, size, V4L2_MEMORY_USERPTR); + if (err) + return err; + for (i = 0; i < count; i++) { + field = videobuf_next_field(q); + err = q->ops->buf_prepare(q,q->bufs[i],field); + if (err) + return err; + list_add_tail(&q->bufs[i]->stream, &q->stream); + } + spin_lock_irqsave(q->irqlock,flags); + for (i = 0; i < count; i++) + q->ops->buf_queue(q,q->bufs[i]); + spin_unlock_irqrestore(q->irqlock,flags); + q->reading = 1; + return 0; +} + +void videobuf_read_stop(struct videobuf_queue *q) +{ + int i; + + videobuf_queue_cancel(q); + videobuf_mmap_free(q); + INIT_LIST_HEAD(&q->stream); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + q->read_buf = NULL; + q->reading = 0; +} + +ssize_t videobuf_read_stream(struct videobuf_queue *q, + char __user *data, size_t count, loff_t *ppos, + int vbihack, int nonblocking) +{ + unsigned int *fc, bytes; + int err, retval; + unsigned long flags; + + dprintk(2,"%s\n",__FUNCTION__); + down(&q->lock); + retval = -EBUSY; + if (q->streaming) + goto done; + if (!q->reading) { + retval = videobuf_read_start(q); + if (retval < 0) + goto done; + } + + retval = 0; + while (count > 0) { + /* get / wait for data */ + if (NULL == q->read_buf) { + q->read_buf = list_entry(q->stream.next, + struct videobuf_buffer, + stream); + list_del(&q->read_buf->stream); + q->read_off = 0; + } + err = videobuf_waiton(q->read_buf, nonblocking, 1); + if (err < 0) { + if (0 == retval) + retval = err; + break; + } + + if (q->read_buf->state == STATE_DONE) { + if (vbihack) { + /* dirty, undocumented hack -- pass the frame counter + * within the last four bytes of each vbi data block. + * We need that one to maintain backward compatibility + * to all vbi decoding software out there ... */ + fc = (unsigned int*)q->read_buf->dma.vmalloc; + fc += (q->read_buf->size>>2) -1; + *fc = q->read_buf->field_count >> 1; + dprintk(1,"vbihack: %d\n",*fc); + } + + /* copy stuff */ + bytes = count; + if (bytes > q->read_buf->size - q->read_off) + bytes = q->read_buf->size - q->read_off; + if (copy_to_user(data + retval, + q->read_buf->dma.vmalloc + q->read_off, + bytes)) { + if (0 == retval) + retval = -EFAULT; + break; + } + count -= bytes; + retval += bytes; + q->read_off += bytes; + } else { + /* some error */ + q->read_off = q->read_buf->size; + if (0 == retval) + retval = -EIO; + } + + /* requeue buffer when done with copying */ + if (q->read_off == q->read_buf->size) { + list_add_tail(&q->read_buf->stream, + &q->stream); + spin_lock_irqsave(q->irqlock,flags); + q->ops->buf_queue(q,q->read_buf); + spin_unlock_irqrestore(q->irqlock,flags); + q->read_buf = NULL; + } + if (retval < 0) + break; + } + + done: + up(&q->lock); + return retval; +} + +unsigned int videobuf_poll_stream(struct file *file, + struct videobuf_queue *q, + poll_table *wait) +{ + struct videobuf_buffer *buf = NULL; + unsigned int rc = 0; + + down(&q->lock); + if (q->streaming) { + if (!list_empty(&q->stream)) + buf = list_entry(q->stream.next, + struct videobuf_buffer, stream); + } else { + if (!q->reading) + videobuf_read_start(q); + if (!q->reading) { + rc = POLLERR; + } else if (NULL == q->read_buf) { + q->read_buf = list_entry(q->stream.next, + struct videobuf_buffer, + stream); + list_del(&q->read_buf->stream); + q->read_off = 0; + } + buf = q->read_buf; + } + if (!buf) + rc = POLLERR; + + if (0 == rc) { + poll_wait(file, &buf->done, wait); + if (buf->state == STATE_DONE || + buf->state == STATE_ERROR) + rc = POLLIN|POLLRDNORM; + } + up(&q->lock); + return rc; +} + +/* --------------------------------------------------------------------- */ + +static void +videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + dprintk(2,"vm_open %p [count=%d,vma=%08lx-%08lx]\n",map, + map->count,vma->vm_start,vma->vm_end); + map->count++; +} + +static void +videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + int i; + + dprintk(2,"vm_close %p [count=%d,vma=%08lx-%08lx]\n",map, + map->count,vma->vm_start,vma->vm_end); + + map->count--; + if (0 == map->count) { + dprintk(1,"munmap %p q=%p\n",map,q); + down(&q->lock); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (q->bufs[i]) + ; + if (q->bufs[i]->map != map) + continue; + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + q->ops->buf_release(q,q->bufs[i]); + } + up(&q->lock); + kfree(map); + } + return; +} + +/* + * Get a anonymous page for the mapping. Make sure we can DMA to that + * memory location with 32bit PCI devices (i.e. don't use highmem for + * now ...). Bounce buffers don't work very well for the data rates + * video capture has. + */ +static struct page* +videobuf_vm_nopage(struct vm_area_struct *vma, unsigned long vaddr, + int *type) +{ + struct page *page; + + dprintk(3,"nopage: fault @ %08lx [vma %08lx-%08lx]\n", + vaddr,vma->vm_start,vma->vm_end); + if (vaddr > vma->vm_end) + return NOPAGE_SIGBUS; + page = alloc_page(GFP_USER); + if (!page) + return NOPAGE_OOM; + clear_user_page(page_address(page), vaddr, page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct videobuf_vm_ops = +{ + .open = videobuf_vm_open, + .close = videobuf_vm_close, + .nopage = videobuf_vm_nopage, +}; + +int videobuf_mmap_setup(struct videobuf_queue *q, + unsigned int bcount, unsigned int bsize, + enum v4l2_memory memory) +{ + unsigned int i; + int err; + + err = videobuf_mmap_free(q); + if (0 != err) + return err; + + for (i = 0; i < bcount; i++) { + q->bufs[i] = videobuf_alloc(q->msize); + q->bufs[i]->i = i; + q->bufs[i]->input = UNSET; + q->bufs[i]->memory = memory; + q->bufs[i]->bsize = bsize; + switch (memory) { + case V4L2_MEMORY_MMAP: + q->bufs[i]->boff = bsize * i; + break; + case V4L2_MEMORY_USERPTR: + case V4L2_MEMORY_OVERLAY: + /* nothing */ + break; + } + } + dprintk(1,"mmap setup: %d buffers, %d bytes each\n", + bcount,bsize); + return 0; +} + +int videobuf_mmap_free(struct videobuf_queue *q) +{ + int i; + + for (i = 0; i < VIDEO_MAX_FRAME; i++) + if (q->bufs[i] && q->bufs[i]->map) + return -EBUSY; + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + q->ops->buf_release(q,q->bufs[i]); + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + return 0; +} + +int videobuf_mmap_mapper(struct videobuf_queue *q, + struct vm_area_struct *vma) +{ + struct videobuf_mapping *map; + unsigned int first,last,size,i; + int retval; + + down(&q->lock); + retval = -EINVAL; + if (!(vma->vm_flags & VM_WRITE)) { + dprintk(1,"mmap app bug: PROT_WRITE please\n"); + goto done; + } + if (!(vma->vm_flags & VM_SHARED)) { + dprintk(1,"mmap app bug: MAP_SHARED please\n"); + goto done; + } + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (NULL == q->bufs[first]) + continue; + if (V4L2_MEMORY_MMAP != q->bufs[first]->memory) + continue; + if (q->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT)) + break; + } + if (VIDEO_MAX_FRAME == first) { + dprintk(1,"mmap app bug: offset invalid [offset=0x%lx]\n", + (vma->vm_pgoff << PAGE_SHIFT)); + goto done; + } + + /* look for last buffer to map */ + for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) { + if (NULL == q->bufs[last]) + continue; + if (V4L2_MEMORY_MMAP != q->bufs[last]->memory) + continue; + if (q->bufs[last]->map) { + retval = -EBUSY; + goto done; + } + size += q->bufs[last]->bsize; + if (size == (vma->vm_end - vma->vm_start)) + break; + } + if (VIDEO_MAX_FRAME == last) { + dprintk(1,"mmap app bug: size invalid [size=0x%lx]\n", + (vma->vm_end - vma->vm_start)); + goto done; + } + + /* create mapping + update buffer list */ + retval = -ENOMEM; + map = kmalloc(sizeof(struct videobuf_mapping),GFP_KERNEL); + if (NULL == map) + goto done; + for (size = 0, i = first; i <= last; size += q->bufs[i++]->bsize) { + q->bufs[i]->map = map; + q->bufs[i]->baddr = vma->vm_start + size; + } + map->count = 1; + map->start = vma->vm_start; + map->end = vma->vm_end; + map->q = q; + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + vma->vm_private_data = map; + dprintk(1,"mmap %p: q=%p %08lx-%08lx pgoff %08lx bufs %d-%d\n", + map,q,vma->vm_start,vma->vm_end,vma->vm_pgoff,first,last); + retval = 0; + + done: + up(&q->lock); + return retval; +} + +/* --------------------------------------------------------------------- */ + +EXPORT_SYMBOL_GPL(videobuf_vmalloc_to_sg); + +EXPORT_SYMBOL_GPL(videobuf_dma_init); +EXPORT_SYMBOL_GPL(videobuf_dma_init_user); +EXPORT_SYMBOL_GPL(videobuf_dma_init_kernel); +EXPORT_SYMBOL_GPL(videobuf_dma_init_overlay); +EXPORT_SYMBOL_GPL(videobuf_dma_pci_map); +EXPORT_SYMBOL_GPL(videobuf_dma_pci_sync); +EXPORT_SYMBOL_GPL(videobuf_dma_pci_unmap); +EXPORT_SYMBOL_GPL(videobuf_dma_free); + +EXPORT_SYMBOL_GPL(videobuf_alloc); +EXPORT_SYMBOL_GPL(videobuf_waiton); +EXPORT_SYMBOL_GPL(videobuf_iolock); + +EXPORT_SYMBOL_GPL(videobuf_queue_init); +EXPORT_SYMBOL_GPL(videobuf_queue_cancel); +EXPORT_SYMBOL_GPL(videobuf_queue_is_busy); + +EXPORT_SYMBOL_GPL(videobuf_next_field); +EXPORT_SYMBOL_GPL(videobuf_status); +EXPORT_SYMBOL_GPL(videobuf_reqbufs); +EXPORT_SYMBOL_GPL(videobuf_querybuf); +EXPORT_SYMBOL_GPL(videobuf_qbuf); +EXPORT_SYMBOL_GPL(videobuf_dqbuf); +EXPORT_SYMBOL_GPL(videobuf_streamon); +EXPORT_SYMBOL_GPL(videobuf_streamoff); + +EXPORT_SYMBOL_GPL(videobuf_read_start); +EXPORT_SYMBOL_GPL(videobuf_read_stop); +EXPORT_SYMBOL_GPL(videobuf_read_stream); +EXPORT_SYMBOL_GPL(videobuf_read_one); +EXPORT_SYMBOL_GPL(videobuf_poll_stream); + +EXPORT_SYMBOL_GPL(videobuf_mmap_setup); +EXPORT_SYMBOL_GPL(videobuf_mmap_free); +EXPORT_SYMBOL_GPL(videobuf_mmap_mapper); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/videocodec.c b/drivers/media/video/videocodec.c new file mode 100644 index 000000000000..c9d5f1a873cc --- /dev/null +++ b/drivers/media/video/videocodec.c @@ -0,0 +1,490 @@ +/* + * VIDEO MOTION CODECs internal API for video devices + * + * Interface for MJPEG (and maybe later MPEG/WAVELETS) codec's + * bound to a master device. + * + * (c) 2002 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: videocodec.c,v 1.1.2.8 2003/03/29 07:16:04 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ------------------------------------------------------------------------ + */ + +#define VIDEOCODEC_VERSION "v0.2" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/slab.h> + +// kernel config is here (procfs flag) +#include <linux/config.h> + +#ifdef CONFIG_PROC_FS +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#endif + +#include "videocodec.h" + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +struct attached_list { + struct videocodec *codec; + struct attached_list *next; +}; + +struct codec_list { + const struct videocodec *codec; + int attached; + struct attached_list *list; + struct codec_list *next; +}; + +static struct codec_list *codeclist_top = NULL; + +/* ================================================= */ +/* function prototypes of the master/slave interface */ +/* ================================================= */ + +struct videocodec * +videocodec_attach (struct videocodec_master *master) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a, *ptr; + struct videocodec *codec; + int res; + + if (!master) { + dprintk(1, KERN_ERR "videocodec_attach: no data\n"); + return NULL; + } + + dprintk(2, + "videocodec_attach: '%s', type: %x, flags %lx, magic %lx\n", + master->name, master->type, master->flags, master->magic); + + if (!h) { + dprintk(1, + KERN_ERR + "videocodec_attach: no device available\n"); + return NULL; + } + + while (h) { + // attach only if the slave has at least the flags + // expected by the master + if ((master->flags & h->codec->flags) == master->flags) { + dprintk(4, "videocodec_attach: try '%s'\n", + h->codec->name); + + if (!try_module_get(h->codec->owner)) + return NULL; + + codec = + kmalloc(sizeof(struct videocodec), GFP_KERNEL); + if (!codec) { + dprintk(1, + KERN_ERR + "videocodec_attach: no mem\n"); + goto out_module_put; + } + memcpy(codec, h->codec, sizeof(struct videocodec)); + + snprintf(codec->name, sizeof(codec->name), + "%s[%d]", codec->name, h->attached); + codec->master_data = master; + res = codec->setup(codec); + if (res == 0) { + dprintk(3, "videocodec_attach '%s'\n", + codec->name); + ptr = (struct attached_list *) + kmalloc(sizeof(struct attached_list), + GFP_KERNEL); + if (!ptr) { + dprintk(1, + KERN_ERR + "videocodec_attach: no memory\n"); + goto out_kfree; + } + memset(ptr, 0, + sizeof(struct attached_list)); + ptr->codec = codec; + + a = h->list; + if (!a) { + h->list = ptr; + dprintk(4, + "videocodec: first element\n"); + } else { + while (a->next) + a = a->next; // find end + a->next = ptr; + dprintk(4, + "videocodec: in after '%s'\n", + h->codec->name); + } + + h->attached += 1; + return codec; + } else { + kfree(codec); + } + } + h = h->next; + } + + dprintk(1, KERN_ERR "videocodec_attach: no codec found!\n"); + return NULL; + + out_module_put: + module_put(h->codec->owner); + out_kfree: + kfree(codec); + return NULL; +} + +int +videocodec_detach (struct videocodec *codec) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a, *prev; + int res; + + if (!codec) { + dprintk(1, KERN_ERR "videocodec_detach: no data\n"); + return -EINVAL; + } + + dprintk(2, + "videocodec_detach: '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + if (!h) { + dprintk(1, + KERN_ERR "videocodec_detach: no device left...\n"); + return -ENXIO; + } + + while (h) { + a = h->list; + prev = NULL; + while (a) { + if (codec == a->codec) { + res = a->codec->unset(a->codec); + if (res >= 0) { + dprintk(3, + "videocodec_detach: '%s'\n", + a->codec->name); + a->codec->master_data = NULL; + } else { + dprintk(1, + KERN_ERR + "videocodec_detach: '%s'\n", + a->codec->name); + a->codec->master_data = NULL; + } + if (prev == NULL) { + h->list = a->next; + dprintk(4, + "videocodec: delete first\n"); + } else { + prev->next = a->next; + dprintk(4, + "videocodec: delete middle\n"); + } + module_put(a->codec->owner); + kfree(a->codec); + kfree(a); + h->attached -= 1; + return 0; + } + prev = a; + a = a->next; + } + h = h->next; + } + + dprintk(1, KERN_ERR "videocodec_detach: given codec not found!\n"); + return -EINVAL; +} + +int +videocodec_register (const struct videocodec *codec) +{ + struct codec_list *ptr, *h = codeclist_top; + + if (!codec) { + dprintk(1, KERN_ERR "videocodec_register: no data!\n"); + return -EINVAL; + } + + dprintk(2, + "videocodec: register '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + ptr = + (struct codec_list *) kmalloc(sizeof(struct codec_list), + GFP_KERNEL); + if (!ptr) { + dprintk(1, KERN_ERR "videocodec_register: no memory\n"); + return -ENOMEM; + } + memset(ptr, 0, sizeof(struct codec_list)); + ptr->codec = codec; + + if (!h) { + codeclist_top = ptr; + dprintk(4, "videocodec: hooked in as first element\n"); + } else { + while (h->next) + h = h->next; // find the end + h->next = ptr; + dprintk(4, "videocodec: hooked in after '%s'\n", + h->codec->name); + } + + return 0; +} + +int +videocodec_unregister (const struct videocodec *codec) +{ + struct codec_list *prev = NULL, *h = codeclist_top; + + if (!codec) { + dprintk(1, KERN_ERR "videocodec_unregister: no data!\n"); + return -EINVAL; + } + + dprintk(2, + "videocodec: unregister '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + if (!h) { + dprintk(1, + KERN_ERR + "videocodec_unregister: no device left...\n"); + return -ENXIO; + } + + while (h) { + if (codec == h->codec) { + if (h->attached) { + dprintk(1, + KERN_ERR + "videocodec: '%s' is used\n", + h->codec->name); + return -EBUSY; + } + dprintk(3, "videocodec: unregister '%s' is ok.\n", + h->codec->name); + if (prev == NULL) { + codeclist_top = h->next; + dprintk(4, + "videocodec: delete first element\n"); + } else { + prev->next = h->next; + dprintk(4, + "videocodec: delete middle element\n"); + } + kfree(h); + return 0; + } + prev = h; + h = h->next; + } + + dprintk(1, + KERN_ERR + "videocodec_unregister: given codec not found!\n"); + return -EINVAL; +} + +#ifdef CONFIG_PROC_FS +/* ============ */ +/* procfs stuff */ +/* ============ */ + +static char *videocodec_buf = NULL; +static int videocodec_bufsize = 0; + +static int +videocodec_build_table (void) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a; + int i = 0, size; + + // sum up amount of slaves plus their attached masters + while (h) { + i += h->attached + 1; + h = h->next; + } +#define LINESIZE 100 + size = LINESIZE * (i + 1); + + dprintk(3, "videocodec_build table: %d entries, %d bytes\n", i, + size); + + if (videocodec_buf) + kfree(videocodec_buf); + videocodec_buf = (char *) kmalloc(size, GFP_KERNEL); + + i = 0; + i += scnprintf(videocodec_buf + i, size - 1, + "<S>lave or attached <M>aster name type flags magic "); + i += scnprintf(videocodec_buf + i, size -i - 1, "(connected as)\n"); + + h = codeclist_top; + while (h) { + if (i > (size - LINESIZE)) + break; // security check + i += scnprintf(videocodec_buf + i, size -i -1, + "S %32s %04x %08lx %08lx (TEMPLATE)\n", + h->codec->name, h->codec->type, + h->codec->flags, h->codec->magic); + a = h->list; + while (a) { + if (i > (size - LINESIZE)) + break; // security check + i += scnprintf(videocodec_buf + i, size -i -1, + "M %32s %04x %08lx %08lx (%s)\n", + a->codec->master_data->name, + a->codec->master_data->type, + a->codec->master_data->flags, + a->codec->master_data->magic, + a->codec->name); + a = a->next; + } + h = h->next; + } + + return i; +} + +//The definition: +//typedef int (read_proc_t)(char *page, char **start, off_t off, +// int count, int *eof, void *data); + +static int +videocodec_info (char *buffer, + char **buffer_location, + off_t offset, + int buffer_length, + int *eof, + void *data) +{ + int size; + + dprintk(3, "videocodec_info: offset: %ld, len %d / size %d\n", + offset, buffer_length, videocodec_bufsize); + + if (offset == 0) { + videocodec_bufsize = videocodec_build_table(); + } + if ((offset < 0) || (offset >= videocodec_bufsize)) { + dprintk(4, + "videocodec_info: call delivers no result, return 0\n"); + *eof = 1; + return 0; + } + + if (buffer_length < (videocodec_bufsize - offset)) { + dprintk(4, "videocodec_info: %ld needed, %d got\n", + videocodec_bufsize - offset, buffer_length); + size = buffer_length; + } else { + dprintk(4, "videocodec_info: last reading of %ld bytes\n", + videocodec_bufsize - offset); + size = videocodec_bufsize - offset; + *eof = 1; + } + + memcpy(buffer, videocodec_buf + offset, size); + /* doesn't work... */ + /* copy_to_user(buffer, videocodec_buf+offset, size); */ + /* *buffer_location = videocodec_buf+offset; */ + + return size; +} +#endif + +/* ===================== */ +/* hook in driver module */ +/* ===================== */ +static int __init +videocodec_init (void) +{ +#ifdef CONFIG_PROC_FS + static struct proc_dir_entry *videocodec_proc_entry; +#endif + + printk(KERN_INFO "Linux video codec intermediate layer: %s\n", + VIDEOCODEC_VERSION); + +#ifdef CONFIG_PROC_FS + videocodec_buf = NULL; + videocodec_bufsize = 0; + + videocodec_proc_entry = create_proc_entry("videocodecs", 0, NULL); + if (videocodec_proc_entry) { + videocodec_proc_entry->read_proc = videocodec_info; + videocodec_proc_entry->write_proc = NULL; + videocodec_proc_entry->data = NULL; + videocodec_proc_entry->owner = THIS_MODULE; + } else { + dprintk(1, KERN_ERR "videocodec: can't init procfs.\n"); + } +#endif + return 0; +} + +static void __exit +videocodec_exit (void) +{ +#ifdef CONFIG_PROC_FS + remove_proc_entry("videocodecs", NULL); + if (videocodec_buf) + kfree(videocodec_buf); +#endif +} + +EXPORT_SYMBOL(videocodec_attach); +EXPORT_SYMBOL(videocodec_detach); +EXPORT_SYMBOL(videocodec_register); +EXPORT_SYMBOL(videocodec_unregister); + +module_init(videocodec_init); +module_exit(videocodec_exit); + +MODULE_AUTHOR("Wolfgang Scherr <scherr@net4you.at>"); +MODULE_DESCRIPTION("Intermediate API module for video codecs " + VIDEOCODEC_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/videocodec.h b/drivers/media/video/videocodec.h new file mode 100644 index 000000000000..156ae57096fe --- /dev/null +++ b/drivers/media/video/videocodec.h @@ -0,0 +1,358 @@ +/* + * VIDEO MOTION CODECs internal API for video devices + * + * Interface for MJPEG (and maybe later MPEG/WAVELETS) codec's + * bound to a master device. + * + * (c) 2002 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: videocodec.h,v 1.1.2.4 2003/01/14 21:15:03 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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. + * + * ------------------------------------------------------------------------ + */ + +/* =================== */ +/* general description */ +/* =================== */ + +/* Should ease the (re-)usage of drivers supporting cards with (different) + video codecs. The codecs register to this module their functionality, + and the processors (masters) can attach to them if they fit. + + The codecs are typically have a "strong" binding to their master - so I + don't think it makes sense to have a full blown interfacing as with e.g. + i2c. If you have an other opinion, let's discuss & implement it :-))) + + Usage: + + The slave has just to setup the videocodec structure and use two functions: + videocodec_register(codecdata); + videocodec_unregister(codecdata); + The best is just calling them at module (de-)initialisation. + + The master sets up the structure videocodec_master and calls: + codecdata=videocodec_attach(master_codecdata); + videocodec_detach(codecdata); + + The slave is called during attach/detach via functions setup previously + during register. At that time, the master_data pointer is set up + and the slave can access any io registers of the master device (in the case + the slave is bound to it). Otherwise it doesn't need this functions and + therfor they may not be initialized. + + The other fuctions are just for convenience, as they are for shure used by + most/all of the codecs. The last ones may be ommited, too. + + See the structure declaration below for more information and which data has + to be set up for the master and the slave. + + ---------------------------------------------------------------------------- + The master should have "knowledge" of the slave and vice versa. So the data + structures sent to/from slave via set_data/get_data set_image/get_image are + device dependent and vary between MJPEG/MPEG/WAVELET/... devices. (!!!!) + ---------------------------------------------------------------------------- +*/ + + +/* ========================================== */ +/* description of the videocodec_io structure */ +/* ========================================== */ + +/* + ==== master setup ==== + name -> name of the device structure for reference and debugging + master_data -> data ref. for the master (e.g. the zr36055,57,67) + readreg -> ref. to read-fn from register (setup by master, used by slave) + writereg -> ref. to write-fn to register (setup by master, used by slave) + this two functions do the lowlevel I/O job + + ==== slave functionality setup ==== + slave_data -> data ref. for the slave (e.g. the zr36050,60) + check -> fn-ref. checks availability of an device, returns -EIO on failure or + the type on success + this makes espcecially sense if a driver module supports more than + one codec which may be quite similar to access, nevertheless it + is good for a first functionality check + + -- main functions you always need for compression/decompression -- + + set_mode -> this fn-ref. resets the entire codec, and sets up the mode + with the last defined norm/size (or device default if not + available) - it returns 0 if the mode is possible + set_size -> this fn-ref. sets the norm and image size for + compression/decompression (returns 0 on success) + the norm param is defined in videodev.h (VIDEO_MODE_*) + + additional setup may be available, too - but the codec should work with + some default values even without this + + set_data -> sets device-specific data (tables, quality etc.) + get_data -> query device-specific data (tables, quality etc.) + + if the device delivers interrupts, they may be setup/handled here + setup_interrupt -> codec irq setup (not needed for 36050/60) + handle_interrupt -> codec irq handling (not needed for 36050/60) + + if the device delivers pictures, they may be handled here + put_image -> puts image data to the codec (not needed for 36050/60) + get_image -> gets image data from the codec (not needed for 36050/60) + the calls include frame numbers and flags (even/odd/...) + if needed and a flag which allows blocking until its ready +*/ + +/* ============== */ +/* user interface */ +/* ============== */ + +/* + Currently there is only a information display planned, as the layer + is not visible for the user space at all. + + Information is available via procfs. The current entry is "/proc/videocodecs" + but it makes sense to "hide" it in the /proc/video tree of v4l(2) --TODO--. + +A example for such an output is: + +<S>lave or attached <M>aster name type flags magic (connected as) +S zr36050 0002 0000d001 00000000 (TEMPLATE) +M zr36055[0] 0001 0000c001 00000000 (zr36050[0]) +M zr36055[1] 0001 0000c001 00000000 (zr36050[1]) + +*/ + + +/* =============================================== */ +/* special defines for the videocodec_io structure */ +/* =============================================== */ + +#ifndef __LINUX_VIDEOCODEC_H +#define __LINUX_VIDEOCODEC_H + +#include <linux/videodev.h> + +//should be in videodev.h ??? (VID_DO_....) +#define CODEC_DO_COMPRESSION 0 +#define CODEC_DO_EXPANSION 1 + +/* this are the current codec flags I think they are needed */ +/* -> type value in structure */ +#define CODEC_FLAG_JPEG 0x00000001L // JPEG codec +#define CODEC_FLAG_MPEG 0x00000002L // MPEG1/2/4 codec +#define CODEC_FLAG_DIVX 0x00000004L // DIVX codec +#define CODEC_FLAG_WAVELET 0x00000008L // WAVELET codec + // room for other types + +#define CODEC_FLAG_MAGIC 0x00000800L // magic key must match +#define CODEC_FLAG_HARDWARE 0x00001000L // is a hardware codec +#define CODEC_FLAG_VFE 0x00002000L // has direct video frontend +#define CODEC_FLAG_ENCODER 0x00004000L // compression capability +#define CODEC_FLAG_DECODER 0x00008000L // decompression capability +#define CODEC_FLAG_NEEDIRQ 0x00010000L // needs irq handling +#define CODEC_FLAG_RDWRPIC 0x00020000L // handles picture I/O + +/* a list of modes, some are just examples (is there any HW?) */ +#define CODEC_MODE_BJPG 0x0001 // Baseline JPEG +#define CODEC_MODE_LJPG 0x0002 // Lossless JPEG +#define CODEC_MODE_MPEG1 0x0003 // MPEG 1 +#define CODEC_MODE_MPEG2 0x0004 // MPEG 2 +#define CODEC_MODE_MPEG4 0x0005 // MPEG 4 +#define CODEC_MODE_MSDIVX 0x0006 // MS DivX +#define CODEC_MODE_ODIVX 0x0007 // Open DivX +#define CODEC_MODE_WAVELET 0x0008 // Wavelet + +/* this are the current codec types I want to implement */ +/* -> type value in structure */ +#define CODEC_TYPE_NONE 0 +#define CODEC_TYPE_L64702 1 +#define CODEC_TYPE_ZR36050 2 +#define CODEC_TYPE_ZR36016 3 +#define CODEC_TYPE_ZR36060 4 + +/* the type of data may be enhanced by future implementations (data-fn.'s) */ +/* -> used in command */ +#define CODEC_G_STATUS 0x0000 /* codec status (query only) */ +#define CODEC_S_CODEC_MODE 0x0001 /* codec mode (baseline JPEG, MPEG1,... */ +#define CODEC_G_CODEC_MODE 0x8001 +#define CODEC_S_VFE 0x0002 /* additional video frontend setup */ +#define CODEC_G_VFE 0x8002 +#define CODEC_S_MMAP 0x0003 /* MMAP setup (if available) */ + +#define CODEC_S_JPEG_TDS_BYTE 0x0010 /* target data size in bytes */ +#define CODEC_G_JPEG_TDS_BYTE 0x8010 +#define CODEC_S_JPEG_SCALE 0x0011 /* scaling factor for quant. tables */ +#define CODEC_G_JPEG_SCALE 0x8011 +#define CODEC_S_JPEG_HDT_DATA 0x0018 /* huffman-tables */ +#define CODEC_G_JPEG_HDT_DATA 0x8018 +#define CODEC_S_JPEG_QDT_DATA 0x0019 /* quantizing-tables */ +#define CODEC_G_JPEG_QDT_DATA 0x8019 +#define CODEC_S_JPEG_APP_DATA 0x001A /* APP marker */ +#define CODEC_G_JPEG_APP_DATA 0x801A +#define CODEC_S_JPEG_COM_DATA 0x001B /* COM marker */ +#define CODEC_G_JPEG_COM_DATA 0x801B + +#define CODEC_S_PRIVATE 0x1000 /* "private" commands start here */ +#define CODEC_G_PRIVATE 0x9000 + +#define CODEC_G_FLAG 0x8000 /* this is how 'get' is detected */ + +/* types of transfer, directly user space or a kernel buffer (image-fn.'s) */ +/* -> used in get_image, put_image */ +#define CODEC_TRANSFER_KERNEL 0 /* use "memcopy" */ +#define CODEC_TRANSFER_USER 1 /* use "to/from_user" */ + + +/* ========================= */ +/* the structures itself ... */ +/* ========================= */ + +struct vfe_polarity { + int vsync_pol:1; + int hsync_pol:1; + int field_pol:1; + int blank_pol:1; + int subimg_pol:1; + int poe_pol:1; + int pvalid_pol:1; + int vclk_pol:1; +}; + +struct vfe_settings { + __u32 x, y; /* Offsets into image */ + __u32 width, height; /* Area to capture */ + __u16 decimation; /* Decimation divider */ + __u16 flags; /* Flags for capture */ +/* flags are the same as in struct video_capture - see videodev.h: +#define VIDEO_CAPTURE_ODD 0 +#define VIDEO_CAPTURE_EVEN 1 +*/ + __u16 quality; /* quality of the video */ +}; + +struct tvnorm { + u16 Wt, Wa, HStart, HSyncStart, Ht, Ha, VStart; +}; + +struct jpeg_com_marker { + int len; /* number of usable bytes in data */ + char data[60]; +}; + +struct jpeg_app_marker { + int appn; /* number app segment */ + int len; /* number of usable bytes in data */ + char data[60]; +}; + +struct videocodec { + struct module *owner; + /* -- filled in by slave device during register -- */ + char name[32]; + unsigned long magic; /* may be used for client<->master attaching */ + unsigned long flags; /* functionality flags */ + unsigned int type; /* codec type */ + + /* -- these is filled in later during master device attach -- */ + + struct videocodec_master *master_data; + + /* -- these are filled in by the slave device during register -- */ + + void *data; /* private slave data */ + + /* attach/detach client functions (indirect call) */ + int (*setup) (struct videocodec * codec); + int (*unset) (struct videocodec * codec); + + /* main functions, every client needs them for sure! */ + // set compression or decompression (or freeze, stop, standby, etc) + int (*set_mode) (struct videocodec * codec, + int mode); + // setup picture size and norm (for the codec's video frontend) + int (*set_video) (struct videocodec * codec, + struct tvnorm * norm, + struct vfe_settings * cap, + struct vfe_polarity * pol); + // other control commands, also mmap setup etc. + int (*control) (struct videocodec * codec, + int type, + int size, + void *data); + + /* additional setup/query/processing (may be NULL pointer) */ + // interrupt setup / handling (for irq's delivered by master) + int (*setup_interrupt) (struct videocodec * codec, + long mode); + int (*handle_interrupt) (struct videocodec * codec, + int source, + long flag); + // picture interface (if any) + long (*put_image) (struct videocodec * codec, + int tr_type, + int block, + long *fr_num, + long *flag, + long size, + void *buf); + long (*get_image) (struct videocodec * codec, + int tr_type, + int block, + long *fr_num, + long *flag, + long size, + void *buf); +}; + +struct videocodec_master { + /* -- filled in by master device for registration -- */ + char name[32]; + unsigned long magic; /* may be used for client<->master attaching */ + unsigned long flags; /* functionality flags */ + unsigned int type; /* master type */ + + void *data; /* private master data */ + + __u32(*readreg) (struct videocodec * codec, + __u16 reg); + void (*writereg) (struct videocodec * codec, + __u16 reg, + __u32 value); +}; + + +/* ================================================= */ +/* function prototypes of the master/slave interface */ +/* ================================================= */ + +/* attach and detach commands for the master */ +// * master structure needs to be kmalloc'ed before calling attach +// and free'd after calling detach +// * returns pointer on success, NULL on failure +extern struct videocodec *videocodec_attach(struct videocodec_master *); +// * 0 on success, <0 (errno) on failure +extern int videocodec_detach(struct videocodec *); + +/* register and unregister commands for the slaves */ +// * 0 on success, <0 (errno) on failure +extern int videocodec_register(const struct videocodec *); +// * 0 on success, <0 (errno) on failure +extern int videocodec_unregister(const struct videocodec *); + +/* the other calls are directly done via the videocodec structure! */ + +#endif /*ifndef __LINUX_VIDEOCODEC_H */ diff --git a/drivers/media/video/videodev.c b/drivers/media/video/videodev.c new file mode 100644 index 000000000000..06df15f75de9 --- /dev/null +++ b/drivers/media/video/videodev.c @@ -0,0 +1,436 @@ +/* + * Video capture interface for Linux + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * 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. + * + * Author: Alan Cox, <alan@redhat.com> + * + * Fixes: 20000516 Claudio Matsuoka <claudio@conectiva.com> + * - Added procfs support + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/devfs_fs_kernel.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/semaphore.h> + +#include <linux/videodev.h> + +#define VIDEO_NUM_DEVICES 256 +#define VIDEO_NAME "video4linux" + +/* + * sysfs stuff + */ + +static ssize_t show_name(struct class_device *cd, char *buf) +{ + struct video_device *vfd = container_of(cd, struct video_device, class_dev); + return sprintf(buf,"%.*s\n",(int)sizeof(vfd->name),vfd->name); +} + +static CLASS_DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +struct video_device *video_device_alloc(void) +{ + struct video_device *vfd; + + vfd = kmalloc(sizeof(*vfd),GFP_KERNEL); + if (NULL == vfd) + return NULL; + memset(vfd,0,sizeof(*vfd)); + return vfd; +} + +void video_device_release(struct video_device *vfd) +{ + kfree(vfd); +} + +static void video_release(struct class_device *cd) +{ + struct video_device *vfd = container_of(cd, struct video_device, class_dev); + +#if 1 /* needed until all drivers are fixed */ + if (!vfd->release) + return; +#endif + vfd->release(vfd); +} + +static struct class video_class = { + .name = VIDEO_NAME, + .release = video_release, +}; + +/* + * Active devices + */ + +static struct video_device *video_device[VIDEO_NUM_DEVICES]; +static DECLARE_MUTEX(videodev_lock); + +struct video_device* video_devdata(struct file *file) +{ + return video_device[iminor(file->f_dentry->d_inode)]; +} + +/* + * Open a video device. + */ +static int video_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + int err = 0; + struct video_device *vfl; + struct file_operations *old_fops; + + if(minor>=VIDEO_NUM_DEVICES) + return -ENODEV; + down(&videodev_lock); + vfl=video_device[minor]; + if(vfl==NULL) { + up(&videodev_lock); + request_module("char-major-%d-%d", VIDEO_MAJOR, minor); + down(&videodev_lock); + vfl=video_device[minor]; + if (vfl==NULL) { + up(&videodev_lock); + return -ENODEV; + } + } + old_fops = file->f_op; + file->f_op = fops_get(vfl->fops); + if(file->f_op->open) + err = file->f_op->open(inode,file); + if (err) { + fops_put(file->f_op); + file->f_op = fops_get(old_fops); + } + fops_put(old_fops); + up(&videodev_lock); + return err; +} + +/* + * helper function -- handles userspace copying for ioctl arguments + */ + +static unsigned int +video_fix_command(unsigned int cmd) +{ + switch (cmd) { + case VIDIOC_OVERLAY_OLD: + cmd = VIDIOC_OVERLAY; + break; + case VIDIOC_S_PARM_OLD: + cmd = VIDIOC_S_PARM; + break; + case VIDIOC_S_CTRL_OLD: + cmd = VIDIOC_S_CTRL; + break; + case VIDIOC_G_AUDIO_OLD: + cmd = VIDIOC_G_AUDIO; + break; + case VIDIOC_G_AUDOUT_OLD: + cmd = VIDIOC_G_AUDOUT; + break; + case VIDIOC_CROPCAP_OLD: + cmd = VIDIOC_CROPCAP; + break; + } + return cmd; +} + +int +video_usercopy(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + int (*func)(struct inode *inode, struct file *file, + unsigned int cmd, void *arg)) +{ + char sbuf[128]; + void *mbuf = NULL; + void *parg = NULL; + int err = -EINVAL; + + cmd = video_fix_command(cmd); + + /* Copy arguments into temp kernel buffer */ + switch (_IOC_DIR(cmd)) { + case _IOC_NONE: + parg = NULL; + break; + case _IOC_READ: + case _IOC_WRITE: + case (_IOC_WRITE | _IOC_READ): + if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { + parg = sbuf; + } else { + /* too big to allocate from stack */ + mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); + if (NULL == mbuf) + return -ENOMEM; + parg = mbuf; + } + + err = -EFAULT; + if (_IOC_DIR(cmd) & _IOC_WRITE) + if (copy_from_user(parg, (void __user *)arg, _IOC_SIZE(cmd))) + goto out; + break; + } + + /* call driver */ + err = func(inode, file, cmd, parg); + if (err == -ENOIOCTLCMD) + err = -EINVAL; + if (err < 0) + goto out; + + /* Copy results into user buffer */ + switch (_IOC_DIR(cmd)) + { + case _IOC_READ: + case (_IOC_WRITE | _IOC_READ): + if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd))) + err = -EFAULT; + break; + } + +out: + if (mbuf) + kfree(mbuf); + return err; +} + +/* + * open/release helper functions -- handle exclusive opens + */ +int video_exclusive_open(struct inode *inode, struct file *file) +{ + struct video_device *vfl = video_devdata(file); + int retval = 0; + + down(&vfl->lock); + if (vfl->users) { + retval = -EBUSY; + } else { + vfl->users++; + } + up(&vfl->lock); + return retval; +} + +int video_exclusive_release(struct inode *inode, struct file *file) +{ + struct video_device *vfl = video_devdata(file); + + vfl->users--; + return 0; +} + +static struct file_operations video_fops; + +/** + * video_register_device - register video4linux devices + * @vfd: video device structure we want to register + * @type: type of device to register + * @nr: which device number (0 == /dev/video0, 1 == /dev/video1, ... + * -1 == first free) + * + * The registration code assigns minor numbers based on the type + * requested. -ENFILE is returned in all the device slots for this + * category are full. If not then the minor field is set and the + * driver initialize function is called (if non %NULL). + * + * Zero is returned on success. + * + * Valid types are + * + * %VFL_TYPE_GRABBER - A frame grabber + * + * %VFL_TYPE_VTX - A teletext device + * + * %VFL_TYPE_VBI - Vertical blank data (undecoded) + * + * %VFL_TYPE_RADIO - A radio card + */ + +int video_register_device(struct video_device *vfd, int type, int nr) +{ + int i=0; + int base; + int end; + char *name_base; + + switch(type) + { + case VFL_TYPE_GRABBER: + base=0; + end=64; + name_base = "video"; + break; + case VFL_TYPE_VTX: + base=192; + end=224; + name_base = "vtx"; + break; + case VFL_TYPE_VBI: + base=224; + end=240; + name_base = "vbi"; + break; + case VFL_TYPE_RADIO: + base=64; + end=128; + name_base = "radio"; + break; + default: + return -1; + } + + /* pick a minor number */ + down(&videodev_lock); + if (nr >= 0 && nr < end-base) { + /* use the one the driver asked for */ + i = base+nr; + if (NULL != video_device[i]) { + up(&videodev_lock); + return -ENFILE; + } + } else { + /* use first free */ + for(i=base;i<end;i++) + if (NULL == video_device[i]) + break; + if (i == end) { + up(&videodev_lock); + return -ENFILE; + } + } + video_device[i]=vfd; + vfd->minor=i; + up(&videodev_lock); + + sprintf(vfd->devfs_name, "v4l/%s%d", name_base, i - base); + devfs_mk_cdev(MKDEV(VIDEO_MAJOR, vfd->minor), + S_IFCHR | S_IRUSR | S_IWUSR, vfd->devfs_name); + init_MUTEX(&vfd->lock); + + /* sysfs class */ + memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev)); + if (vfd->dev) + vfd->class_dev.dev = vfd->dev; + vfd->class_dev.class = &video_class; + vfd->class_dev.devt = MKDEV(VIDEO_MAJOR, vfd->minor); + strlcpy(vfd->class_dev.class_id, vfd->devfs_name + 4, BUS_ID_SIZE); + class_device_register(&vfd->class_dev); + class_device_create_file(&vfd->class_dev, + &class_device_attr_name); + +#if 1 /* needed until all drivers are fixed */ + if (!vfd->release) + printk(KERN_WARNING "videodev: \"%s\" has no release callback. " + "Please fix your driver for proper sysfs support, see " + "http://lwn.net/Articles/36850/\n", vfd->name); +#endif + return 0; +} + +/** + * video_unregister_device - unregister a video4linux device + * @vfd: the device to unregister + * + * This unregisters the passed device and deassigns the minor + * number. Future open calls will be met with errors. + */ + +void video_unregister_device(struct video_device *vfd) +{ + down(&videodev_lock); + if(video_device[vfd->minor]!=vfd) + panic("videodev: bad unregister"); + + devfs_remove(vfd->devfs_name); + video_device[vfd->minor]=NULL; + class_device_unregister(&vfd->class_dev); + up(&videodev_lock); +} + + +static struct file_operations video_fops= +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = video_open, +}; + +/* + * Initialise video for linux + */ + +static int __init videodev_init(void) +{ + int ret; + + printk(KERN_INFO "Linux video capture interface: v1.00\n"); + if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) { + printk(KERN_WARNING "video_dev: unable to get major %d\n", VIDEO_MAJOR); + return -EIO; + } + + ret = class_register(&video_class); + if (ret < 0) { + unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME); + printk(KERN_WARNING "video_dev: class_register failed\n"); + return -EIO; + } + + return 0; +} + +static void __exit videodev_exit(void) +{ + class_unregister(&video_class); + unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME); +} + +module_init(videodev_init) +module_exit(videodev_exit) + +EXPORT_SYMBOL(video_register_device); +EXPORT_SYMBOL(video_unregister_device); +EXPORT_SYMBOL(video_devdata); +EXPORT_SYMBOL(video_usercopy); +EXPORT_SYMBOL(video_exclusive_open); +EXPORT_SYMBOL(video_exclusive_release); +EXPORT_SYMBOL(video_device_alloc); +EXPORT_SYMBOL(video_device_release); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("Device registrar for Video4Linux drivers"); +MODULE_LICENSE("GPL"); + + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/vino.c b/drivers/media/video/vino.c new file mode 100644 index 000000000000..76e8681d65c6 --- /dev/null +++ b/drivers/media/video/vino.c @@ -0,0 +1,347 @@ +/* + * (incomplete) Driver for the VINO (Video In No Out) system found in SGI Indys. + * + * This file is subject to the terms and conditions of the GNU General Public + * License version 2 as published by the Free Software Foundation. + * + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/wrapper.h> +#include <linux/errno.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-sgi.h> + +#include <asm/addrspace.h> +#include <asm/system.h> +#include <asm/bootinfo.h> +#include <asm/pgtable.h> +#include <asm/paccess.h> +#include <asm/io.h> +#include <asm/sgi/ip22.h> +#include <asm/sgi/hpc3.h> +#include <asm/sgi/mc.h> + +#include "vino.h" + +/* debugging? */ +#if 1 +#define DEBUG(x...) printk(x); +#else +#define DEBUG(x...) +#endif + + +/* VINO ASIC registers */ +struct sgi_vino *vino; + +static const char *vinostr = "VINO IndyCam/TV"; +static int threshold_a = 512; +static int threshold_b = 512; + +struct vino_device { + struct video_device vdev; +#define VINO_CHAN_A 1 +#define VINO_CHAN_B 2 + int chan; +}; + +struct vino_client { + struct i2c_client *driver; + int owner; +}; + +struct vino_video { + struct vino_device chA; + struct vino_device chB; + + struct vino_client decoder; + struct vino_client camera; + + struct semaphore input_lock; + + /* Loaded into VINO descriptors to clear End Of Descriptors table + * interupt condition */ + unsigned long dummy_page; + unsigned int dummy_buf[4] __attribute__((aligned(8))); +}; + +static struct vino_video *Vino; + +unsigned i2c_vino_getctrl(void *data) +{ + return vino->i2c_control; +} + +void i2c_vino_setctrl(void *data, unsigned val) +{ + vino->i2c_control = val; +} + +unsigned i2c_vino_rdata(void *data) +{ + return vino->i2c_data; +} + +void i2c_vino_wdata(void *data, unsigned val) +{ + vino->i2c_data = val; +} + +static struct i2c_algo_sgi_data i2c_sgi_vino_data = +{ + .getctrl = &i2c_vino_getctrl, + .setctrl = &i2c_vino_setctrl, + .rdata = &i2c_vino_rdata, + .wdata = &i2c_vino_wdata, + .xfer_timeout = 200, + .ack_timeout = 1000, +}; + +/* + * There are two possible clients on VINO I2C bus, so we limit usage only + * to them. + */ +static int i2c_vino_client_reg(struct i2c_client *client) +{ + int res = 0; + + down(&Vino->input_lock); + switch (client->driver->id) { + case I2C_DRIVERID_SAA7191: + if (Vino->decoder.driver) + res = -EBUSY; + else + Vino->decoder.driver = client; + break; + case I2C_DRIVERID_INDYCAM: + if (Vino->camera.driver) + res = -EBUSY; + else + Vino->camera.driver = client; + break; + default: + res = -ENODEV; + } + up(&Vino->input_lock); + + return res; +} + +static int i2c_vino_client_unreg(struct i2c_client *client) +{ + int res = 0; + + down(&Vino->input_lock); + if (client == Vino->decoder.driver) { + if (Vino->decoder.owner) + res = -EBUSY; + else + Vino->decoder.driver = NULL; + } else if (client == Vino->camera.driver) { + if (Vino->camera.owner) + res = -EBUSY; + else + Vino->camera.driver = NULL; + } + up(&Vino->input_lock); + + return res; +} + +static struct i2c_adapter vino_i2c_adapter = +{ + .name = "VINO I2C bus", + .id = I2C_HW_SGI_VINO, + .algo_data = &i2c_sgi_vino_data, + .client_register = &i2c_vino_client_reg, + .client_unregister = &i2c_vino_client_unreg, +}; + +static int vino_i2c_add_bus(void) +{ + return i2c_sgi_add_bus(&vino_i2c_adapter); +} + +static int vino_i2c_del_bus(void) +{ + return i2c_sgi_del_bus(&vino_i2c_adapter); +} + + +static void vino_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ +} + +static int vino_open(struct video_device *dev, int flags) +{ + struct vino_device *videv = (struct vino_device *)dev; + + return 0; +} + +static void vino_close(struct video_device *dev) +{ + struct vino_device *videv = (struct vino_device *)dev; +} + +static int vino_mmap(struct video_device *dev, const char *adr, + unsigned long size) +{ + struct vino_device *videv = (struct vino_device *)dev; + + return -EINVAL; +} + +static int vino_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct vino_device *videv = (struct vino_device *)dev; + + return -EINVAL; +} + +static const struct video_device vino_device = { + .owner = THIS_MODULE, + .type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE, + .hardware = VID_HARDWARE_VINO, + .name = "VINO", + .open = vino_open, + .close = vino_close, + .ioctl = vino_ioctl, + .mmap = vino_mmap, +}; + +static int __init vino_init(void) +{ + unsigned long rev; + int i, ret = 0; + + /* VINO is Indy specific beast */ + if (ip22_is_fullhouse()) + return -ENODEV; + + /* + * VINO is in the EISA address space, so the sysid register will tell + * us if the EISA_PRESENT pin on MC has been pulled low. + * + * If EISA_PRESENT is not set we definitely don't have a VINO equiped + * system. + */ + if (!(sgimc->systemid & SGIMC_SYSID_EPRESENT)) { + printk(KERN_ERR "VINO not found\n"); + return -ENODEV; + } + + vino = (struct sgi_vino *)ioremap(VINO_BASE, sizeof(struct sgi_vino)); + if (!vino) + return -EIO; + + /* Okay, once we know that VINO is present we'll read its revision + * safe way. One never knows... */ + if (get_dbe(rev, &(vino->rev_id))) { + printk(KERN_ERR "VINO: failed to read revision register\n"); + ret = -ENODEV; + goto out_unmap; + } + if (VINO_ID_VALUE(rev) != VINO_CHIP_ID) { + printk(KERN_ERR "VINO is not VINO (Rev/ID: 0x%04lx)\n", rev); + ret = -ENODEV; + goto out_unmap; + } + printk(KERN_INFO "VINO Rev: 0x%02lx\n", VINO_REV_NUM(rev)); + + Vino = (struct vino_video *) + kmalloc(sizeof(struct vino_video), GFP_KERNEL); + if (!Vino) { + ret = -ENOMEM; + goto out_unmap; + } + + Vino->dummy_page = get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!Vino->dummy_page) { + ret = -ENOMEM; + goto out_free_vino; + } + for (i = 0; i < 4; i++) + Vino->dummy_buf[i] = PHYSADDR(Vino->dummy_page); + + vino->control = 0; + /* prevent VINO from throwing spurious interrupts */ + vino->a.next_4_desc = PHYSADDR(Vino->dummy_buf); + vino->b.next_4_desc = PHYSADDR(Vino->dummy_buf); + udelay(5); + vino->intr_status = 0; + /* set threshold level */ + vino->a.fifo_thres = threshold_a; + vino->b.fifo_thres = threshold_b; + + init_MUTEX(&Vino->input_lock); + + if (request_irq(SGI_VINO_IRQ, vino_interrupt, 0, vinostr, NULL)) { + printk(KERN_ERR "VINO: irq%02d registration failed\n", + SGI_VINO_IRQ); + ret = -EAGAIN; + goto out_free_page; + } + + ret = vino_i2c_add_bus(); + if (ret) { + printk(KERN_ERR "VINO: I2C bus registration failed\n"); + goto out_free_irq; + } + + if (video_register_device(&Vino->chA.vdev, VFL_TYPE_GRABBER, -1) < 0) { + printk("%s, chnl %d: device registration failed.\n", + Vino->chA.vdev.name, Vino->chA.chan); + ret = -EINVAL; + goto out_i2c_del_bus; + } + if (video_register_device(&Vino->chB.vdev, VFL_TYPE_GRABBER, -1) < 0) { + printk("%s, chnl %d: device registration failed.\n", + Vino->chB.vdev.name, Vino->chB.chan); + ret = -EINVAL; + goto out_unregister_vdev; + } + + return 0; + +out_unregister_vdev: + video_unregister_device(&Vino->chA.vdev); +out_i2c_del_bus: + vino_i2c_del_bus(); +out_free_irq: + free_irq(SGI_VINO_IRQ, NULL); +out_free_page: + free_page(Vino->dummy_page); +out_free_vino: + kfree(Vino); +out_unmap: + iounmap(vino); + + return ret; +} + +static void __exit vino_exit(void) +{ + video_unregister_device(&Vino->chA.vdev); + video_unregister_device(&Vino->chB.vdev); + vino_i2c_del_bus(); + free_irq(SGI_VINO_IRQ, NULL); + free_page(Vino->dummy_page); + kfree(Vino); + iounmap(vino); +} + +module_init(vino_init); +module_exit(vino_exit); + +MODULE_DESCRIPTION("Video4Linux driver for SGI Indy VINO (IndyCam)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/vino.h b/drivers/media/video/vino.h new file mode 100644 index 000000000000..d2fce472f35a --- /dev/null +++ b/drivers/media/video/vino.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 1999 Ulf Karlsson <ulfc@bun.falkenberg.se> + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + */ + +#ifndef VINO_H +#define VINO_H + +#define VINO_BASE 0x00080000 /* Vino is in the EISA address space, + * but it is not an EISA bus card */ + +struct sgi_vino_channel { + u32 _pad_alpha; + volatile u32 alpha; + +#define VINO_CLIP_X(x) ((x) & 0x3ff) /* bits 0:9 */ +#define VINO_CLIP_ODD(x) (((x) & 0x1ff) << 10) /* bits 10:18 */ +#define VINO_CLIP_EVEN(x) (((x) & 0x1ff) << 19) /* bits 19:27 */ + u32 _pad_clip_start; + volatile u32 clip_start; + u32 _pad_clip_end; + volatile u32 clip_end; + +#define VINO_FRAMERT_PAL (1<<0) /* 0=NTSC 1=PAL */ +#define VINO_FRAMERT_RT(x) (((x) & 0x1fff) << 1) /* bits 1:12 */ + u32 _pad_frame_rate; + volatile u32 frame_rate; + + u32 _pad_field_counter; + volatile u32 field_counter; + u32 _pad_line_size; + volatile u32 line_size; + u32 _pad_line_count; + volatile u32 line_count; + u32 _pad_page_index; + volatile u32 page_index; + u32 _pad_next_4_desc; + volatile u32 next_4_desc; + u32 _pad_start_desc_tbl; + volatile u32 start_desc_tbl; + +#define VINO_DESC_JUMP (1<<30) +#define VINO_DESC_STOP (1<<31) +#define VINO_DESC_VALID (1<<32) + u32 _pad_desc_0; + volatile u32 desc_0; + u32 _pad_desc_1; + volatile u32 desc_1; + u32 _pad_desc_2; + volatile u32 desc_2; + u32 _pad_Bdesc_3; + volatile u32 desc_3; + + u32 _pad_fifo_thres; + volatile u32 fifo_thres; + u32 _pad_fifo_read; + volatile u32 fifo_read; + u32 _pad_fifo_write; + volatile u32 fifo_write; +}; + +struct sgi_vino { +#define VINO_CHIP_ID 0xb +#define VINO_REV_NUM(x) ((x) & 0x0f) +#define VINO_ID_VALUE(x) (((x) & 0xf0) >> 4) + u32 _pad_rev_id; + volatile u32 rev_id; + +#define VINO_CTRL_LITTLE_ENDIAN (1<<0) +#define VINO_CTRL_A_FIELD_TRANS_INT (1<<1) /* Field transferred int */ +#define VINO_CTRL_A_FIFO_OF_INT (1<<2) /* FIFO overflow int */ +#define VINO_CTRL_A_END_DESC_TBL_INT (1<<3) /* End of desc table int */ +#define VINO_CTRL_A_INT (VINO_CTRL_A_FIELD_TRANS_INT | \ + VINO_CTRL_A_FIFO_OF_INT | \ + VINO_CTRL_A_END_DESC_TBL_INT) +#define VINO_CTRL_B_FIELD_TRANS_INT (1<<4) /* Field transferred int */ +#define VINO_CTRL_B_FIFO_OF_INT (1<<5) /* FIFO overflow int */ +#define VINO_CTRL_B_END_DESC_TBL_INT (1<<6) /* End of desc table int */ +#define VINO_CTRL_B_INT (VINO_CTRL_B_FIELD_TRANS_INT | \ + VINO_CTRL_B_FIFO_OF_INT | \ + VINO_CTRL_B_END_DESC_TBL_INT) +#define VINO_CTRL_A_DMA_ENBL (1<<7) +#define VINO_CTRL_A_INTERLEAVE_ENBL (1<<8) +#define VINO_CTRL_A_SYNC_ENBL (1<<9) +#define VINO_CTRL_A_SELECT (1<<10) /* 1=D1 0=Philips */ +#define VINO_CTRL_A_RGB (1<<11) /* 1=RGB 0=YUV */ +#define VINO_CTRL_A_LUMA_ONLY (1<<12) +#define VINO_CTRL_A_DEC_ENBL (1<<13) /* Decimation */ +#define VINO_CTRL_A_DEC_SCALE_MASK 0x1c000 /* bits 14:17 */ +#define VINO_CTRL_A_DEC_SCALE_SHIFT (14) +#define VINO_CTRL_A_DEC_HOR_ONLY (1<<17) /* Horizontal only */ +#define VINO_CTRL_A_DITHER (1<<18) /* 24 -> 8 bit dither */ +#define VINO_CTRL_B_DMA_ENBL (1<<19) +#define VINO_CTRL_B_INTERLEAVE_ENBL (1<<20) +#define VINO_CTRL_B_SYNC_ENBL (1<<21) +#define VINO_CTRL_B_SELECT (1<<22) /* 1=D1 0=Philips */ +#define VINO_CTRL_B_RGB (1<<23) /* 1=RGB 0=YUV */ +#define VINO_CTRL_B_LUMA_ONLY (1<<24) +#define VINO_CTRL_B_DEC_ENBL (1<<25) /* Decimation */ +#define VINO_CTRL_B_DEC_SCALE_MASK 0x1c000000 /* bits 26:28 */ +#define VINO_CTRL_B_DEC_SCALE_SHIFT (26) +#define VINO_CTRL_B_DEC_HOR_ONLY (1<<29) /* Decimation horizontal only */ +#define VINO_CTRL_B_DITHER (1<<30) /* ChanB 24 -> 8 bit dither */ + u32 _pad_control; + volatile u32 control; + +#define VINO_INTSTAT_A_FIELD_TRANS (1<<0) /* Field transferred int */ +#define VINO_INTSTAT_A_FIFO_OF (1<<1) /* FIFO overflow int */ +#define VINO_INTSTAT_A_END_DESC_TBL (1<<2) /* End of desc table int */ +#define VINO_INTSTAT_A (VINO_INTSTAT_A_FIELD_TRANS | \ + VINO_INTSTAT_A_FIFO_OF | \ + VINO_INTSTAT_A_END_DESC_TBL) +#define VINO_INTSTAT_B_FIELD_TRANS (1<<3) /* Field transferred int */ +#define VINO_INTSTAT_B_FIFO_OF (1<<4) /* FIFO overflow int */ +#define VINO_INTSTAT_B_END_DESC_TBL (1<<5) /* End of desc table int */ +#define VINO_INTSTAT_B (VINO_INTSTAT_B_FIELD_TRANS | \ + VINO_INTSTAT_B_FIFO_OF | \ + VINO_INTSTAT_B_END_DESC_TBL) + u32 _pad_intr_status; + volatile u32 intr_status; + + u32 _pad_i2c_control; + volatile u32 i2c_control; + u32 _pad_i2c_data; + volatile u32 i2c_data; + + struct sgi_vino_channel a; + struct sgi_vino_channel b; +}; + +#endif diff --git a/drivers/media/video/vpx3220.c b/drivers/media/video/vpx3220.c new file mode 100644 index 000000000000..0fd6c9a70917 --- /dev/null +++ b/drivers/media/video/vpx3220.c @@ -0,0 +1,754 @@ +/* + * vpx3220a, vpx3216b & vpx3214c video decoder driver version 0.0.1 + * + * Copyright (C) 2001 Laurent Pinchart <lpinchart@freegates.be> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/slab.h> + +#include <linux/byteorder/swab.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <linux/i2c.h> +#include <linux/i2c-dev.h> + +#define I2C_NAME(x) (x)->name + +#include <linux/videodev.h> +#include <linux/video_decoder.h> + +#define I2C_VPX3220 0x86 +#define VPX3220_DEBUG KERN_DEBUG "vpx3220: " + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +#define VPX_TIMEOUT_COUNT 10 + +/* ----------------------------------------------------------------------- */ + +struct vpx3220 { + unsigned char reg[255]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +static char *inputs[] = { "internal", "composite", "svideo" }; + +/* ----------------------------------------------------------------------- */ +static inline int +vpx3220_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct vpx3220 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +vpx3220_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +vpx3220_fp_status (struct i2c_client *client) +{ + unsigned char status; + unsigned int i; + + for (i = 0; i < VPX_TIMEOUT_COUNT; i++) { + status = vpx3220_read(client, 0x29); + + if (!(status & 4)) + return 0; + + udelay(10); + + if (need_resched()) + cond_resched(); + } + + return -1; +} + +static int +vpx3220_fp_write (struct i2c_client *client, + u8 fpaddr, + u16 data) +{ + /* Write the 16-bit address to the FPWR register */ + if (i2c_smbus_write_word_data(client, 0x27, swab16(fpaddr)) == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + if (vpx3220_fp_status(client) < 0) + return -1; + + /* Write the 16-bit data to the FPDAT register */ + if (i2c_smbus_write_word_data(client, 0x28, swab16(data)) == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + return 0; +} + +static u16 +vpx3220_fp_read (struct i2c_client *client, + u16 fpaddr) +{ + s16 data; + + /* Write the 16-bit address to the FPRD register */ + if (i2c_smbus_write_word_data(client, 0x26, swab16(fpaddr)) == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + if (vpx3220_fp_status(client) < 0) + return -1; + + /* Read the 16-bit data from the FPDAT register */ + data = i2c_smbus_read_word_data(client, 0x28); + if (data == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + return swab16(data); +} + +static int +vpx3220_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + u8 reg; + int ret = -1; + + while (len >= 2) { + reg = *data++; + if ((ret = + vpx3220_write(client, reg, *data++)) < 0) + break; + len -= 2; + } + + return ret; +} + +static int +vpx3220_write_fp_block (struct i2c_client *client, + const u16 *data, + unsigned int len) +{ + u8 reg; + int ret = 0; + + while (len > 1) { + reg = *data++; + ret |= vpx3220_fp_write(client, reg, *data++); + len -= 2; + } + + return ret; +} + +/* ---------------------------------------------------------------------- */ + +static const unsigned short init_ntsc[] = { + 0x1c, 0x00, /* NTSC tint angle */ + 0x88, 17, /* Window 1 vertical */ + 0x89, 240, /* Vertical lines in */ + 0x8a, 240, /* Vertical lines out */ + 0x8b, 000, /* Horizontal begin */ + 0x8c, 640, /* Horizontal length */ + 0x8d, 640, /* Number of pixels */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x173, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x13, /* NTSC M, composite input */ + 0xe7, 0x1e1, /* Enable vertical standard + * locking @ 240 lines */ +}; + +static const unsigned short init_pal[] = { + 0x88, 23, /* Window 1 vertical begin */ + 0x89, 288 + 16, /* Vertical lines in (16 lines + * skipped by the VFE) */ + 0x8a, 288 + 16, /* Vertical lines out (16 lines + * skipped by the VFE) */ + 0x8b, 16, /* Horizontal begin */ + 0x8c, 768, /* Horizontal length */ + 0x8d, 784, /* Number of pixels + * Must be >= Horizontal begin + Horizontal length */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x177, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x3d1, /* PAL B,G,H,I, composite input */ + 0xe7, 0x261, /* PAL/SECAM set to 288 + 16 lines + * change to 0x241 for 288 lines */ +}; + +static const unsigned short init_secam[] = { + 0x88, 23 - 16, /* Window 1 vertical begin */ + 0x89, 288 + 16, /* Vertical lines in (16 lines + * skipped by the VFE) */ + 0x8a, 288 + 16, /* Vertical lines out (16 lines + * skipped by the VFE) */ + 0x8b, 16, /* Horizontal begin */ + 0x8c, 768, /* Horizontal length */ + 0x8d, 784, /* Number of pixels + * Must be >= Horizontal begin + Horizontal length */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x177, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x3d5, /* SECAM, composite input */ + 0xe7, 0x261, /* PAL/SECAM set to 288 + 16 lines + * change to 0x241 for 288 lines */ +}; + +static const unsigned char init_common[] = { + 0xf2, 0x00, /* Disable all outputs */ + 0x33, 0x0d, /* Luma : VIN2, Chroma : CIN + * (clamp off) */ + 0xd8, 0xa8, /* HREF/VREF active high, VREF + * pulse = 2, Odd/Even flag */ + 0x20, 0x03, /* IF compensation 0dB/oct */ + 0xe0, 0xff, /* Open up all comparators */ + 0xe1, 0x00, + 0xe2, 0x7f, + 0xe3, 0x80, + 0xe4, 0x7f, + 0xe5, 0x80, + 0xe6, 0x00, /* Brightness set to 0 */ + 0xe7, 0xe0, /* Contrast to 1.0, noise shaping + * 10 to 8 2-bit error diffusion */ + 0xe8, 0xf8, /* YUV422, CbCr binary offset, + * ... (p.32) */ + 0xea, 0x18, /* LLC2 connected, output FIFO + * reset with VACTintern */ + 0xf0, 0x8a, /* Half full level to 10, bus + * shuffler [7:0, 23:16, 15:8] */ + 0xf1, 0x18, /* Single clock, sync mode, no + * FE delay, no HLEN counter */ + 0xf8, 0x12, /* Port A, PIXCLK, HF# & FE# + * strength to 2 */ + 0xf9, 0x24, /* Port B, HREF, VREF, PREF & + * ALPHA strength to 4 */ +}; + +static const unsigned short init_fp[] = { + 0x59, 0, + 0xa0, 2070, /* ACC reference */ + 0xa3, 0, + 0xa4, 0, + 0xa8, 30, + 0xb2, 768, + 0xbe, 27, + 0x58, 0, + 0x26, 0, + 0x4b, 0x298, /* PLL gain */ +}; + +static void +vpx3220_dump_i2c (struct i2c_client *client) +{ + int len = sizeof(init_common); + const unsigned char *data = init_common; + + while (len > 1) { + dprintk(1, + KERN_DEBUG "vpx3216b i2c reg 0x%02x data 0x%02x\n", + *data, vpx3220_read(client, *data)); + data += 2; + len -= 2; + } +} + +static int +vpx3220_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct vpx3220 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + case 0: + { + vpx3220_write_block(client, init_common, + sizeof(init_common)); + vpx3220_write_fp_block(client, init_fp, + sizeof(init_fp) >> 1); + switch (decoder->norm) { + + case VIDEO_MODE_NTSC: + vpx3220_write_fp_block(client, init_ntsc, + sizeof(init_ntsc) >> 1); + break; + + case VIDEO_MODE_PAL: + vpx3220_write_fp_block(client, init_pal, + sizeof(init_pal) >> 1); + break; + case VIDEO_MODE_SECAM: + vpx3220_write_fp_block(client, init_secam, + sizeof(init_secam) >> 1); + break; + default: + vpx3220_write_fp_block(client, init_pal, + sizeof(init_pal) >> 1); + break; + } + } + break; + + case DECODER_DUMP: + { + vpx3220_dump_i2c(client); + } + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + dprintk(1, KERN_DEBUG "%s: DECODER_GET_CAPABILITIES\n", + I2C_NAME(client)); + + cap->flags = VIDEO_DECODER_PAL | + VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | + VIDEO_DECODER_AUTO | + VIDEO_DECODER_CCIR; + cap->inputs = 3; + cap->outputs = 1; + } + break; + + case DECODER_GET_STATUS: + { + int res = 0, status; + + dprintk(1, KERN_INFO "%s: DECODER_GET_STATUS\n", + I2C_NAME(client)); + + status = vpx3220_fp_read(client, 0x0f3); + + dprintk(1, KERN_INFO "%s: status: 0x%04x\n", I2C_NAME(client), + status); + + if (status < 0) + return status; + + if ((status & 0x20) == 0) { + res |= DECODER_STATUS_GOOD | DECODER_STATUS_COLOR; + + switch (status & 0x18) { + + case 0x00: + case 0x10: + case 0x14: + case 0x18: + res |= DECODER_STATUS_PAL; + break; + + case 0x08: + res |= DECODER_STATUS_SECAM; + break; + + case 0x04: + case 0x0c: + case 0x1c: + res |= DECODER_STATUS_NTSC; + break; + } + } + + *(int *) arg = res; + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg, data; + + dprintk(1, KERN_DEBUG "%s: DECODER_SET_NORM %d\n", + I2C_NAME(client), *iarg); + switch (*iarg) { + + case VIDEO_MODE_NTSC: + vpx3220_write_fp_block(client, init_ntsc, + sizeof(init_ntsc) >> 1); + dprintk(1, KERN_INFO "%s: norm switched to NTSC\n", + I2C_NAME(client)); + break; + + case VIDEO_MODE_PAL: + vpx3220_write_fp_block(client, init_pal, + sizeof(init_pal) >> 1); + dprintk(1, KERN_INFO "%s: norm switched to PAL\n", + I2C_NAME(client)); + break; + + case VIDEO_MODE_SECAM: + vpx3220_write_fp_block(client, init_secam, + sizeof(init_secam) >> 1); + dprintk(1, KERN_INFO "%s: norm switched to SECAM\n", + I2C_NAME(client)); + break; + + case VIDEO_MODE_AUTO: + /* FIXME This is only preliminary support */ + data = vpx3220_fp_read(client, 0xf2) & 0x20; + vpx3220_fp_write(client, 0xf2, 0x00c0 | data); + dprintk(1, KERN_INFO "%s: norm switched to Auto\n", + I2C_NAME(client)); + break; + + default: + return -EINVAL; + + } + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg, data; + + /* RJ: *iarg = 0: ST8 (PCTV) input + *iarg = 1: COMPOSITE input + *iarg = 2: SVHS input */ + + const int input[3][2] = { + {0x0c, 0}, + {0x0d, 0}, + {0x0e, 1} + }; + + if (*iarg < 0 || *iarg > 2) + return -EINVAL; + + dprintk(1, KERN_INFO "%s: input switched to %s\n", + I2C_NAME(client), inputs[*iarg]); + + vpx3220_write(client, 0x33, input[*iarg][0]); + + data = vpx3220_fp_read(client, 0xf2) & ~(0x0020); + if (data < 0) + return data; + /* 0x0010 is required to latch the setting */ + vpx3220_fp_write(client, 0xf2, + data | (input[*iarg][1] << 5) | 0x0010); + + udelay(10); + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_DEBUG "%s: DECODER_ENABLE_OUTPUT %d\n", + I2C_NAME(client), *iarg); + + vpx3220_write(client, 0xf2, (*iarg ? 0x1b : 0x00)); + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want -128 to 128 we get 0-65535 */ + decoder->bright = pic->brightness; + vpx3220_write(client, 0xe6, + (decoder->bright - 32768) >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 64 we get 0-65535 */ + /* Bit 7 and 8 is for noise shaping */ + decoder->contrast = pic->contrast; + vpx3220_write(client, 0xe7, + (decoder->contrast >> 10) + 192); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 4096 we get 0-65535 */ + decoder->sat = pic->colour; + vpx3220_fp_write(client, 0xa0, + decoder->sat >> 4); + } + if (decoder->hue != pic->hue) { + /* We want -512 to 512 we get 0-65535 */ + decoder->hue = pic->hue; + vpx3220_fp_write(client, 0x1c, + ((decoder->hue - 32768) >> 6) & 0xFFF); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int +vpx3220_init_client (struct i2c_client *client) +{ + vpx3220_write_block(client, init_common, sizeof(init_common)); + vpx3220_write_fp_block(client, init_fp, sizeof(init_fp) >> 1); + /* Default to PAL */ + vpx3220_write_fp_block(client, init_pal, sizeof(init_pal) >> 1); + + return 0; +} + +/* ----------------------------------------------------------------------- + * Client managment code + */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_VPX3220 >> 1, (I2C_VPX3220 >> 1) + 4, + I2C_CLIENT_END +}; +static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; + +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END , I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .normal_i2c_range = normal_i2c_range, + .probe = probe, + .probe_range = probe_range, + .ignore = ignore, + .ignore_range = ignore_range, + .force = force +}; + +static struct i2c_driver vpx3220_i2c_driver; + +static int +vpx3220_detach_client (struct i2c_client *client) +{ + struct vpx3220 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +static int +vpx3220_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int err; + struct i2c_client *client; + struct vpx3220 *decoder; + + dprintk(1, VPX3220_DEBUG "%s\n", __func__); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality + (adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == NULL) { + return -ENOMEM; + } + + memset(client, 0, sizeof(struct i2c_client)); + + client->addr = address; + client->adapter = adapter; + client->driver = &vpx3220_i2c_driver; + client->flags = I2C_CLIENT_ALLOW_USE; + + /* Check for manufacture ID and part number */ + if (kind < 0) { + u8 id; + u16 pn; + + id = vpx3220_read(client, 0x00); + if (id != 0xec) { + dprintk(1, + KERN_INFO + "vpx3220_attach: Wrong manufacturer ID (0x%02x)\n", + id); + kfree(client); + return 0; + } + + pn = (vpx3220_read(client, 0x02) << 8) + + vpx3220_read(client, 0x01); + switch (pn) { + case 0x4680: + strlcpy(I2C_NAME(client), "vpx3220a", + sizeof(I2C_NAME(client))); + break; + case 0x4260: + strlcpy(I2C_NAME(client), "vpx3216b", + sizeof(I2C_NAME(client))); + break; + case 0x4280: + strlcpy(I2C_NAME(client), "vpx3214c", + sizeof(I2C_NAME(client))); + break; + default: + dprintk(1, + KERN_INFO + "%s: Wrong part number (0x%04x)\n", + __func__, pn); + kfree(client); + return 0; + } + } else { + strlcpy(I2C_NAME(client), "forced vpx32xx", + sizeof(I2C_NAME(client))); + } + + decoder = kmalloc(sizeof(struct vpx3220), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + memset(decoder, 0, sizeof(struct vpx3220)); + decoder->norm = VIDEO_MODE_PAL; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + i2c_set_clientdata(client, decoder); + + err = i2c_attach_client(client); + if (err) { + kfree(client); + kfree(decoder); + return err; + } + + dprintk(1, KERN_INFO "%s: vpx32xx client found at address 0x%02x\n", + I2C_NAME(client), client->addr << 1); + + vpx3220_init_client(client); + + return 0; +} + +static int +vpx3220_attach_adapter (struct i2c_adapter *adapter) +{ + int ret; + + ret = i2c_probe(adapter, &addr_data, &vpx3220_detect_client); + dprintk(1, VPX3220_DEBUG "%s: i2c_probe returned %d\n", + __func__, ret); + return ret; +} + +/* ----------------------------------------------------------------------- + * Driver initialization and cleanup code + */ + +static struct i2c_driver vpx3220_i2c_driver = { + .owner = THIS_MODULE, + .name = "vpx3220", + + .id = I2C_DRIVERID_VPX3220, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = vpx3220_attach_adapter, + .detach_client = vpx3220_detach_client, + .command = vpx3220_command, +}; + +static int __init +vpx3220_init (void) +{ + return i2c_add_driver(&vpx3220_i2c_driver); +} + +static void __exit +vpx3220_cleanup (void) +{ + i2c_del_driver(&vpx3220_i2c_driver); +} + +module_init(vpx3220_init); +module_exit(vpx3220_cleanup); + +MODULE_DESCRIPTION("vpx3220a/vpx3216b/vpx3214c video encoder driver"); +MODULE_AUTHOR("Laurent Pinchart"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/w9966.c b/drivers/media/video/w9966.c new file mode 100644 index 000000000000..c318ba32fbaf --- /dev/null +++ b/drivers/media/video/w9966.c @@ -0,0 +1,984 @@ +/* + Winbond w9966cf Webcam parport driver. + + Version 0.32 + + Copyright (C) 2001 Jakob Kemi <jakob.kemi@post.utfors.se> + + 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. +*/ +/* + Supported devices: + *Lifeview FlyCam Supra (using the Philips saa7111a chip) + + Does any other model using the w9966 interface chip exist ? + + Todo: + + *Add a working EPP mode, since DMA ECP read isn't implemented + in the parport drivers. (That's why it's so sloow) + + *Add support for other ccd-control chips than the saa7111 + please send me feedback on what kind of chips you have. + + *Add proper probing. I don't know what's wrong with the IEEE1284 + parport drivers but (IEEE1284_MODE_NIBBLE|IEEE1284_DEVICE_ID) + and nibble read seems to be broken for some peripherals. + + *Add probing for onboard SRAM, port directions etc. (if possible) + + *Add support for the hardware compressed modes (maybe using v4l2) + + *Fix better support for the capture window (no skewed images, v4l + interface to capt. window) + + *Probably some bugs that I don't know of + + Please support me by sending feedback! + + Changes: + + Alan Cox: Removed RGB mode for kernel merge, added THIS_MODULE + and owner support for newer module locks +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/videodev.h> +#include <linux/parport.h> + +//#define DEBUG // Undef me for production + +#ifdef DEBUG +#define DPRINTF(x, a...) printk(KERN_DEBUG "W9966: %s(): "x, __FUNCTION__ , ##a) +#else +#define DPRINTF(x...) +#endif + +/* + * Defines, simple typedefs etc. + */ + +#define W9966_DRIVERNAME "W9966CF Webcam" +#define W9966_MAXCAMS 4 // Maximum number of cameras +#define W9966_RBUFFER 2048 // Read buffer (must be an even number) +#define W9966_SRAMSIZE 131072 // 128kb +#define W9966_SRAMID 0x02 // check w9966cf.pdf + +// Empirically determined window limits +#define W9966_WND_MIN_X 16 +#define W9966_WND_MIN_Y 14 +#define W9966_WND_MAX_X 705 +#define W9966_WND_MAX_Y 253 +#define W9966_WND_MAX_W (W9966_WND_MAX_X - W9966_WND_MIN_X) +#define W9966_WND_MAX_H (W9966_WND_MAX_Y - W9966_WND_MIN_Y) + +// Keep track of our current state +#define W9966_STATE_PDEV 0x01 +#define W9966_STATE_CLAIMED 0x02 +#define W9966_STATE_VDEV 0x04 + +#define W9966_I2C_W_ID 0x48 +#define W9966_I2C_R_ID 0x49 +#define W9966_I2C_R_DATA 0x08 +#define W9966_I2C_R_CLOCK 0x04 +#define W9966_I2C_W_DATA 0x02 +#define W9966_I2C_W_CLOCK 0x01 + +struct w9966_dev { + unsigned char dev_state; + unsigned char i2c_state; + unsigned short ppmode; + struct parport* pport; + struct pardevice* pdev; + struct video_device vdev; + unsigned short width; + unsigned short height; + unsigned char brightness; + signed char contrast; + signed char color; + signed char hue; +}; + +/* + * Module specific properties + */ + +MODULE_AUTHOR("Jakob Kemi <jakob.kemi@post.utfors.se>"); +MODULE_DESCRIPTION("Winbond w9966cf WebCam driver (0.32)"); +MODULE_LICENSE("GPL"); + + +#ifdef MODULE +static const char* pardev[] = {[0 ... W9966_MAXCAMS] = ""}; +#else +static const char* pardev[] = {[0 ... W9966_MAXCAMS] = "aggressive"}; +#endif +module_param_array(pardev, charp, NULL, 0); +MODULE_PARM_DESC(pardev, "pardev: where to search for\n\ +\teach camera. 'aggressive' means brute-force search.\n\ +\tEg: >pardev=parport3,aggressive,parport2,parport1< would assign\n\ +\tcam 1 to parport3 and search every parport for cam 2 etc..."); + +static int parmode = 0; +module_param(parmode, int, 0); +MODULE_PARM_DESC(parmode, "parmode: transfer mode (0=auto, 1=ecp, 2=epp"); + +static int video_nr = -1; +module_param(video_nr, int, 0); + +/* + * Private data + */ + +static struct w9966_dev w9966_cams[W9966_MAXCAMS]; + +/* + * Private function declares + */ + +static inline void w9966_setState(struct w9966_dev* cam, int mask, int val); +static inline int w9966_getState(struct w9966_dev* cam, int mask, int val); +static inline void w9966_pdev_claim(struct w9966_dev *vdev); +static inline void w9966_pdev_release(struct w9966_dev *vdev); + +static int w9966_rReg(struct w9966_dev* cam, int reg); +static int w9966_wReg(struct w9966_dev* cam, int reg, int data); +#if 0 +static int w9966_rReg_i2c(struct w9966_dev* cam, int reg); +#endif +static int w9966_wReg_i2c(struct w9966_dev* cam, int reg, int data); +static int w9966_findlen(int near, int size, int maxlen); +static int w9966_calcscale(int size, int min, int max, int* beg, int* end, unsigned char* factor); +static int w9966_setup(struct w9966_dev* cam, int x1, int y1, int x2, int y2, int w, int h); + +static int w9966_init(struct w9966_dev* cam, struct parport* port); +static void w9966_term(struct w9966_dev* cam); + +static inline void w9966_i2c_setsda(struct w9966_dev* cam, int state); +static inline int w9966_i2c_setscl(struct w9966_dev* cam, int state); +static inline int w9966_i2c_getsda(struct w9966_dev* cam); +static inline int w9966_i2c_getscl(struct w9966_dev* cam); +static int w9966_i2c_wbyte(struct w9966_dev* cam, int data); +#if 0 +static int w9966_i2c_rbyte(struct w9966_dev* cam); +#endif + +static int w9966_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static ssize_t w9966_v4l_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos); + +static struct file_operations w9966_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = w9966_v4l_ioctl, + .read = w9966_v4l_read, + .llseek = no_llseek, +}; +static struct video_device w9966_template = { + .owner = THIS_MODULE, + .name = W9966_DRIVERNAME, + .type = VID_TYPE_CAPTURE | VID_TYPE_SCALES, + .hardware = VID_HARDWARE_W9966, + .fops = &w9966_fops, +}; + +/* + * Private function defines + */ + + +// Set camera phase flags, so we know what to uninit when terminating +static inline void w9966_setState(struct w9966_dev* cam, int mask, int val) +{ + cam->dev_state = (cam->dev_state & ~mask) ^ val; +} + +// Get camera phase flags +static inline int w9966_getState(struct w9966_dev* cam, int mask, int val) +{ + return ((cam->dev_state & mask) == val); +} + +// Claim parport for ourself +static inline void w9966_pdev_claim(struct w9966_dev* cam) +{ + if (w9966_getState(cam, W9966_STATE_CLAIMED, W9966_STATE_CLAIMED)) + return; + parport_claim_or_block(cam->pdev); + w9966_setState(cam, W9966_STATE_CLAIMED, W9966_STATE_CLAIMED); +} + +// Release parport for others to use +static inline void w9966_pdev_release(struct w9966_dev* cam) +{ + if (w9966_getState(cam, W9966_STATE_CLAIMED, 0)) + return; + parport_release(cam->pdev); + w9966_setState(cam, W9966_STATE_CLAIMED, 0); +} + +// Read register from W9966 interface-chip +// Expects a claimed pdev +// -1 on error, else register data (byte) +static int w9966_rReg(struct w9966_dev* cam, int reg) +{ + // ECP, read, regtransfer, REG, REG, REG, REG, REG + const unsigned char addr = 0x80 | (reg & 0x1f); + unsigned char val; + + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_ADDR) != 0) + return -1; + if (parport_write(cam->pport, &addr, 1) != 1) + return -1; + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_DATA) != 0) + return -1; + if (parport_read(cam->pport, &val, 1) != 1) + return -1; + + return val; +} + +// Write register to W9966 interface-chip +// Expects a claimed pdev +// -1 on error +static int w9966_wReg(struct w9966_dev* cam, int reg, int data) +{ + // ECP, write, regtransfer, REG, REG, REG, REG, REG + const unsigned char addr = 0xc0 | (reg & 0x1f); + const unsigned char val = data; + + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_ADDR) != 0) + return -1; + if (parport_write(cam->pport, &addr, 1) != 1) + return -1; + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_DATA) != 0) + return -1; + if (parport_write(cam->pport, &val, 1) != 1) + return -1; + + return 0; +} + +// Initialize camera device. Setup all internal flags, set a +// default video mode, setup ccd-chip, register v4l device etc.. +// Also used for 'probing' of hardware. +// -1 on error +static int w9966_init(struct w9966_dev* cam, struct parport* port) +{ + if (cam->dev_state != 0) + return -1; + + cam->pport = port; + cam->brightness = 128; + cam->contrast = 64; + cam->color = 64; + cam->hue = 0; + +// Select requested transfer mode + switch(parmode) + { + default: // Auto-detect (priority: hw-ecp, hw-epp, sw-ecp) + case 0: + if (port->modes & PARPORT_MODE_ECP) + cam->ppmode = IEEE1284_MODE_ECP; + else if (port->modes & PARPORT_MODE_EPP) + cam->ppmode = IEEE1284_MODE_EPP; + else + cam->ppmode = IEEE1284_MODE_ECP; + break; + case 1: // hw- or sw-ecp + cam->ppmode = IEEE1284_MODE_ECP; + break; + case 2: // hw- or sw-epp + cam->ppmode = IEEE1284_MODE_EPP; + break; + } + +// Tell the parport driver that we exists + cam->pdev = parport_register_device(port, "w9966", NULL, NULL, NULL, 0, NULL); + if (cam->pdev == NULL) { + DPRINTF("parport_register_device() failed\n"); + return -1; + } + w9966_setState(cam, W9966_STATE_PDEV, W9966_STATE_PDEV); + + w9966_pdev_claim(cam); + +// Setup a default capture mode + if (w9966_setup(cam, 0, 0, 1023, 1023, 200, 160) != 0) { + DPRINTF("w9966_setup() failed.\n"); + return -1; + } + + w9966_pdev_release(cam); + +// Fill in the video_device struct and register us to v4l + memcpy(&cam->vdev, &w9966_template, sizeof(struct video_device)); + cam->vdev.priv = cam; + + if (video_register_device(&cam->vdev, VFL_TYPE_GRABBER, video_nr) == -1) + return -1; + + w9966_setState(cam, W9966_STATE_VDEV, W9966_STATE_VDEV); + + // All ok + printk( + "w9966cf: Found and initialized a webcam on %s.\n", + cam->pport->name + ); + return 0; +} + + +// Terminate everything gracefully +static void w9966_term(struct w9966_dev* cam) +{ +// Unregister from v4l + if (w9966_getState(cam, W9966_STATE_VDEV, W9966_STATE_VDEV)) { + video_unregister_device(&cam->vdev); + w9966_setState(cam, W9966_STATE_VDEV, 0); + } + +// Terminate from IEEE1284 mode and release pdev block + if (w9966_getState(cam, W9966_STATE_PDEV, W9966_STATE_PDEV)) { + w9966_pdev_claim(cam); + parport_negotiate(cam->pport, IEEE1284_MODE_COMPAT); + w9966_pdev_release(cam); + } + +// Unregister from parport + if (w9966_getState(cam, W9966_STATE_PDEV, W9966_STATE_PDEV)) { + parport_unregister_device(cam->pdev); + w9966_setState(cam, W9966_STATE_PDEV, 0); + } +} + + +// Find a good length for capture window (used both for W and H) +// A bit ugly but pretty functional. The capture length +// have to match the downscale +static int w9966_findlen(int near, int size, int maxlen) +{ + int bestlen = size; + int besterr = abs(near - bestlen); + int len; + + for(len = size+1;len < maxlen;len++) + { + int err; + if ( ((64*size) %len) != 0) + continue; + + err = abs(near - len); + + // Only continue as long as we keep getting better values + if (err > besterr) + break; + + besterr = err; + bestlen = len; + } + + return bestlen; +} + +// Modify capture window (if necessary) +// and calculate downscaling +// Return -1 on error +static int w9966_calcscale(int size, int min, int max, int* beg, int* end, unsigned char* factor) +{ + int maxlen = max - min; + int len = *end - *beg + 1; + int newlen = w9966_findlen(len, size, maxlen); + int err = newlen - len; + + // Check for bad format + if (newlen > maxlen || newlen < size) + return -1; + + // Set factor (6 bit fixed) + *factor = (64*size) / newlen; + if (*factor == 64) + *factor = 0x00; // downscale is disabled + else + *factor |= 0x80; // set downscale-enable bit + + // Modify old beginning and end + *beg -= err / 2; + *end += err - (err / 2); + + // Move window if outside borders + if (*beg < min) { + *end += min - *beg; + *beg += min - *beg; + } + if (*end > max) { + *beg -= *end - max; + *end -= *end - max; + } + + return 0; +} + +// Setup the cameras capture window etc. +// Expects a claimed pdev +// return -1 on error +static int w9966_setup(struct w9966_dev* cam, int x1, int y1, int x2, int y2, int w, int h) +{ + unsigned int i; + unsigned int enh_s, enh_e; + unsigned char scale_x, scale_y; + unsigned char regs[0x1c]; + unsigned char saa7111_regs[] = { + 0x21, 0x00, 0xd8, 0x23, 0x00, 0x80, 0x80, 0x00, + 0x88, 0x10, 0x80, 0x40, 0x40, 0x00, 0x01, 0x00, + 0x48, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0xe7, 0x00, 0x00, 0xc0 + }; + + + if (w*h*2 > W9966_SRAMSIZE) + { + DPRINTF("capture window exceeds SRAM size!.\n"); + w = 200; h = 160; // Pick default values + } + + w &= ~0x1; + if (w < 2) w = 2; + if (h < 1) h = 1; + if (w > W9966_WND_MAX_W) w = W9966_WND_MAX_W; + if (h > W9966_WND_MAX_H) h = W9966_WND_MAX_H; + + cam->width = w; + cam->height = h; + + enh_s = 0; + enh_e = w*h*2; + +// Modify capture window if necessary and calculate downscaling + if ( + w9966_calcscale(w, W9966_WND_MIN_X, W9966_WND_MAX_X, &x1, &x2, &scale_x) != 0 || + w9966_calcscale(h, W9966_WND_MIN_Y, W9966_WND_MAX_Y, &y1, &y2, &scale_y) != 0 + ) return -1; + + DPRINTF( + "%dx%d, x: %d<->%d, y: %d<->%d, sx: %d/64, sy: %d/64.\n", + w, h, x1, x2, y1, y2, scale_x&~0x80, scale_y&~0x80 + ); + +// Setup registers + regs[0x00] = 0x00; // Set normal operation + regs[0x01] = 0x18; // Capture mode + regs[0x02] = scale_y; // V-scaling + regs[0x03] = scale_x; // H-scaling + + // Capture window + regs[0x04] = (x1 & 0x0ff); // X-start (8 low bits) + regs[0x05] = (x1 & 0x300)>>8; // X-start (2 high bits) + regs[0x06] = (y1 & 0x0ff); // Y-start (8 low bits) + regs[0x07] = (y1 & 0x300)>>8; // Y-start (2 high bits) + regs[0x08] = (x2 & 0x0ff); // X-end (8 low bits) + regs[0x09] = (x2 & 0x300)>>8; // X-end (2 high bits) + regs[0x0a] = (y2 & 0x0ff); // Y-end (8 low bits) + + regs[0x0c] = W9966_SRAMID; // SRAM-banks (1x 128kb) + + // Enhancement layer + regs[0x0d] = (enh_s& 0x000ff); // Enh. start (0-7) + regs[0x0e] = (enh_s& 0x0ff00)>>8; // Enh. start (8-15) + regs[0x0f] = (enh_s& 0x70000)>>16; // Enh. start (16-17/18??) + regs[0x10] = (enh_e& 0x000ff); // Enh. end (0-7) + regs[0x11] = (enh_e& 0x0ff00)>>8; // Enh. end (8-15) + regs[0x12] = (enh_e& 0x70000)>>16; // Enh. end (16-17/18??) + + // Misc + regs[0x13] = 0x40; // VEE control (raw 4:2:2) + regs[0x17] = 0x00; // ??? + regs[0x18] = cam->i2c_state = 0x00; // Serial bus + regs[0x19] = 0xff; // I/O port direction control + regs[0x1a] = 0xff; // I/O port data register + regs[0x1b] = 0x10; // ??? + + // SAA7111 chip settings + saa7111_regs[0x0a] = cam->brightness; + saa7111_regs[0x0b] = cam->contrast; + saa7111_regs[0x0c] = cam->color; + saa7111_regs[0x0d] = cam->hue; + +// Reset (ECP-fifo & serial-bus) + if (w9966_wReg(cam, 0x00, 0x03) == -1) + return -1; + +// Write regs to w9966cf chip + for (i = 0; i < 0x1c; i++) + if (w9966_wReg(cam, i, regs[i]) == -1) + return -1; + +// Write regs to saa7111 chip + for (i = 0; i < 0x20; i++) + if (w9966_wReg_i2c(cam, i, saa7111_regs[i]) == -1) + return -1; + + return 0; +} + +/* + * Ugly and primitive i2c protocol functions + */ + +// Sets the data line on the i2c bus. +// Expects a claimed pdev. +static inline void w9966_i2c_setsda(struct w9966_dev* cam, int state) +{ + if (state) + cam->i2c_state |= W9966_I2C_W_DATA; + else + cam->i2c_state &= ~W9966_I2C_W_DATA; + + w9966_wReg(cam, 0x18, cam->i2c_state); + udelay(5); +} + +// Get peripheral clock line +// Expects a claimed pdev. +static inline int w9966_i2c_getscl(struct w9966_dev* cam) +{ + const unsigned char state = w9966_rReg(cam, 0x18); + return ((state & W9966_I2C_R_CLOCK) > 0); +} + +// Sets the clock line on the i2c bus. +// Expects a claimed pdev. -1 on error +static inline int w9966_i2c_setscl(struct w9966_dev* cam, int state) +{ + unsigned long timeout; + + if (state) + cam->i2c_state |= W9966_I2C_W_CLOCK; + else + cam->i2c_state &= ~W9966_I2C_W_CLOCK; + + w9966_wReg(cam, 0x18, cam->i2c_state); + udelay(5); + + // we go to high, we also expect the peripheral to ack. + if (state) { + timeout = jiffies + 100; + while (!w9966_i2c_getscl(cam)) { + if (time_after(jiffies, timeout)) + return -1; + } + } + return 0; +} + +// Get peripheral data line +// Expects a claimed pdev. +static inline int w9966_i2c_getsda(struct w9966_dev* cam) +{ + const unsigned char state = w9966_rReg(cam, 0x18); + return ((state & W9966_I2C_R_DATA) > 0); +} + +// Write a byte with ack to the i2c bus. +// Expects a claimed pdev. -1 on error +static int w9966_i2c_wbyte(struct w9966_dev* cam, int data) +{ + int i; + for (i = 7; i >= 0; i--) + { + w9966_i2c_setsda(cam, (data >> i) & 0x01); + + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setscl(cam, 0); + } + + w9966_i2c_setsda(cam, 1); + + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setscl(cam, 0); + + return 0; +} + +// Read a data byte with ack from the i2c-bus +// Expects a claimed pdev. -1 on error +#if 0 +static int w9966_i2c_rbyte(struct w9966_dev* cam) +{ + unsigned char data = 0x00; + int i; + + w9966_i2c_setsda(cam, 1); + + for (i = 0; i < 8; i++) + { + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + data = data << 1; + if (w9966_i2c_getsda(cam)) + data |= 0x01; + + w9966_i2c_setscl(cam, 0); + } + return data; +} +#endif + +// Read a register from the i2c device. +// Expects claimed pdev. -1 on error +#if 0 +static int w9966_rReg_i2c(struct w9966_dev* cam, int reg) +{ + int data; + + w9966_i2c_setsda(cam, 0); + w9966_i2c_setscl(cam, 0); + + if ( + w9966_i2c_wbyte(cam, W9966_I2C_W_ID) == -1 || + w9966_i2c_wbyte(cam, reg) == -1 + ) + return -1; + + w9966_i2c_setsda(cam, 1); + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setsda(cam, 0); + w9966_i2c_setscl(cam, 0); + + if ( + w9966_i2c_wbyte(cam, W9966_I2C_R_ID) == -1 || + (data = w9966_i2c_rbyte(cam)) == -1 + ) + return -1; + + w9966_i2c_setsda(cam, 0); + + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setsda(cam, 1); + + return data; +} +#endif + +// Write a register to the i2c device. +// Expects claimed pdev. -1 on error +static int w9966_wReg_i2c(struct w9966_dev* cam, int reg, int data) +{ + w9966_i2c_setsda(cam, 0); + w9966_i2c_setscl(cam, 0); + + if ( + w9966_i2c_wbyte(cam, W9966_I2C_W_ID) == -1 || + w9966_i2c_wbyte(cam, reg) == -1 || + w9966_i2c_wbyte(cam, data) == -1 + ) + return -1; + + w9966_i2c_setsda(cam, 0); + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + + w9966_i2c_setsda(cam, 1); + + return 0; +} + +/* + * Video4linux interfacing + */ + +static int w9966_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct w9966_dev *cam = vdev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + static struct video_capability vcap = { + .name = W9966_DRIVERNAME, + .type = VID_TYPE_CAPTURE | VID_TYPE_SCALES, + .channels = 1, + .maxwidth = W9966_WND_MAX_W, + .maxheight = W9966_WND_MAX_H, + .minwidth = 2, + .minheight = 1, + }; + struct video_capability *cap = arg; + *cap = vcap; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *vch = arg; + if(vch->channel != 0) // We only support one channel (#0) + return -EINVAL; + memset(vch,0,sizeof(*vch)); + strcpy(vch->name, "CCD-input"); + vch->type = VIDEO_TYPE_CAMERA; + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *vch = arg; + if(vch->channel != 0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *vtune = arg; + if(vtune->tuner != 0) + return -EINVAL; + strcpy(vtune->name, "no tuner"); + vtune->rangelow = 0; + vtune->rangehigh = 0; + vtune->flags = VIDEO_TUNER_NORM; + vtune->mode = VIDEO_MODE_AUTO; + vtune->signal = 0xffff; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *vtune = arg; + if (vtune->tuner != 0) + return -EINVAL; + if (vtune->mode != VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture vpic = { + cam->brightness << 8, // brightness + (cam->hue + 128) << 8, // hue + cam->color << 9, // color + cam->contrast << 9, // contrast + 0x8000, // whiteness + 16, VIDEO_PALETTE_YUV422// bpp, palette format + }; + struct video_picture *pic = arg; + *pic = vpic; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *vpic = arg; + if (vpic->depth != 16 || vpic->palette != VIDEO_PALETTE_YUV422) + return -EINVAL; + + cam->brightness = vpic->brightness >> 8; + cam->hue = (vpic->hue >> 8) - 128; + cam->color = vpic->colour >> 9; + cam->contrast = vpic->contrast >> 9; + + w9966_pdev_claim(cam); + + if ( + w9966_wReg_i2c(cam, 0x0a, cam->brightness) == -1 || + w9966_wReg_i2c(cam, 0x0b, cam->contrast) == -1 || + w9966_wReg_i2c(cam, 0x0c, cam->color) == -1 || + w9966_wReg_i2c(cam, 0x0d, cam->hue) == -1 + ) { + w9966_pdev_release(cam); + return -EIO; + } + + w9966_pdev_release(cam); + return 0; + } + case VIDIOCSWIN: + { + int ret; + struct video_window *vwin = arg; + + if (vwin->flags != 0) + return -EINVAL; + if (vwin->clipcount != 0) + return -EINVAL; + if (vwin->width < 2 || vwin->width > W9966_WND_MAX_W) + return -EINVAL; + if (vwin->height < 1 || vwin->height > W9966_WND_MAX_H) + return -EINVAL; + + // Update camera regs + w9966_pdev_claim(cam); + ret = w9966_setup(cam, 0, 0, 1023, 1023, vwin->width, vwin->height); + w9966_pdev_release(cam); + + if (ret != 0) { + DPRINTF("VIDIOCSWIN: w9966_setup() failed.\n"); + return -EIO; + } + + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vwin = arg; + memset(vwin, 0, sizeof(*vwin)); + vwin->width = cam->width; + vwin->height = cam->height; + return 0; + } + // Unimplemented + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCKEY: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int w9966_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, w9966_v4l_do_ioctl); +} + +// Capture data +static ssize_t w9966_v4l_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct w9966_dev *cam = vdev->priv; + unsigned char addr = 0xa0; // ECP, read, CCD-transfer, 00000 + unsigned char __user *dest = (unsigned char __user *)buf; + unsigned long dleft = count; + unsigned char *tbuf; + + // Why would anyone want more than this?? + if (count > cam->width * cam->height * 2) + return -EINVAL; + + w9966_pdev_claim(cam); + w9966_wReg(cam, 0x00, 0x02); // Reset ECP-FIFO buffer + w9966_wReg(cam, 0x00, 0x00); // Return to normal operation + w9966_wReg(cam, 0x01, 0x98); // Enable capture + + // write special capture-addr and negotiate into data transfer + if ( + (parport_negotiate(cam->pport, cam->ppmode|IEEE1284_ADDR) != 0 )|| + (parport_write(cam->pport, &addr, 1) != 1 )|| + (parport_negotiate(cam->pport, cam->ppmode|IEEE1284_DATA) != 0 ) + ) { + w9966_pdev_release(cam); + return -EFAULT; + } + + tbuf = kmalloc(W9966_RBUFFER, GFP_KERNEL); + if (tbuf == NULL) { + count = -ENOMEM; + goto out; + } + + while(dleft > 0) + { + unsigned long tsize = (dleft > W9966_RBUFFER) ? W9966_RBUFFER : dleft; + + if (parport_read(cam->pport, tbuf, tsize) < tsize) { + count = -EFAULT; + goto out; + } + if (copy_to_user(dest, tbuf, tsize) != 0) { + count = -EFAULT; + goto out; + } + dest += tsize; + dleft -= tsize; + } + + w9966_wReg(cam, 0x01, 0x18); // Disable capture + +out: + kfree(tbuf); + w9966_pdev_release(cam); + + return count; +} + + +// Called once for every parport on init +static void w9966_attach(struct parport *port) +{ + int i; + + for (i = 0; i < W9966_MAXCAMS; i++) + { + if (w9966_cams[i].dev_state != 0) // Cam is already assigned + continue; + if ( + strcmp(pardev[i], "aggressive") == 0 || + strcmp(pardev[i], port->name) == 0 + ) { + if (w9966_init(&w9966_cams[i], port) != 0) + w9966_term(&w9966_cams[i]); + break; // return + } + } +} + +// Called once for every parport on termination +static void w9966_detach(struct parport *port) +{ + int i; + for (i = 0; i < W9966_MAXCAMS; i++) + if (w9966_cams[i].dev_state != 0 && w9966_cams[i].pport == port) + w9966_term(&w9966_cams[i]); +} + + +static struct parport_driver w9966_ppd = { + .name = W9966_DRIVERNAME, + .attach = w9966_attach, + .detach = w9966_detach, +}; + +// Module entry point +static int __init w9966_mod_init(void) +{ + int i; + for (i = 0; i < W9966_MAXCAMS; i++) + w9966_cams[i].dev_state = 0; + + return parport_register_driver(&w9966_ppd); +} + +// Module cleanup +static void __exit w9966_mod_term(void) +{ + parport_unregister_driver(&w9966_ppd); +} + +module_init(w9966_mod_init); +module_exit(w9966_mod_term); diff --git a/drivers/media/video/zoran.h b/drivers/media/video/zoran.h new file mode 100644 index 000000000000..9fe6ad3b6352 --- /dev/null +++ b/drivers/media/video/zoran.h @@ -0,0 +1,515 @@ +/* + * zoran - Iomega Buz driver + * + * Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> + * + * based on + * + * zoran.0.0.3 Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * and + * + * bttv - Bt848 frame grabber driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _BUZ_H_ +#define _BUZ_H_ + +struct zoran_requestbuffers { + unsigned long count; /* Number of buffers for MJPEG grabbing */ + unsigned long size; /* Size PER BUFFER in bytes */ +}; + +struct zoran_sync { + unsigned long frame; /* number of buffer that has been free'd */ + unsigned long length; /* number of code bytes in buffer (capture only) */ + unsigned long seq; /* frame sequence number */ + struct timeval timestamp; /* timestamp */ +}; + +struct zoran_status { + int input; /* Input channel, has to be set prior to BUZIOC_G_STATUS */ + int signal; /* Returned: 1 if valid video signal detected */ + int norm; /* Returned: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */ + int color; /* Returned: 1 if color signal detected */ +}; + +struct zoran_params { + + /* The following parameters can only be queried */ + + int major_version; /* Major version number of driver */ + int minor_version; /* Minor version number of driver */ + + /* Main control parameters */ + + int input; /* Input channel: 0 = Composite, 1 = S-VHS */ + int norm; /* Norm: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */ + int decimation; /* decimation of captured video, + * enlargement of video played back. + * Valid values are 1, 2, 4 or 0. + * 0 is a special value where the user + * has full control over video scaling */ + + /* The following parameters only have to be set if decimation==0, + * for other values of decimation they provide the data how the image is captured */ + + int HorDcm; /* Horizontal decimation: 1, 2 or 4 */ + int VerDcm; /* Vertical decimation: 1 or 2 */ + int TmpDcm; /* Temporal decimation: 1 or 2, + * if TmpDcm==2 in capture every second frame is dropped, + * in playback every frame is played twice */ + int field_per_buff; /* Number of fields per buffer: 1 or 2 */ + int img_x; /* start of image in x direction */ + int img_y; /* start of image in y direction */ + int img_width; /* image width BEFORE decimation, + * must be a multiple of HorDcm*16 */ + int img_height; /* image height BEFORE decimation, + * must be a multiple of VerDcm*8 */ + + /* --- End of parameters for decimation==0 only --- */ + + /* JPEG control parameters */ + + int quality; /* Measure for quality of compressed images. + * Scales linearly with the size of the compressed images. + * Must be beetween 0 and 100, 100 is a compression + * ratio of 1:4 */ + + int odd_even; /* Which field should come first ??? */ + + int APPn; /* Number of APP segment to be written, must be 0..15 */ + int APP_len; /* Length of data in JPEG APPn segment */ + char APP_data[60]; /* Data in the JPEG APPn segment. */ + + int COM_len; /* Length of data in JPEG COM segment */ + char COM_data[60]; /* Data in JPEG COM segment */ + + unsigned long jpeg_markers; /* Which markers should go into the JPEG output. + * Unless you exactly know what you do, leave them untouched. + * Inluding less markers will make the resulting code + * smaller, but there will be fewer aplications + * which can read it. + * The presence of the APP and COM marker is + * influenced by APP0_len and COM_len ONLY! */ +#define JPEG_MARKER_DHT (1<<3) /* Define Huffman Tables */ +#define JPEG_MARKER_DQT (1<<4) /* Define Quantization Tables */ +#define JPEG_MARKER_DRI (1<<5) /* Define Restart Interval */ +#define JPEG_MARKER_COM (1<<6) /* Comment segment */ +#define JPEG_MARKER_APP (1<<7) /* App segment, driver will allways use APP0 */ + + int VFIFO_FB; /* Flag for enabling Video Fifo Feedback. + * If this flag is turned on and JPEG decompressing + * is going to the screen, the decompress process + * is stopped every time the Video Fifo is full. + * This enables a smooth decompress to the screen + * but the video output signal will get scrambled */ + + /* Misc */ + + char reserved[312]; /* Makes 512 bytes for this structure */ +}; + +/* +Private IOCTL to set up for displaying MJPEG +*/ +#define BUZIOC_G_PARAMS _IOR ('v', BASE_VIDIOCPRIVATE+0, struct zoran_params) +#define BUZIOC_S_PARAMS _IOWR('v', BASE_VIDIOCPRIVATE+1, struct zoran_params) +#define BUZIOC_REQBUFS _IOWR('v', BASE_VIDIOCPRIVATE+2, struct zoran_requestbuffers) +#define BUZIOC_QBUF_CAPT _IOW ('v', BASE_VIDIOCPRIVATE+3, int) +#define BUZIOC_QBUF_PLAY _IOW ('v', BASE_VIDIOCPRIVATE+4, int) +#define BUZIOC_SYNC _IOR ('v', BASE_VIDIOCPRIVATE+5, struct zoran_sync) +#define BUZIOC_G_STATUS _IOWR('v', BASE_VIDIOCPRIVATE+6, struct zoran_status) + + +#ifdef __KERNEL__ + +#define MAJOR_VERSION 0 /* driver major version */ +#define MINOR_VERSION 9 /* driver minor version */ +#define RELEASE_VERSION 5 /* release version */ + +#define ZORAN_NAME "ZORAN" /* name of the device */ + +#define ZR_DEVNAME(zr) ((zr)->name) + +#define BUZ_MAX_WIDTH (zr->timing->Wa) +#define BUZ_MAX_HEIGHT (zr->timing->Ha) +#define BUZ_MIN_WIDTH 32 /* never display less than 32 pixels */ +#define BUZ_MIN_HEIGHT 24 /* never display less than 24 rows */ + +#define BUZ_NUM_STAT_COM 4 +#define BUZ_MASK_STAT_COM 3 + +#define BUZ_MAX_FRAME 256 /* Must be a power of 2 */ +#define BUZ_MASK_FRAME 255 /* Must be BUZ_MAX_FRAME-1 */ + +#define BUZ_MAX_INPUT 8 + +#if VIDEO_MAX_FRAME <= 32 +# define V4L_MAX_FRAME 32 +#elif VIDEO_MAX_FRAME <= 64 +# define V4L_MAX_FRAME 64 +#else +# error "Too many video frame buffers to handle" +#endif +#define V4L_MASK_FRAME (V4L_MAX_FRAME - 1) + +#define MAX_KMALLOC_MEM (128*1024) + +#include "zr36057.h" + +enum card_type { + UNKNOWN = -1, + + /* Pinnacle/Miro */ + DC10_old, /* DC30 like */ + DC10_new, /* DC10plus like */ + DC10plus, + DC30, + DC30plus, + + /* Linux Media Labs */ + LML33, + LML33R10, + + /* Iomega */ + BUZ, + + /* total number of cards */ + NUM_CARDS +}; + +enum zoran_codec_mode { + BUZ_MODE_IDLE, /* nothing going on */ + BUZ_MODE_MOTION_COMPRESS, /* grabbing frames */ + BUZ_MODE_MOTION_DECOMPRESS, /* playing frames */ + BUZ_MODE_STILL_COMPRESS, /* still frame conversion */ + BUZ_MODE_STILL_DECOMPRESS /* still frame conversion */ +}; + +enum zoran_buffer_state { + BUZ_STATE_USER, /* buffer is owned by application */ + BUZ_STATE_PEND, /* buffer is queued in pend[] ready to feed to I/O */ + BUZ_STATE_DMA, /* buffer is queued in dma[] for I/O */ + BUZ_STATE_DONE /* buffer is ready to return to application */ +}; + +enum zoran_map_mode { + ZORAN_MAP_MODE_RAW, + ZORAN_MAP_MODE_JPG_REC, +#define ZORAN_MAP_MODE_JPG ZORAN_MAP_MODE_JPG_REC + ZORAN_MAP_MODE_JPG_PLAY, +}; + +enum gpio_type { + GPIO_JPEG_SLEEP = 0, + GPIO_JPEG_RESET, + GPIO_JPEG_FRAME, + GPIO_VID_DIR, + GPIO_VID_EN, + GPIO_VID_RESET, + GPIO_CLK_SEL1, + GPIO_CLK_SEL2, + GPIO_MAX, +}; + +enum gpcs_type { + GPCS_JPEG_RESET = 0, + GPCS_JPEG_START, + GPCS_MAX, +}; + +struct zoran_format { + char *name; + int palette; + __u32 fourcc; + int colorspace; + int depth; + __u32 flags; +}; +/* flags */ +#define ZORAN_FORMAT_COMPRESSED 1<<0 +#define ZORAN_FORMAT_OVERLAY 1<<1 +#define ZORAN_FORMAT_CAPTURE 1<<2 +#define ZORAN_FORMAT_PLAYBACK 1<<3 + +/* overlay-settings */ +struct zoran_overlay_settings { + int is_set; + int x, y, width, height; /* position */ + int clipcount; /* position and number of clips */ + const struct zoran_format *format; /* overlay format */ +}; + +/* v4l-capture settings */ +struct zoran_v4l_settings { + int width, height, bytesperline; /* capture size */ + const struct zoran_format *format; /* capture format */ +}; + +/* whoops, this one is undeclared if !v4l2 */ +#ifndef HAVE_V4L2 +struct v4l2_jpegcompression { + int quality; + int APPn; + int APP_len; + char APP_data[60]; + int COM_len; + char COM_data[60]; + __u32 jpeg_markers; + __u8 reserved[116]; +}; +#endif + +/* jpg-capture/-playback settings */ +struct zoran_jpg_settings { + int decimation; /* this bit is used to set everything to default */ + int HorDcm, VerDcm, TmpDcm; /* capture decimation settings (TmpDcm=1 means both fields) */ + int field_per_buff, odd_even; /* field-settings (odd_even=1 (+TmpDcm=1) means top-field-first) */ + int img_x, img_y, img_width, img_height; /* crop settings (subframe capture) */ + struct v4l2_jpegcompression jpg_comp; /* JPEG-specific capture settings */ +}; + +struct zoran_mapping { + struct file *file; + int count; +}; + +struct zoran_jpg_buffer { + struct zoran_mapping *map; + u32 *frag_tab; /* addresses of frag table */ + u32 frag_tab_bus; /* same value cached to save time in ISR */ + enum zoran_buffer_state state; /* non-zero if corresponding buffer is in use in grab queue */ + struct zoran_sync bs; /* DONE: info to return to application */ +}; + +struct zoran_v4l_buffer { + struct zoran_mapping *map; + char *fbuffer; /* virtual address of frame buffer */ + unsigned long fbuffer_phys; /* physical address of frame buffer */ + unsigned long fbuffer_bus; /* bus address of frame buffer */ + enum zoran_buffer_state state; /* state: unused/pending/done */ + struct zoran_sync bs; /* DONE: info to return to application */ +}; + +enum zoran_lock_activity { + ZORAN_FREE, /* free for use */ + ZORAN_ACTIVE, /* active but unlocked */ + ZORAN_LOCKED, /* locked */ +}; + +/* buffer collections */ +struct zoran_jpg_struct { + enum zoran_lock_activity active; /* feature currently in use? */ + struct zoran_jpg_buffer buffer[BUZ_MAX_FRAME]; /* buffers */ + int num_buffers, buffer_size; + u8 allocated; /* Flag if buffers are allocated */ + u8 ready_to_be_freed; /* hack - see zoran_driver.c */ + u8 need_contiguous; /* Flag if contiguous buffers are needed */ +}; + +struct zoran_v4l_struct { + enum zoran_lock_activity active; /* feature currently in use? */ + struct zoran_v4l_buffer buffer[VIDEO_MAX_FRAME]; /* buffers */ + int num_buffers, buffer_size; + u8 allocated; /* Flag if buffers are allocated */ + u8 ready_to_be_freed; /* hack - see zoran_driver.c */ +}; + +struct zoran; + +/* zoran_fh contains per-open() settings */ +struct zoran_fh { + struct zoran *zr; + + enum zoran_map_mode map_mode; /* Flag which bufferset will map by next mmap() */ + + struct zoran_overlay_settings overlay_settings; + u32 *overlay_mask; /* overlay mask */ + enum zoran_lock_activity overlay_active; /* feature currently in use? */ + + struct zoran_v4l_settings v4l_settings; /* structure with a lot of things to play with */ + struct zoran_v4l_struct v4l_buffers; /* V4L buffers' info */ + + struct zoran_jpg_settings jpg_settings; /* structure with a lot of things to play with */ + struct zoran_jpg_struct jpg_buffers; /* MJPEG buffers' info */ +}; + +struct card_info { + enum card_type type; + char name[32]; + u16 i2c_decoder, i2c_encoder; /* I2C types */ + u16 video_vfe, video_codec; /* videocodec types */ + u16 audio_chip; /* audio type */ + u16 vendor_id, device_id; /* subsystem vendor/device ID */ + + int inputs; /* number of video inputs */ + struct input { + int muxsel; + char name[32]; + } input[BUZ_MAX_INPUT]; + + int norms; + struct tvnorm *tvn[3]; /* supported TV norms */ + + u32 jpeg_int; /* JPEG interrupt */ + u32 vsync_int; /* VSYNC interrupt */ + s8 gpio[GPIO_MAX]; + u8 gpcs[GPCS_MAX]; + + struct vfe_polarity vfe_pol; + u8 gpio_pol[GPIO_MAX]; + + /* is the /GWS line conected? */ + u8 gws_not_connected; + + void (*init) (struct zoran * zr); +}; + +struct zoran { + struct video_device *video_dev; + + struct i2c_adapter i2c_adapter; /* */ + struct i2c_algo_bit_data i2c_algo; /* */ + u32 i2cbr; + + struct i2c_client *decoder; /* video decoder i2c client */ + struct i2c_client *encoder; /* video encoder i2c client */ + + struct videocodec *codec; /* video codec */ + struct videocodec *vfe; /* video front end */ + + struct semaphore resource_lock; /* prevent evil stuff */ + + u8 initialized; /* flag if zoran has been correctly initalized */ + int user; /* number of current users */ + struct card_info card; + struct tvnorm *timing; + + unsigned short id; /* number of this device */ + char name[32]; /* name of this device */ + struct pci_dev *pci_dev; /* PCI device */ + unsigned char revision; /* revision of zr36057 */ + unsigned int zr36057_adr; /* bus address of IO mem returned by PCI BIOS */ + unsigned char __iomem *zr36057_mem;/* pointer to mapped IO memory */ + + spinlock_t spinlock; /* Spinlock */ + + /* Video for Linux parameters */ + int input, norm; /* card's norm and input - norm=VIDEO_MODE_* */ + int hue, saturation, contrast, brightness; /* Current picture params */ + struct video_buffer buffer; /* Current buffer params */ + struct zoran_overlay_settings overlay_settings; + u32 *overlay_mask; /* overlay mask */ + enum zoran_lock_activity overlay_active; /* feature currently in use? */ + + wait_queue_head_t v4l_capq; + + int v4l_overlay_active; /* Overlay grab is activated */ + int v4l_memgrab_active; /* Memory grab is activated */ + + int v4l_grab_frame; /* Frame number being currently grabbed */ +#define NO_GRAB_ACTIVE (-1) + unsigned long v4l_grab_seq; /* Number of frames grabbed */ + struct zoran_v4l_settings v4l_settings; /* structure with a lot of things to play with */ + + /* V4L grab queue of frames pending */ + unsigned long v4l_pend_head; + unsigned long v4l_pend_tail; + unsigned long v4l_sync_tail; + int v4l_pend[V4L_MAX_FRAME]; + struct zoran_v4l_struct v4l_buffers; /* V4L buffers' info */ + + /* Buz MJPEG parameters */ + enum zoran_codec_mode codec_mode; /* status of codec */ + struct zoran_jpg_settings jpg_settings; /* structure with a lot of things to play with */ + + wait_queue_head_t jpg_capq; /* wait here for grab to finish */ + + /* grab queue counts/indices, mask with BUZ_MASK_STAT_COM before using as index */ + /* (dma_head - dma_tail) is number active in DMA, must be <= BUZ_NUM_STAT_COM */ + /* (value & BUZ_MASK_STAT_COM) corresponds to index in stat_com table */ + unsigned long jpg_que_head; /* Index where to put next buffer which is queued */ + unsigned long jpg_dma_head; /* Index of next buffer which goes into stat_com */ + unsigned long jpg_dma_tail; /* Index of last buffer in stat_com */ + unsigned long jpg_que_tail; /* Index of last buffer in queue */ + unsigned long jpg_seq_num; /* count of frames since grab/play started */ + unsigned long jpg_err_seq; /* last seq_num before error */ + unsigned long jpg_err_shift; + unsigned long jpg_queued_num; /* count of frames queued since grab/play started */ + + /* zr36057's code buffer table */ + u32 *stat_com; /* stat_com[i] is indexed by dma_head/tail & BUZ_MASK_STAT_COM */ + + /* (value & BUZ_MASK_FRAME) corresponds to index in pend[] queue */ + int jpg_pend[BUZ_MAX_FRAME]; + + /* array indexed by frame number */ + struct zoran_jpg_struct jpg_buffers; /* MJPEG buffers' info */ + + /* Additional stuff for testing */ +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *zoran_proc; +#else + void *zoran_proc; +#endif + int testing; + int jpeg_error; + int intr_counter_GIRQ1; + int intr_counter_GIRQ0; + int intr_counter_CodRepIRQ; + int intr_counter_JPEGRepIRQ; + int field_counter; + int IRQ1_in; + int IRQ1_out; + int JPEG_in; + int JPEG_out; + int JPEG_0; + int JPEG_1; + int END_event_missed; + int JPEG_missed; + int JPEG_error; + int num_errors; + int JPEG_max_missed; + int JPEG_min_missed; + + u32 last_isr; + unsigned long frame_num; + + wait_queue_head_t test_q; +}; + +/*The following should be done in more portable way. It depends on define + of _ALPHA_BUZ in the Makefile.*/ + +#ifdef _ALPHA_BUZ +#define btwrite(dat,adr) writel((dat), zr->zr36057_adr+(adr)) +#define btread(adr) readl(zr->zr36057_adr+(adr)) +#else +#define btwrite(dat,adr) writel((dat), zr->zr36057_mem+(adr)) +#define btread(adr) readl(zr->zr36057_mem+(adr)) +#endif + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#endif /* __kernel__ */ + +#endif diff --git a/drivers/media/video/zoran_card.c b/drivers/media/video/zoran_card.c new file mode 100644 index 000000000000..25743085b2d5 --- /dev/null +++ b/drivers/media/video/zoran_card.c @@ -0,0 +1,1583 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/vmalloc.h> + +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/sem.h> +#include <linux/kmod.h> +#include <linux/wait.h> + +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> + +#include <asm/io.h> + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_card.h" +#include "zoran_device.h" +#include "zoran_procfs.h" + +#define I2C_NAME(x) (x)->name + +extern const struct zoran_format zoran_formats[]; + +static int card[BUZ_MAX] = { -1, -1, -1, -1 }; +module_param_array(card, int, NULL, 0); +MODULE_PARM_DESC(card, "The type of card"); + +static int encoder[BUZ_MAX] = { -1, -1, -1, -1 }; +module_param_array(encoder, int, NULL, 0); +MODULE_PARM_DESC(encoder, "i2c TV encoder"); + +static int decoder[BUZ_MAX] = { -1, -1, -1, -1 }; +module_param_array(decoder, int, NULL, 0); +MODULE_PARM_DESC(decoder, "i2c TV decoder"); + +/* + The video mem address of the video card. + The driver has a little database for some videocards + to determine it from there. If your video card is not in there + you have either to give it to the driver as a parameter + or set in in a VIDIOCSFBUF ioctl + */ + +static unsigned long vidmem = 0; /* Video memory base address */ +module_param(vidmem, ulong, 0); + +/* + Default input and video norm at startup of the driver. +*/ + +static int default_input = 0; /* 0=Composite, 1=S-Video */ +module_param(default_input, int, 0); +MODULE_PARM_DESC(default_input, + "Default input (0=Composite, 1=S-Video, 2=Internal)"); + +static int default_norm = 0; /* 0=PAL, 1=NTSC 2=SECAM */ +module_param(default_norm, int, 0); +MODULE_PARM_DESC(default_norm, "Default norm (0=PAL, 1=NTSC, 2=SECAM)"); + +static int video_nr = -1; /* /dev/videoN, -1 for autodetect */ +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, "video device number"); + +/* + Number and size of grab buffers for Video 4 Linux + The vast majority of applications should not need more than 2, + the very popular BTTV driver actually does ONLY have 2. + Time sensitive applications might need more, the maximum + is VIDEO_MAX_FRAME (defined in <linux/videodev.h>). + + The size is set so that the maximum possible request + can be satisfied. Decrease it, if bigphys_area alloc'd + memory is low. If you don't have the bigphys_area patch, + set it to 128 KB. Will you allow only to grab small + images with V4L, but that's better than nothing. + + v4l_bufsize has to be given in KB ! + +*/ + +int v4l_nbufs = 2; +int v4l_bufsize = 128; /* Everybody should be able to work with this setting */ +module_param(v4l_nbufs, int, 0); +MODULE_PARM_DESC(v4l_nbufs, "Maximum number of V4L buffers to use"); +module_param(v4l_bufsize, int, 0); +MODULE_PARM_DESC(v4l_bufsize, "Maximum size per V4L buffer (in kB)"); + +int jpg_nbufs = 32; +int jpg_bufsize = 512; /* max size for 100% quality full-PAL frame */ +module_param(jpg_nbufs, int, 0); +MODULE_PARM_DESC(jpg_nbufs, "Maximum number of JPG buffers to use"); +module_param(jpg_bufsize, int, 0); +MODULE_PARM_DESC(jpg_bufsize, "Maximum size per JPG buffer (in kB)"); + +int pass_through = 0; /* 1=Pass through TV signal when device is not used */ + /* 0=Show color bar when device is not used (LML33: only if lml33dpath=1) */ +module_param(pass_through, int, 0); +MODULE_PARM_DESC(pass_through, + "Pass TV signal through to TV-out when idling"); + +static int debug = 1; +int *zr_debug = &debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +MODULE_DESCRIPTION("Zoran-36057/36067 JPEG codec driver"); +MODULE_AUTHOR("Serguei Miridonov"); +MODULE_LICENSE("GPL"); + +static struct pci_device_id zr36067_pci_tbl[] = { + {PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36057, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0} +}; +MODULE_DEVICE_TABLE(pci, zr36067_pci_tbl); + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +int zoran_num; /* number of Buzs in use */ +struct zoran zoran[BUZ_MAX]; + +/* videocodec bus functions ZR36060 */ +static u32 +zr36060_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg & 0xff)) { + return -1; + } + + data = post_office_read(zr, 0, 3) & 0xff; + return data; +} + +static void +zr36060_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg & 0xff)) { + return; + } + + post_office_write(zr, 0, 3, val & 0xff); +} + +/* videocodec bus functions ZR36050 */ +static u32 +zr36050_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) + || post_office_write(zr, 1, 0, reg >> 2)) { // reg. HIGHBYTES + return -1; + } + + data = post_office_read(zr, 0, reg & 0x03) & 0xff; // reg. LOWBYTES + read + return data; +} + +static void +zr36050_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr) + || post_office_write(zr, 1, 0, reg >> 2)) { // reg. HIGHBYTES + return; + } + + post_office_write(zr, 0, reg & 0x03, val & 0xff); // reg. LOWBYTES + wr. data +} + +/* videocodec bus functions ZR36016 */ +static u32 +zr36016_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr)) { + return -1; + } + + data = post_office_read(zr, 2, reg & 0x03) & 0xff; // read + return data; +} + +/* hack for in zoran_device.c */ +void +zr36016_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr)) { + return; + } + + post_office_write(zr, 2, reg & 0x03, val & 0x0ff); // wr. data +} + +/* + * Board specific information + */ + +static void +dc10_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: dc10_init()\n", ZR_DEVNAME(zr)); + + /* Pixel clock selection */ + GPIO(zr, 4, 0); + GPIO(zr, 5, 1); + /* Enable the video bus sync signals */ + GPIO(zr, 7, 0); +} + +static void +dc10plus_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: dc10plus_init()\n", ZR_DEVNAME(zr)); +} + +static void +buz_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: buz_init()\n", ZR_DEVNAME(zr)); + + /* some stuff from Iomega */ + pci_write_config_dword(zr->pci_dev, 0xfc, 0x90680f15); + pci_write_config_dword(zr->pci_dev, 0x0c, 0x00012020); + pci_write_config_dword(zr->pci_dev, 0xe8, 0xc0200000); +} + +static void +lml33_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: lml33_init()\n", ZR_DEVNAME(zr)); + + GPIO(zr, 2, 1); // Set Composite input/output +} + +static char * +i2cid_to_modulename (u16 i2c_id) +{ + char *name = NULL; + + switch (i2c_id) { + case I2C_DRIVERID_SAA7110: + name = "saa7110"; + break; + case I2C_DRIVERID_SAA7111A: + name = "saa7111"; + break; + case I2C_DRIVERID_SAA7114: + name = "saa7114"; + break; + case I2C_DRIVERID_SAA7185B: + name = "saa7185"; + break; + case I2C_DRIVERID_ADV7170: + name = "adv7170"; + break; + case I2C_DRIVERID_ADV7175: + name = "adv7175"; + break; + case I2C_DRIVERID_BT819: + name = "bt819"; + break; + case I2C_DRIVERID_BT856: + name = "bt856"; + break; + case I2C_DRIVERID_VPX3220: + name = "vpx3220"; + break; +/* case I2C_DRIVERID_VPX3224: + name = "vpx3224"; + break; + case I2C_DRIVERID_MSE3000: + name = "mse3000"; + break;*/ + default: + break; + } + + return name; +} + +static char * +codecid_to_modulename (u16 codecid) +{ + char *name = NULL; + + switch (codecid) { + case CODEC_TYPE_ZR36060: + name = "zr36060"; + break; + case CODEC_TYPE_ZR36050: + name = "zr36050"; + break; + case CODEC_TYPE_ZR36016: + name = "zr36016"; + break; + default: + break; + } + + return name; +} + +// struct tvnorm { +// u16 Wt, Wa, HStart, HSyncStart, Ht, Ha, VStart; +// }; + +static struct tvnorm f50sqpixel = { 944, 768, 83, 880, 625, 576, 16 }; +static struct tvnorm f60sqpixel = { 780, 640, 51, 716, 525, 480, 12 }; +static struct tvnorm f50ccir601 = { 864, 720, 75, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601 = { 858, 720, 57, 788, 525, 480, 16 }; + +static struct tvnorm f50ccir601_lml33 = { 864, 720, 75+34, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_lml33 = { 858, 720, 57+34, 788, 525, 480, 16 }; + +/* The DC10 (57/16/50) uses VActive as HSync, so HStart must be 0 */ +static struct tvnorm f50sqpixel_dc10 = { 944, 768, 0, 880, 625, 576, 0 }; +static struct tvnorm f60sqpixel_dc10 = { 780, 640, 0, 716, 525, 480, 12 }; + +/* FIXME: I cannot swap U and V in saa7114, so i do one + * pixel left shift in zoran (75 -> 74) + * (Maxim Yevtyushkin <max@linuxmedialabs.com>) */ +static struct tvnorm f50ccir601_lm33r10 = { 864, 720, 74+54, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_lm33r10 = { 858, 720, 56+54, 788, 525, 480, 16 }; + +static struct card_info zoran_cards[NUM_CARDS] __devinitdata = { + { + .type = DC10_old, + .name = "DC10(old)", + .i2c_decoder = I2C_DRIVERID_VPX3220, + /*.i2c_encoder = I2C_DRIVERID_MSE3000,*/ + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10_init, + }, { + .type = DC10_new, + .name = "DC10(new)", + .i2c_decoder = I2C_DRIVERID_SAA7110, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel}, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1}, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10plus_init, + }, { + .type = DC10plus, + .name = "DC10plus", + .vendor_id = PCI_VENDOR_ID_MIRO, + .device_id = PCI_DEVICE_ID_MIRO_DC10PLUS, + .i2c_decoder = I2C_DRIVERID_SAA7110, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel + }, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1 }, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10plus_init, + }, { + .type = DC30, + .name = "DC30", + .i2c_decoder = I2C_DRIVERID_VPX3220, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10_init, + }, { + .type = DC30plus, + .name = "DC30plus", + .vendor_id = PCI_VENDOR_ID_MIRO, + .device_id = PCI_DEVICE_ID_MIRO_DC30PLUS, + .i2c_decoder = I2C_DRIVERID_VPX3220, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10_init, + }, { + .type = LML33, + .name = "LML33", + .i2c_decoder = I2C_DRIVERID_BT819, + .i2c_encoder = I2C_DRIVERID_BT856, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = 2, + .tvn = { + &f50ccir601_lml33, + &f60ccir601_lml33, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .init = &lml33_init, + }, { + .type = LML33R10, + .name = "LML33R10", + .vendor_id = PCI_VENDOR_ID_ELECTRONICDESIGNGMBH, + .device_id = PCI_DEVICE_ID_LML_33R10, + .i2c_decoder = I2C_DRIVERID_SAA7114, + .i2c_encoder = I2C_DRIVERID_ADV7170, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = 2, + .tvn = { + &f50ccir601_lm33r10, + &f60ccir601_lm33r10, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .init = &lml33_init, + }, { + .type = BUZ, + .name = "Buz", + .vendor_id = PCI_VENDOR_ID_IOMEGA, + .device_id = PCI_DEVICE_ID_IOMEGA_BUZ, + .i2c_decoder = I2C_DRIVERID_SAA7111A, + .i2c_encoder = I2C_DRIVERID_SAA7185B, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 3, "Composite" }, + { 7, "S-Video" } + }, + .norms = 3, + .tvn = { + &f50ccir601, + &f60ccir601, + &f50ccir601 + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, -1, -1, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .init = &buz_init, + } +}; + +/* + * I2C functions + */ +/* software I2C functions */ +static int +zoran_i2c_getsda (void *data) +{ + struct zoran *zr = (struct zoran *) data; + + return (btread(ZR36057_I2CBR) >> 1) & 1; +} + +static int +zoran_i2c_getscl (void *data) +{ + struct zoran *zr = (struct zoran *) data; + + return btread(ZR36057_I2CBR) & 1; +} + +static void +zoran_i2c_setsda (void *data, + int state) +{ + struct zoran *zr = (struct zoran *) data; + + if (state) + zr->i2cbr |= 2; + else + zr->i2cbr &= ~2; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static void +zoran_i2c_setscl (void *data, + int state) +{ + struct zoran *zr = (struct zoran *) data; + + if (state) + zr->i2cbr |= 1; + else + zr->i2cbr &= ~1; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static int +zoran_i2c_client_register (struct i2c_client *client) +{ + struct zoran *zr = (struct zoran *) i2c_get_adapdata(client->adapter); + int res = 0; + + dprintk(2, + KERN_DEBUG "%s: i2c_client_register() - driver id = %d\n", + ZR_DEVNAME(zr), client->driver->id); + + down(&zr->resource_lock); + + if (zr->user > 0) { + /* we're already busy, so we keep a reference to + * them... Could do a lot of stuff here, but this + * is easiest. (Did I ever mention I'm a lazy ass?) + */ + res = -EBUSY; + goto clientreg_unlock_and_return; + } + + if (client->driver->id == zr->card.i2c_decoder) + zr->decoder = client; + else if (client->driver->id == zr->card.i2c_encoder) + zr->encoder = client; + else { + res = -ENODEV; + goto clientreg_unlock_and_return; + } + +clientreg_unlock_and_return: + up(&zr->resource_lock); + + return res; +} + +static int +zoran_i2c_client_unregister (struct i2c_client *client) +{ + struct zoran *zr = (struct zoran *) i2c_get_adapdata(client->adapter); + int res = 0; + + dprintk(2, KERN_DEBUG "%s: i2c_client_unregister()\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + + if (zr->user > 0) { + res = -EBUSY; + goto clientunreg_unlock_and_return; + } + + /* try to locate it */ + if (client == zr->encoder) { + zr->encoder = NULL; + } else if (client == zr->decoder) { + zr->decoder = NULL; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "MJPEG[%d]", zr->id); + } +clientunreg_unlock_and_return: + up(&zr->resource_lock); + return res; +} + +static struct i2c_algo_bit_data zoran_i2c_bit_data_template = { + .setsda = zoran_i2c_setsda, + .setscl = zoran_i2c_setscl, + .getsda = zoran_i2c_getsda, + .getscl = zoran_i2c_getscl, + .udelay = 10, + .mdelay = 0, + .timeout = 100, +}; + +static struct i2c_adapter zoran_i2c_adapter_template = { + I2C_DEVNAME("zr36057"), + .id = I2C_HW_B_ZR36067, + .algo = NULL, + .client_register = zoran_i2c_client_register, + .client_unregister = zoran_i2c_client_unregister, +}; + +static int +zoran_register_i2c (struct zoran *zr) +{ + memcpy(&zr->i2c_algo, &zoran_i2c_bit_data_template, + sizeof(struct i2c_algo_bit_data)); + zr->i2c_algo.data = zr; + memcpy(&zr->i2c_adapter, &zoran_i2c_adapter_template, + sizeof(struct i2c_adapter)); + strncpy(I2C_NAME(&zr->i2c_adapter), ZR_DEVNAME(zr), + sizeof(I2C_NAME(&zr->i2c_adapter)) - 1); + i2c_set_adapdata(&zr->i2c_adapter, zr); + zr->i2c_adapter.algo_data = &zr->i2c_algo; + return i2c_bit_add_bus(&zr->i2c_adapter); +} + +static void +zoran_unregister_i2c (struct zoran *zr) +{ + i2c_bit_del_bus((&zr->i2c_adapter)); +} + +/* Check a zoran_params struct for correctness, insert default params */ + +int +zoran_check_jpg_settings (struct zoran *zr, + struct zoran_jpg_settings *settings) +{ + int err = 0, err0 = 0; + + dprintk(4, + KERN_DEBUG + "%s: check_jpg_settings() - dec: %d, Hdcm: %d, Vdcm: %d, Tdcm: %d\n", + ZR_DEVNAME(zr), settings->decimation, settings->HorDcm, + settings->VerDcm, settings->TmpDcm); + dprintk(4, + KERN_DEBUG + "%s: check_jpg_settings() - x: %d, y: %d, w: %d, y: %d\n", + ZR_DEVNAME(zr), settings->img_x, settings->img_y, + settings->img_width, settings->img_height); + /* Check decimation, set default values for decimation = 1, 2, 4 */ + switch (settings->decimation) { + case 1: + + settings->HorDcm = 1; + settings->VerDcm = 1; + settings->TmpDcm = 1; + settings->field_per_buff = 2; + settings->img_x = 0; + settings->img_y = 0; + settings->img_width = BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 2: + + settings->HorDcm = 2; + settings->VerDcm = 1; + settings->TmpDcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 4: + + if (zr->card.type == DC10_new) { + dprintk(1, + KERN_DEBUG + "%s: check_jpg_settings() - HDec by 4 is not supported on the DC10\n", + ZR_DEVNAME(zr)); + err0++; + break; + } + + settings->HorDcm = 4; + settings->VerDcm = 2; + settings->TmpDcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 0: + + /* We have to check the data the user has set */ + + if (settings->HorDcm != 1 && settings->HorDcm != 2 && + (zr->card.type == DC10_new || settings->HorDcm != 4)) + err0++; + if (settings->VerDcm != 1 && settings->VerDcm != 2) + err0++; + if (settings->TmpDcm != 1 && settings->TmpDcm != 2) + err0++; + if (settings->field_per_buff != 1 && + settings->field_per_buff != 2) + err0++; + if (settings->img_x < 0) + err0++; + if (settings->img_y < 0) + err0++; + if (settings->img_width < 0) + err0++; + if (settings->img_height < 0) + err0++; + if (settings->img_x + settings->img_width > BUZ_MAX_WIDTH) + err0++; + if (settings->img_y + settings->img_height > + BUZ_MAX_HEIGHT / 2) + err0++; + if (settings->HorDcm && settings->VerDcm) { + if (settings->img_width % + (16 * settings->HorDcm) != 0) + err0++; + if (settings->img_height % + (8 * settings->VerDcm) != 0) + err0++; + } + + if (err0) { + dprintk(1, + KERN_ERR + "%s: check_jpg_settings() - error in params for decimation = 0\n", + ZR_DEVNAME(zr)); + err++; + } + break; + default: + dprintk(1, + KERN_ERR + "%s: check_jpg_settings() - decimation = %d, must be 0, 1, 2 or 4\n", + ZR_DEVNAME(zr), settings->decimation); + err++; + break; + } + + if (settings->jpg_comp.quality > 100) + settings->jpg_comp.quality = 100; + if (settings->jpg_comp.quality < 5) + settings->jpg_comp.quality = 5; + if (settings->jpg_comp.APPn < 0) + settings->jpg_comp.APPn = 0; + if (settings->jpg_comp.APPn > 15) + settings->jpg_comp.APPn = 15; + if (settings->jpg_comp.APP_len < 0) + settings->jpg_comp.APP_len = 0; + if (settings->jpg_comp.APP_len > 60) + settings->jpg_comp.APP_len = 60; + if (settings->jpg_comp.COM_len < 0) + settings->jpg_comp.COM_len = 0; + if (settings->jpg_comp.COM_len > 60) + settings->jpg_comp.COM_len = 60; + if (err) + return -EINVAL; + return 0; +} + +void +zoran_open_init_params (struct zoran *zr) +{ + int i; + + /* User must explicitly set a window */ + zr->overlay_settings.is_set = 0; + zr->overlay_mask = NULL; + zr->overlay_active = ZORAN_FREE; + + zr->v4l_memgrab_active = 0; + zr->v4l_overlay_active = 0; + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + zr->v4l_grab_seq = 0; + zr->v4l_settings.width = 192; + zr->v4l_settings.height = 144; + zr->v4l_settings.format = &zoran_formats[4]; /* YUY2 - YUV-4:2:2 packed */ + zr->v4l_settings.bytesperline = + zr->v4l_settings.width * + ((zr->v4l_settings.format->depth + 7) / 8); + + /* DMA ring stuff for V4L */ + zr->v4l_pend_tail = 0; + zr->v4l_pend_head = 0; + zr->v4l_sync_tail = 0; + zr->v4l_buffers.active = ZORAN_FREE; + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + zr->v4l_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + zr->v4l_buffers.allocated = 0; + + for (i = 0; i < BUZ_MAX_FRAME; i++) { + zr->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + zr->jpg_buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + /* Set necessary params and call zoran_check_jpg_settings to set the defaults */ + zr->jpg_settings.decimation = 1; + zr->jpg_settings.jpg_comp.quality = 50; /* default compression factor 8 */ + if (zr->card.type != BUZ) + zr->jpg_settings.odd_even = 1; + else + zr->jpg_settings.odd_even = 0; + zr->jpg_settings.jpg_comp.APPn = 0; + zr->jpg_settings.jpg_comp.APP_len = 0; /* No APPn marker */ + memset(zr->jpg_settings.jpg_comp.APP_data, 0, + sizeof(zr->jpg_settings.jpg_comp.APP_data)); + zr->jpg_settings.jpg_comp.COM_len = 0; /* No COM marker */ + memset(zr->jpg_settings.jpg_comp.COM_data, 0, + sizeof(zr->jpg_settings.jpg_comp.COM_data)); + zr->jpg_settings.jpg_comp.jpeg_markers = + JPEG_MARKER_DHT | JPEG_MARKER_DQT; + i = zoran_check_jpg_settings(zr, &zr->jpg_settings); + if (i) + dprintk(1, + KERN_ERR + "%s: zoran_open_init_params() internal error\n", + ZR_DEVNAME(zr)); + + clear_interrupt_counters(zr); + zr->testing = 0; +} + +static void __devinit +test_interrupts (struct zoran *zr) +{ + DEFINE_WAIT(wait); + int timeout, icr; + + clear_interrupt_counters(zr); + + zr->testing = 1; + icr = btread(ZR36057_ICR); + btwrite(0x78000000 | ZR36057_ICR_IntPinEn, ZR36057_ICR); + prepare_to_wait(&zr->test_q, &wait, TASK_INTERRUPTIBLE); + timeout = schedule_timeout(HZ); + finish_wait(&zr->test_q, &wait); + btwrite(0, ZR36057_ICR); + btwrite(0x78000000, ZR36057_ISR); + zr->testing = 0; + dprintk(5, KERN_INFO "%s: Testing interrupts...\n", ZR_DEVNAME(zr)); + if (timeout) { + dprintk(1, ": time spent: %d\n", 1 * HZ - timeout); + } + if (*zr_debug > 1) + print_interrupts(zr); + btwrite(icr, ZR36057_ICR); +} + +static int __devinit +zr36057_init (struct zoran *zr) +{ + unsigned long mem; + void *vdev; + unsigned mem_needed; + int j; + int two = 2; + int zero = 0; + + dprintk(1, + KERN_INFO + "%s: zr36057_init() - initializing card[%d], zr=%p\n", + ZR_DEVNAME(zr), zr->id, zr); + + /* default setup of all parameters which will persist between opens */ + zr->user = 0; + + init_waitqueue_head(&zr->v4l_capq); + init_waitqueue_head(&zr->jpg_capq); + init_waitqueue_head(&zr->test_q); + zr->jpg_buffers.allocated = 0; + zr->v4l_buffers.allocated = 0; + + zr->buffer.base = (void *) vidmem; + zr->buffer.width = 0; + zr->buffer.height = 0; + zr->buffer.depth = 0; + zr->buffer.bytesperline = 0; + + /* Avoid nonsense settings from user for default input/norm */ + if (default_norm < VIDEO_MODE_PAL && + default_norm > VIDEO_MODE_SECAM) + default_norm = VIDEO_MODE_PAL; + zr->norm = default_norm; + if (!(zr->timing = zr->card.tvn[zr->norm])) { + dprintk(1, + KERN_WARNING + "%s: zr36057_init() - default TV standard not supported by hardware. PAL will be used.\n", + ZR_DEVNAME(zr)); + zr->norm = VIDEO_MODE_PAL; + zr->timing = zr->card.tvn[zr->norm]; + } + + zr->input = default_input = (default_input ? 1 : 0); + + /* Should the following be reset at every open ? */ + zr->hue = 32768; + zr->contrast = 32768; + zr->saturation = 32768; + zr->brightness = 32768; + + /* default setup (will be repeated at every open) */ + zoran_open_init_params(zr); + + /* allocate memory *before* doing anything to the hardware + * in case allocation fails */ + mem_needed = BUZ_NUM_STAT_COM * 4; + mem = (unsigned long) kmalloc(mem_needed, GFP_KERNEL); + vdev = (void *) kmalloc(sizeof(struct video_device), GFP_KERNEL); + if (!mem || !vdev) { + dprintk(1, + KERN_ERR + "%s: zr36057_init() - kmalloc (STAT_COM) failed\n", + ZR_DEVNAME(zr)); + if (vdev) + kfree(vdev); + if (mem) + kfree((void *)mem); + return -ENOMEM; + } + memset((void *) mem, 0, mem_needed); + zr->stat_com = (u32 *) mem; + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = 1; /* mark as unavailable to zr36057 */ + } + + /* + * Now add the template and register the device unit. + */ + zr->video_dev = vdev; + memcpy(zr->video_dev, &zoran_template, sizeof(zoran_template)); + strcpy(zr->video_dev->name, ZR_DEVNAME(zr)); + if (video_register_device(zr->video_dev, VFL_TYPE_GRABBER, + video_nr) < 0) { + zoran_unregister_i2c(zr); + kfree((void *) zr->stat_com); + kfree(vdev); + return -1; + } + + zoran_init_hardware(zr); + if (*zr_debug > 2) + detect_guest_activity(zr); + test_interrupts(zr); + if (!pass_through) { + decoder_command(zr, DECODER_ENABLE_OUTPUT, &zero); + encoder_command(zr, ENCODER_SET_INPUT, &two); + } + + zr->zoran_proc = NULL; + zr->initialized = 1; + return 0; +} + +static void +zoran_release (struct zoran *zr) +{ + if (!zr->initialized) + return; + /* unregister videocodec bus */ + if (zr->codec) { + struct videocodec_master *master = zr->codec->master_data; + videocodec_detach(zr->codec); + if (master) + kfree(master); + } + if (zr->vfe) { + struct videocodec_master *master = zr->vfe->master_data; + videocodec_detach(zr->vfe); + if (master) + kfree(master); + } + + /* unregister i2c bus */ + zoran_unregister_i2c(zr); + /* disable PCI bus-mastering */ + zoran_set_pci_master(zr, 0); + /* put chip into reset */ + btwrite(0, ZR36057_SPGPPCR); + free_irq(zr->pci_dev->irq, zr); + /* unmap and free memory */ + kfree((void *) zr->stat_com); + zoran_proc_cleanup(zr); + iounmap(zr->zr36057_mem); + pci_disable_device(zr->pci_dev); + video_unregister_device(zr->video_dev); +} + +void +zoran_vdev_release (struct video_device *vdev) +{ + kfree(vdev); +} + +static struct videocodec_master * __devinit +zoran_setup_videocodec (struct zoran *zr, + int type) +{ + struct videocodec_master *m = NULL; + + m = kmalloc(sizeof(struct videocodec_master), GFP_KERNEL); + if (!m) { + dprintk(1, + KERN_ERR + "%s: zoran_setup_videocodec() - no memory\n", + ZR_DEVNAME(zr)); + return m; + } + + m->magic = 0L; /* magic not used */ + m->type = VID_HARDWARE_ZR36067; + m->flags = CODEC_FLAG_ENCODER | CODEC_FLAG_DECODER; + strncpy(m->name, ZR_DEVNAME(zr), sizeof(m->name)); + m->data = zr; + + switch (type) + { + case CODEC_TYPE_ZR36060: + m->readreg = zr36060_read; + m->writereg = zr36060_write; + m->flags |= CODEC_FLAG_JPEG | CODEC_FLAG_VFE; + break; + case CODEC_TYPE_ZR36050: + m->readreg = zr36050_read; + m->writereg = zr36050_write; + m->flags |= CODEC_FLAG_JPEG; + break; + case CODEC_TYPE_ZR36016: + m->readreg = zr36016_read; + m->writereg = zr36016_write; + m->flags |= CODEC_FLAG_VFE; + break; + } + + return m; +} + +/* + * Scan for a Buz card (actually for the PCI contoler ZR36057), + * request the irq and map the io memory + */ +static int __devinit +find_zr36057 (void) +{ + unsigned char latency, need_latency; + struct zoran *zr; + struct pci_dev *dev = NULL; + int result; + struct videocodec_master *master_vfe = NULL; + struct videocodec_master *master_codec = NULL; + int card_num; + char *i2c_enc_name, *i2c_dec_name, *codec_name, *vfe_name; + + zoran_num = 0; + while (zoran_num < BUZ_MAX && + (dev = + pci_find_device(PCI_VENDOR_ID_ZORAN, + PCI_DEVICE_ID_ZORAN_36057, dev)) != NULL) { + card_num = card[zoran_num]; + zr = &zoran[zoran_num]; + memset(zr, 0, sizeof(struct zoran)); // Just in case if previous cycle failed + zr->pci_dev = dev; + //zr->zr36057_mem = NULL; + zr->id = zoran_num; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "MJPEG[%u]", zr->id); + spin_lock_init(&zr->spinlock); + init_MUTEX(&zr->resource_lock); + if (pci_enable_device(dev)) + continue; + zr->zr36057_adr = pci_resource_start(zr->pci_dev, 0); + pci_read_config_byte(zr->pci_dev, PCI_CLASS_REVISION, + &zr->revision); + if (zr->revision < 2) { + dprintk(1, + KERN_INFO + "%s: Zoran ZR36057 (rev %d) irq: %d, memory: 0x%08x.\n", + ZR_DEVNAME(zr), zr->revision, zr->pci_dev->irq, + zr->zr36057_adr); + + if (card_num == -1) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - no card specified, please use the card=X insmod option\n", + ZR_DEVNAME(zr)); + continue; + } + } else { + int i; + unsigned short ss_vendor, ss_device; + + ss_vendor = zr->pci_dev->subsystem_vendor; + ss_device = zr->pci_dev->subsystem_device; + dprintk(1, + KERN_INFO + "%s: Zoran ZR36067 (rev %d) irq: %d, memory: 0x%08x\n", + ZR_DEVNAME(zr), zr->revision, zr->pci_dev->irq, + zr->zr36057_adr); + dprintk(1, + KERN_INFO + "%s: subsystem vendor=0x%04x id=0x%04x\n", + ZR_DEVNAME(zr), ss_vendor, ss_device); + if (card_num == -1) { + dprintk(3, + KERN_DEBUG + "%s: find_zr36057() - trying to autodetect card type\n", + ZR_DEVNAME(zr)); + for (i=0;i<NUM_CARDS;i++) { + if (ss_vendor == zoran_cards[i].vendor_id && + ss_device == zoran_cards[i].device_id) { + dprintk(3, + KERN_DEBUG + "%s: find_zr36057() - card %s detected\n", + ZR_DEVNAME(zr), + zoran_cards[i].name); + card_num = i; + break; + } + } + if (i == NUM_CARDS) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - unknown card\n", + ZR_DEVNAME(zr)); + continue; + } + } + } + + if (card_num < 0 || card_num >= NUM_CARDS) { + dprintk(2, + KERN_ERR + "%s: find_zr36057() - invalid cardnum %d\n", + ZR_DEVNAME(zr), card_num); + continue; + } + + /* even though we make this a non pointer and thus + * theoretically allow for making changes to this struct + * on a per-individual card basis at runtime, this is + * strongly discouraged. This structure is intended to + * keep general card information, no settings or anything */ + zr->card = zoran_cards[card_num]; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), + "%s[%u]", zr->card.name, zr->id); + + zr->zr36057_mem = ioremap_nocache(zr->zr36057_adr, 0x1000); + if (!zr->zr36057_mem) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - ioremap failed\n", + ZR_DEVNAME(zr)); + continue; + } + + result = request_irq(zr->pci_dev->irq, + zoran_irq, + SA_SHIRQ | SA_INTERRUPT, + ZR_DEVNAME(zr), + (void *) zr); + if (result < 0) { + if (result == -EINVAL) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - bad irq number or handler\n", + ZR_DEVNAME(zr)); + } else if (result == -EBUSY) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - IRQ %d busy, change your PnP config in BIOS\n", + ZR_DEVNAME(zr), zr->pci_dev->irq); + } else { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - can't assign irq, error code %d\n", + ZR_DEVNAME(zr), result); + } + goto zr_unmap; + } + + /* set PCI latency timer */ + pci_read_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, + &latency); + need_latency = zr->revision > 1 ? 32 : 48; + if (latency != need_latency) { + dprintk(2, + KERN_INFO + "%s: Changing PCI latency from %d to %d.\n", + ZR_DEVNAME(zr), latency, need_latency); + pci_write_config_byte(zr->pci_dev, + PCI_LATENCY_TIMER, + need_latency); + } + + zr36057_restart(zr); + /* i2c */ + dprintk(2, KERN_INFO "%s: Initializing i2c bus...\n", + ZR_DEVNAME(zr)); + + /* i2c decoder */ + if (decoder[zr->id] != -1) { + i2c_dec_name = i2cid_to_modulename(decoder[zr->id]); + zr->card.i2c_decoder = decoder[zr->id]; + } else if (zr->card.i2c_decoder != 0) { + i2c_dec_name = + i2cid_to_modulename(zr->card.i2c_decoder); + } else { + i2c_dec_name = NULL; + } + + if (i2c_dec_name) { + if ((result = request_module(i2c_dec_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load module %s: %d\n", + ZR_DEVNAME(zr), i2c_dec_name, result); + } + } + + /* i2c encoder */ + if (encoder[zr->id] != -1) { + i2c_enc_name = i2cid_to_modulename(encoder[zr->id]); + zr->card.i2c_encoder = encoder[zr->id]; + } else if (zr->card.i2c_encoder != 0) { + i2c_enc_name = + i2cid_to_modulename(zr->card.i2c_encoder); + } else { + i2c_enc_name = NULL; + } + + if (i2c_enc_name) { + if ((result = request_module(i2c_enc_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load module %s: %d\n", + ZR_DEVNAME(zr), i2c_enc_name, result); + } + } + + if (zoran_register_i2c(zr) < 0) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - can't initialize i2c bus\n", + ZR_DEVNAME(zr)); + goto zr_free_irq; + } + + dprintk(2, + KERN_INFO "%s: Initializing videocodec bus...\n", + ZR_DEVNAME(zr)); + + if (zr->card.video_codec != 0 && + (codec_name = + codecid_to_modulename(zr->card.video_codec)) != NULL) { + if ((result = request_module(codec_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load modules %s: %d\n", + ZR_DEVNAME(zr), codec_name, result); + } + } + if (zr->card.video_vfe != 0 && + (vfe_name = + codecid_to_modulename(zr->card.video_vfe)) != NULL) { + if ((result = request_module(vfe_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load modules %s: %d\n", + ZR_DEVNAME(zr), vfe_name, result); + } + } + + /* reset JPEG codec */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_reset(zr); + /* video bus enabled */ + /* display codec revision */ + if (zr->card.video_codec != 0) { + master_codec = zoran_setup_videocodec(zr, + zr->card.video_codec); + if (!master_codec) + goto zr_unreg_i2c; + zr->codec = videocodec_attach(master_codec); + if (!zr->codec) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - no codec found\n", + ZR_DEVNAME(zr)); + goto zr_free_codec; + } + if (zr->codec->type != zr->card.video_codec) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - wrong codec\n", + ZR_DEVNAME(zr)); + goto zr_detach_codec; + } + } + if (zr->card.video_vfe != 0) { + master_vfe = zoran_setup_videocodec(zr, + zr->card.video_vfe); + if (!master_vfe) + goto zr_detach_codec; + zr->vfe = videocodec_attach(master_vfe); + if (!zr->vfe) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - no VFE found\n", + ZR_DEVNAME(zr)); + goto zr_free_vfe; + } + if (zr->vfe->type != zr->card.video_vfe) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() = wrong VFE\n", + ZR_DEVNAME(zr)); + goto zr_detach_vfe; + } + } + + zoran_num++; + continue; + + // Init errors + zr_detach_vfe: + videocodec_detach(zr->vfe); + zr_free_vfe: + kfree(master_vfe); + zr_detach_codec: + videocodec_detach(zr->codec); + zr_free_codec: + kfree(master_codec); + zr_unreg_i2c: + zoran_unregister_i2c(zr); + zr_free_irq: + btwrite(0, ZR36057_SPGPPCR); + free_irq(zr->pci_dev->irq, zr); + zr_unmap: + iounmap(zr->zr36057_mem); + continue; + } + if (zoran_num == 0) { + dprintk(1, KERN_INFO "No known MJPEG cards found.\n"); + } + return zoran_num; +} + +static int __init +init_dc10_cards (void) +{ + int i; + + memset(zoran, 0, sizeof(zoran)); + printk(KERN_INFO "Zoran MJPEG board driver version %d.%d.%d\n", + MAJOR_VERSION, MINOR_VERSION, RELEASE_VERSION); + + /* Look for cards */ + if (find_zr36057() < 0) { + return -EIO; + } + if (zoran_num == 0) + return -ENODEV; + dprintk(1, KERN_INFO "%s: %d card(s) found\n", ZORAN_NAME, + zoran_num); + /* check the parameters we have been given, adjust if necessary */ + if (v4l_nbufs < 2) + v4l_nbufs = 2; + if (v4l_nbufs > VIDEO_MAX_FRAME) + v4l_nbufs = VIDEO_MAX_FRAME; + /* The user specfies the in KB, we want them in byte + * (and page aligned) */ + v4l_bufsize = PAGE_ALIGN(v4l_bufsize * 1024); + if (v4l_bufsize < 32768) + v4l_bufsize = 32768; + /* 2 MB is arbitrary but sufficient for the maximum possible images */ + if (v4l_bufsize > 2048 * 1024) + v4l_bufsize = 2048 * 1024; + if (jpg_nbufs < 4) + jpg_nbufs = 4; + if (jpg_nbufs > BUZ_MAX_FRAME) + jpg_nbufs = BUZ_MAX_FRAME; + jpg_bufsize = PAGE_ALIGN(jpg_bufsize * 1024); + if (jpg_bufsize < 8192) + jpg_bufsize = 8192; + if (jpg_bufsize > (512 * 1024)) + jpg_bufsize = 512 * 1024; + /* Use parameter for vidmem or try to find a video card */ + if (vidmem) { + dprintk(1, + KERN_INFO + "%s: Using supplied video memory base address @ 0x%lx\n", + ZORAN_NAME, vidmem); + } + + /* random nonsense */ + dprintk(5, KERN_DEBUG "Jotti is een held!\n"); + + /* some mainboards might not do PCI-PCI data transfer well */ + if (pci_pci_problems & PCIPCI_FAIL) { + dprintk(1, + KERN_WARNING + "%s: chipset may not support reliable PCI-PCI DMA\n", + ZORAN_NAME); + } + + /* take care of Natoma chipset and a revision 1 zr36057 */ + for (i = 0; i < zoran_num; i++) { + struct zoran *zr = &zoran[i]; + + if (pci_pci_problems & PCIPCI_NATOMA && zr->revision <= 1) { + zr->jpg_buffers.need_contiguous = 1; + dprintk(1, + KERN_INFO + "%s: ZR36057/Natoma bug, max. buffer size is 128K\n", + ZR_DEVNAME(zr)); + } + + if (zr36057_init(zr) < 0) { + for (i = 0; i < zoran_num; i++) + zoran_release(&zoran[i]); + return -EIO; + } + zoran_proc_init(zr); + } + + return 0; +} + +static void __exit +unload_dc10_cards (void) +{ + int i; + + for (i = 0; i < zoran_num; i++) + zoran_release(&zoran[i]); +} + +module_init(init_dc10_cards); +module_exit(unload_dc10_cards); diff --git a/drivers/media/video/zoran_card.h b/drivers/media/video/zoran_card.h new file mode 100644 index 000000000000..e5b6acd3eedc --- /dev/null +++ b/drivers/media/video/zoran_card.h @@ -0,0 +1,45 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ZORAN_CARD_H__ +#define __ZORAN_CARD_H__ + +/* Anybody who uses more than four? */ +#define BUZ_MAX 4 +extern int zoran_num; +extern struct zoran zoran[BUZ_MAX]; + +extern struct video_device zoran_template; + +extern int zoran_check_jpg_settings(struct zoran *zr, + struct zoran_jpg_settings *settings); +extern void zoran_open_init_params(struct zoran *zr); +extern void zoran_vdev_release(struct video_device *vdev); + +#endif /* __ZORAN_CARD_H__ */ diff --git a/drivers/media/video/zoran_device.c b/drivers/media/video/zoran_device.c new file mode 100644 index 000000000000..4e15afdec4c9 --- /dev/null +++ b/drivers/media/video/zoran_device.c @@ -0,0 +1,1785 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles device access (PCI/I2C/codec/...) + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <linux/byteorder/generic.h> + +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/sem.h> + +#include <linux/pci.h> +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> +#include <linux/delay.h> +#include <linux/wait.h> + +#include <asm/io.h> + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_device.h" + +#define IRQ_MASK ( ZR36057_ISR_GIRQ0 | \ + ZR36057_ISR_GIRQ1 | \ + ZR36057_ISR_JPEGRepIRQ ) + +extern const struct zoran_format zoran_formats[]; + +extern int *zr_debug; + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +static int lml33dpath = 0; /* 1 will use digital path in capture + * mode instead of analog. It can be + * used for picture adjustments using + * tool like xawtv while watching image + * on TV monitor connected to the output. + * However, due to absence of 75 Ohm + * load on Bt819 input, there will be + * some image imperfections */ + +module_param(lml33dpath, bool, 0); +MODULE_PARM_DESC(lml33dpath, + "Use digital path capture mode (on LML33 cards)"); + +static void +zr36057_init_vfe (struct zoran *zr); + +/* + * General Purpose I/O and Guest bus access + */ + +/* + * This is a bit tricky. When a board lacks a GPIO function, the corresponding + * GPIO bit number in the card_info structure is set to 0. + */ + +void +GPIO (struct zoran *zr, + int bit, + unsigned int value) +{ + u32 reg; + u32 mask; + + /* Make sure the bit number is legal + * A bit number of -1 (lacking) gives a mask of 0, + * making it harmless */ + mask = (1 << (24 + bit)) & 0xff000000; + reg = btread(ZR36057_GPPGCR1) & ~mask; + if (value) { + reg |= mask; + } + btwrite(reg, ZR36057_GPPGCR1); + udelay(1); +} + +/* + * Wait til post office is no longer busy + */ + +int +post_office_wait (struct zoran *zr) +{ + u32 por; + +// while (((por = btread(ZR36057_POR)) & (ZR36057_POR_POPen | ZR36057_POR_POTime)) == ZR36057_POR_POPen) { + while ((por = btread(ZR36057_POR)) & ZR36057_POR_POPen) { + /* wait for something to happen */ + } + if ((por & ZR36057_POR_POTime) && !zr->card.gws_not_connected) { + /* In LML33/BUZ \GWS line is not connected, so it has always timeout set */ + dprintk(1, KERN_INFO "%s: pop timeout %08x\n", ZR_DEVNAME(zr), + por); + return -1; + } + + return 0; +} + +int +post_office_write (struct zoran *zr, + unsigned int guest, + unsigned int reg, + unsigned int value) +{ + u32 por; + + por = + ZR36057_POR_PODir | ZR36057_POR_POTime | ((guest & 7) << 20) | + ((reg & 7) << 16) | (value & 0xFF); + btwrite(por, ZR36057_POR); + + return post_office_wait(zr); +} + +int +post_office_read (struct zoran *zr, + unsigned int guest, + unsigned int reg) +{ + u32 por; + + por = ZR36057_POR_POTime | ((guest & 7) << 20) | ((reg & 7) << 16); + btwrite(por, ZR36057_POR); + if (post_office_wait(zr) < 0) { + return -1; + } + + return btread(ZR36057_POR) & 0xFF; +} + +/* + * detect guests + */ + +static void +dump_guests (struct zoran *zr) +{ + if (*zr_debug > 2) { + int i, guest[8]; + + for (i = 1; i < 8; i++) { // Don't read jpeg codec here + guest[i] = post_office_read(zr, i, 0); + } + + printk(KERN_INFO "%s: Guests:", ZR_DEVNAME(zr)); + + for (i = 1; i < 8; i++) { + printk(" 0x%02x", guest[i]); + } + printk("\n"); + } +} + +static inline unsigned long +get_time (void) +{ + struct timeval tv; + + do_gettimeofday(&tv); + return (1000000 * tv.tv_sec + tv.tv_usec); +} + +void +detect_guest_activity (struct zoran *zr) +{ + int timeout, i, j, res, guest[8], guest0[8], change[8][3]; + unsigned long t0, t1; + + dump_guests(zr); + printk(KERN_INFO "%s: Detecting guests activity, please wait...\n", + ZR_DEVNAME(zr)); + for (i = 1; i < 8; i++) { // Don't read jpeg codec here + guest0[i] = guest[i] = post_office_read(zr, i, 0); + } + + timeout = 0; + j = 0; + t0 = get_time(); + while (timeout < 10000) { + udelay(10); + timeout++; + for (i = 1; (i < 8) && (j < 8); i++) { + res = post_office_read(zr, i, 0); + if (res != guest[i]) { + t1 = get_time(); + change[j][0] = (t1 - t0); + t0 = t1; + change[j][1] = i; + change[j][2] = res; + j++; + guest[i] = res; + } + } + if (j >= 8) + break; + } + printk(KERN_INFO "%s: Guests:", ZR_DEVNAME(zr)); + + for (i = 1; i < 8; i++) { + printk(" 0x%02x", guest0[i]); + } + printk("\n"); + if (j == 0) { + printk(KERN_INFO "%s: No activity detected.\n", ZR_DEVNAME(zr)); + return; + } + for (i = 0; i < j; i++) { + printk(KERN_INFO "%s: %6d: %d => 0x%02x\n", ZR_DEVNAME(zr), + change[i][0], change[i][1], change[i][2]); + } +} + +/* + * JPEG Codec access + */ + +void +jpeg_codec_sleep (struct zoran *zr, + int sleep) +{ + GPIO(zr, zr->card.gpio[GPIO_JPEG_SLEEP], !sleep); + if (!sleep) { + dprintk(3, + KERN_DEBUG + "%s: jpeg_codec_sleep() - wake GPIO=0x%08x\n", + ZR_DEVNAME(zr), btread(ZR36057_GPPGCR1)); + udelay(500); + } else { + dprintk(3, + KERN_DEBUG + "%s: jpeg_codec_sleep() - sleep GPIO=0x%08x\n", + ZR_DEVNAME(zr), btread(ZR36057_GPPGCR1)); + udelay(2); + } +} + +int +jpeg_codec_reset (struct zoran *zr) +{ + /* Take the codec out of sleep */ + jpeg_codec_sleep(zr, 0); + + if (zr->card.gpcs[GPCS_JPEG_RESET] != 0xff) { + post_office_write(zr, zr->card.gpcs[GPCS_JPEG_RESET], 0, + 0); + udelay(2); + } else { + GPIO(zr, zr->card.gpio[GPIO_JPEG_RESET], 0); + udelay(2); + GPIO(zr, zr->card.gpio[GPIO_JPEG_RESET], 1); + udelay(2); + } + + return 0; +} + +/* + * Set the registers for the size we have specified. Don't bother + * trying to understand this without the ZR36057 manual in front of + * you [AC]. + * + * PS: The manual is free for download in .pdf format from + * www.zoran.com - nicely done those folks. + */ + +static void +zr36057_adjust_vfe (struct zoran *zr, + enum zoran_codec_mode mode) +{ + u32 reg; + + switch (mode) { + case BUZ_MODE_MOTION_DECOMPRESS: + btand(~ZR36057_VFESPFR_ExtFl, ZR36057_VFESPFR); + reg = btread(ZR36057_VFEHCR); + if ((reg & (1 << 10)) && zr->card.type != LML33R10) { + reg += ((1 << 10) | 1); + } + btwrite(reg, ZR36057_VFEHCR); + break; + case BUZ_MODE_MOTION_COMPRESS: + case BUZ_MODE_IDLE: + default: + if (zr->norm == VIDEO_MODE_NTSC || + (zr->card.type == LML33R10 && + zr->norm == VIDEO_MODE_PAL)) + btand(~ZR36057_VFESPFR_ExtFl, ZR36057_VFESPFR); + else + btor(ZR36057_VFESPFR_ExtFl, ZR36057_VFESPFR); + reg = btread(ZR36057_VFEHCR); + if (!(reg & (1 << 10)) && zr->card.type != LML33R10) { + reg -= ((1 << 10) | 1); + } + btwrite(reg, ZR36057_VFEHCR); + break; + } +} + +/* + * set geometry + */ + +static void +zr36057_set_vfe (struct zoran *zr, + int video_width, + int video_height, + const struct zoran_format *format) +{ + struct tvnorm *tvn; + unsigned HStart, HEnd, VStart, VEnd; + unsigned DispMode; + unsigned VidWinWid, VidWinHt; + unsigned hcrop1, hcrop2, vcrop1, vcrop2; + unsigned Wa, We, Ha, He; + unsigned X, Y, HorDcm, VerDcm; + u32 reg; + unsigned mask_line_size; + + tvn = zr->timing; + + Wa = tvn->Wa; + Ha = tvn->Ha; + + dprintk(2, KERN_INFO "%s: set_vfe() - width = %d, height = %d\n", + ZR_DEVNAME(zr), video_width, video_height); + + if (zr->norm != VIDEO_MODE_PAL && + zr->norm != VIDEO_MODE_NTSC && + zr->norm != VIDEO_MODE_SECAM) { + dprintk(1, + KERN_ERR "%s: set_vfe() - norm = %d not valid\n", + ZR_DEVNAME(zr), zr->norm); + return; + } + if (video_width < BUZ_MIN_WIDTH || + video_height < BUZ_MIN_HEIGHT || + video_width > Wa || video_height > Ha) { + dprintk(1, KERN_ERR "%s: set_vfe: w=%d h=%d not valid\n", + ZR_DEVNAME(zr), video_width, video_height); + return; + } + + /**** zr36057 ****/ + + /* horizontal */ + VidWinWid = video_width; + X = (VidWinWid * 64 + tvn->Wa - 1) / tvn->Wa; + We = (VidWinWid * 64) / X; + HorDcm = 64 - X; + hcrop1 = 2 * ((tvn->Wa - We) / 4); + hcrop2 = tvn->Wa - We - hcrop1; + HStart = tvn->HStart ? tvn->HStart : 1; + /* (Ronald) Original comment: + * "| 1 Doesn't have any effect, tested on both a DC10 and a DC10+" + * this is false. It inverses chroma values on the LML33R10 (so Cr + * suddenly is shown as Cb and reverse, really cool effect if you + * want to see blue faces, not useful otherwise). So don't use |1. + * However, the DC10 has '0' as HStart, but does need |1, so we + * use a dirty check... + */ + HEnd = HStart + tvn->Wa - 1; + HStart += hcrop1; + HEnd -= hcrop2; + reg = ((HStart & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HStart) + | ((HEnd & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HEnd); + if (zr->card.vfe_pol.hsync_pol) + reg |= ZR36057_VFEHCR_HSPol; + btwrite(reg, ZR36057_VFEHCR); + + /* Vertical */ + DispMode = !(video_height > BUZ_MAX_HEIGHT / 2); + VidWinHt = DispMode ? video_height : video_height / 2; + Y = (VidWinHt * 64 * 2 + tvn->Ha - 1) / tvn->Ha; + He = (VidWinHt * 64) / Y; + VerDcm = 64 - Y; + vcrop1 = (tvn->Ha / 2 - He) / 2; + vcrop2 = tvn->Ha / 2 - He - vcrop1; + VStart = tvn->VStart; + VEnd = VStart + tvn->Ha / 2; // - 1; FIXME SnapShot times out with -1 in 768*576 on the DC10 - LP + VStart += vcrop1; + VEnd -= vcrop2; + reg = ((VStart & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VStart) + | ((VEnd & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VEnd); + if (zr->card.vfe_pol.vsync_pol) + reg |= ZR36057_VFEVCR_VSPol; + btwrite(reg, ZR36057_VFEVCR); + + /* scaler and pixel format */ + reg = 0; + reg |= (HorDcm << ZR36057_VFESPFR_HorDcm); + reg |= (VerDcm << ZR36057_VFESPFR_VerDcm); + reg |= (DispMode << ZR36057_VFESPFR_DispMode); + if (format->palette != VIDEO_PALETTE_YUV422) + reg |= ZR36057_VFESPFR_LittleEndian; + /* RJ: I don't know, why the following has to be the opposite + * of the corresponding ZR36060 setting, but only this way + * we get the correct colors when uncompressing to the screen */ + //reg |= ZR36057_VFESPFR_VCLKPol; /**/ + /* RJ: Don't know if that is needed for NTSC also */ + if (zr->norm != VIDEO_MODE_NTSC) + reg |= ZR36057_VFESPFR_ExtFl; // NEEDED!!!!!!! Wolfgang + reg |= ZR36057_VFESPFR_TopField; + switch (format->palette) { + + case VIDEO_PALETTE_YUV422: + reg |= ZR36057_VFESPFR_YUV422; + break; + + case VIDEO_PALETTE_RGB555: + reg |= ZR36057_VFESPFR_RGB555 | ZR36057_VFESPFR_ErrDif; + break; + + case VIDEO_PALETTE_RGB565: + reg |= ZR36057_VFESPFR_RGB565 | ZR36057_VFESPFR_ErrDif; + break; + + case VIDEO_PALETTE_RGB24: + reg |= ZR36057_VFESPFR_RGB888 | ZR36057_VFESPFR_Pack24; + break; + + case VIDEO_PALETTE_RGB32: + reg |= ZR36057_VFESPFR_RGB888; + break; + + default: + dprintk(1, + KERN_INFO "%s: set_vfe() - unknown color_fmt=%x\n", + ZR_DEVNAME(zr), format->palette); + return; + + } + if (HorDcm >= 48) { + reg |= 3 << ZR36057_VFESPFR_HFilter; /* 5 tap filter */ + } else if (HorDcm >= 32) { + reg |= 2 << ZR36057_VFESPFR_HFilter; /* 4 tap filter */ + } else if (HorDcm >= 16) { + reg |= 1 << ZR36057_VFESPFR_HFilter; /* 3 tap filter */ + } + btwrite(reg, ZR36057_VFESPFR); + + /* display configuration */ + reg = (16 << ZR36057_VDCR_MinPix) + | (VidWinHt << ZR36057_VDCR_VidWinHt) + | (VidWinWid << ZR36057_VDCR_VidWinWid); + if (pci_pci_problems & PCIPCI_TRITON) + // || zr->revision < 1) // Revision 1 has also Triton support + reg &= ~ZR36057_VDCR_Triton; + else + reg |= ZR36057_VDCR_Triton; + btwrite(reg, ZR36057_VDCR); + + /* (Ronald) don't write this if overlay_mask = NULL */ + if (zr->overlay_mask) { + /* Write overlay clipping mask data, but don't enable overlay clipping */ + /* RJ: since this makes only sense on the screen, we use + * zr->overlay_settings.width instead of video_width */ + + mask_line_size = (BUZ_MAX_WIDTH + 31) / 32; + reg = virt_to_bus(zr->overlay_mask); + btwrite(reg, ZR36057_MMTR); + reg = virt_to_bus(zr->overlay_mask + mask_line_size); + btwrite(reg, ZR36057_MMBR); + reg = + mask_line_size - (zr->overlay_settings.width + + 31) / 32; + if (DispMode == 0) + reg += mask_line_size; + reg <<= ZR36057_OCR_MaskStride; + btwrite(reg, ZR36057_OCR); + } + + zr36057_adjust_vfe(zr, zr->codec_mode); +} + +/* + * Switch overlay on or off + */ + +void +zr36057_overlay (struct zoran *zr, + int on) +{ + u32 reg; + + if (on) { + /* do the necessary settings ... */ + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); /* switch it off first */ + + zr36057_set_vfe(zr, + zr->overlay_settings.width, + zr->overlay_settings.height, + zr->overlay_settings.format); + + /* Start and length of each line MUST be 4-byte aligned. + * This should be allready checked before the call to this routine. + * All error messages are internal driver checking only! */ + + /* video display top and bottom registers */ + reg = (u32) zr->buffer.base + + zr->overlay_settings.x * + ((zr->overlay_settings.format->depth + 7) / 8) + + zr->overlay_settings.y * + zr->buffer.bytesperline; + btwrite(reg, ZR36057_VDTR); + if (reg & 3) + dprintk(1, + KERN_ERR + "%s: zr36057_overlay() - video_address not aligned\n", + ZR_DEVNAME(zr)); + if (zr->overlay_settings.height > BUZ_MAX_HEIGHT / 2) + reg += zr->buffer.bytesperline; + btwrite(reg, ZR36057_VDBR); + + /* video stride, status, and frame grab register */ + reg = zr->buffer.bytesperline - + zr->overlay_settings.width * + ((zr->overlay_settings.format->depth + 7) / 8); + if (zr->overlay_settings.height > BUZ_MAX_HEIGHT / 2) + reg += zr->buffer.bytesperline; + if (reg & 3) + dprintk(1, + KERN_ERR + "%s: zr36057_overlay() - video_stride not aligned\n", + ZR_DEVNAME(zr)); + reg = (reg << ZR36057_VSSFGR_DispStride); + reg |= ZR36057_VSSFGR_VidOvf; /* clear overflow status */ + btwrite(reg, ZR36057_VSSFGR); + + /* Set overlay clipping */ + if (zr->overlay_settings.clipcount > 0) + btor(ZR36057_OCR_OvlEnable, ZR36057_OCR); + + /* ... and switch it on */ + btor(ZR36057_VDCR_VidEn, ZR36057_VDCR); + } else { + /* Switch it off */ + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); + } +} + +/* + * The overlay mask has one bit for each pixel on a scan line, + * and the maximum window size is BUZ_MAX_WIDTH * BUZ_MAX_HEIGHT pixels. + */ + +void +write_overlay_mask (struct file *file, + struct video_clip *vp, + int count) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned mask_line_size = (BUZ_MAX_WIDTH + 31) / 32; + u32 *mask; + int x, y, width, height; + unsigned i, j, k; + u32 reg; + + /* fill mask with one bits */ + memset(fh->overlay_mask, ~0, mask_line_size * 4 * BUZ_MAX_HEIGHT); + reg = 0; + + for (i = 0; i < count; ++i) { + /* pick up local copy of clip */ + x = vp[i].x; + y = vp[i].y; + width = vp[i].width; + height = vp[i].height; + + /* trim clips that extend beyond the window */ + if (x < 0) { + width += x; + x = 0; + } + if (y < 0) { + height += y; + y = 0; + } + if (x + width > fh->overlay_settings.width) { + width = fh->overlay_settings.width - x; + } + if (y + height > fh->overlay_settings.height) { + height = fh->overlay_settings.height - y; + } + + /* ignore degenerate clips */ + if (height <= 0) { + continue; + } + if (width <= 0) { + continue; + } + + /* apply clip for each scan line */ + for (j = 0; j < height; ++j) { + /* reset bit for each pixel */ + /* this can be optimized later if need be */ + mask = fh->overlay_mask + (y + j) * mask_line_size; + for (k = 0; k < width; ++k) { + mask[(x + k) / 32] &= + ~((u32) 1 << (x + k) % 32); + } + } + } +} + +/* Enable/Disable uncompressed memory grabbing of the 36057 */ + +void +zr36057_set_memgrab (struct zoran *zr, + int mode) +{ + if (mode) { + if (btread(ZR36057_VSSFGR) & + (ZR36057_VSSFGR_SnapShot | ZR36057_VSSFGR_FrameGrab)) + dprintk(1, + KERN_WARNING + "%s: zr36057_set_memgrab(1) with SnapShot or FrameGrab on!?\n", + ZR_DEVNAME(zr)); + + /* switch on VSync interrupts */ + btwrite(IRQ_MASK, ZR36057_ISR); // Clear Interrupts + btor(zr->card.vsync_int, ZR36057_ICR); // SW + + /* enable SnapShot */ + btor(ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR); + + /* Set zr36057 video front end and enable video */ + zr36057_set_vfe(zr, zr->v4l_settings.width, + zr->v4l_settings.height, + zr->v4l_settings.format); + + zr->v4l_memgrab_active = 1; + } else { + zr->v4l_memgrab_active = 0; + + /* switch off VSync interrupts */ + btand(~zr->card.vsync_int, ZR36057_ICR); // SW + + /* reenable grabbing to screen if it was running */ + if (zr->v4l_overlay_active) { + zr36057_overlay(zr, 1); + } else { + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); + btand(~ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR); + } + } +} + +int +wait_grab_pending (struct zoran *zr) +{ + unsigned long flags; + + /* wait until all pending grabs are finished */ + + if (!zr->v4l_memgrab_active) + return 0; + + wait_event_interruptible(zr->v4l_capq, + (zr->v4l_pend_tail == zr->v4l_pend_head)); + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&zr->spinlock, flags); + zr36057_set_memgrab(zr, 0); + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +/***************************************************************************** + * * + * Set up the Buz-specific MJPEG part * + * * + *****************************************************************************/ + +static inline void +set_frame (struct zoran *zr, + int val) +{ + GPIO(zr, zr->card.gpio[GPIO_JPEG_FRAME], val); +} + +static void +set_videobus_dir (struct zoran *zr, + int val) +{ + switch (zr->card.type) { + case LML33: + case LML33R10: + if (lml33dpath == 0) + GPIO(zr, 5, val); + else + GPIO(zr, 5, 1); + break; + default: + GPIO(zr, zr->card.gpio[GPIO_VID_DIR], + zr->card.gpio_pol[GPIO_VID_DIR] ? !val : val); + break; + } +} + +static void +init_jpeg_queue (struct zoran *zr) +{ + int i; + + /* re-initialize DMA ring stuff */ + zr->jpg_que_head = 0; + zr->jpg_dma_head = 0; + zr->jpg_dma_tail = 0; + zr->jpg_que_tail = 0; + zr->jpg_seq_num = 0; + zr->JPEG_error = 0; + zr->num_errors = 0; + zr->jpg_err_seq = 0; + zr->jpg_err_shift = 0; + zr->jpg_queued_num = 0; + for (i = 0; i < zr->jpg_buffers.num_buffers; i++) { + zr->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + for (i = 0; i < BUZ_NUM_STAT_COM; i++) { + zr->stat_com[i] = cpu_to_le32(1); /* mark as unavailable to zr36057 */ + } +} + +static void +zr36057_set_jpg (struct zoran *zr, + enum zoran_codec_mode mode) +{ + struct tvnorm *tvn; + u32 reg; + + tvn = zr->timing; + + /* assert P_Reset, disable code transfer, deassert Active */ + btwrite(0, ZR36057_JPC); + + /* MJPEG compression mode */ + switch (mode) { + + case BUZ_MODE_MOTION_COMPRESS: + default: + reg = ZR36057_JMC_MJPGCmpMode; + break; + + case BUZ_MODE_MOTION_DECOMPRESS: + reg = ZR36057_JMC_MJPGExpMode; + reg |= ZR36057_JMC_SyncMstr; + /* RJ: The following is experimental - improves the output to screen */ + //if(zr->jpg_settings.VFIFO_FB) reg |= ZR36057_JMC_VFIFO_FB; // No, it doesn't. SM + break; + + case BUZ_MODE_STILL_COMPRESS: + reg = ZR36057_JMC_JPGCmpMode; + break; + + case BUZ_MODE_STILL_DECOMPRESS: + reg = ZR36057_JMC_JPGExpMode; + break; + + } + reg |= ZR36057_JMC_JPG; + if (zr->jpg_settings.field_per_buff == 1) + reg |= ZR36057_JMC_Fld_per_buff; + btwrite(reg, ZR36057_JMC); + + /* vertical */ + btor(ZR36057_VFEVCR_VSPol, ZR36057_VFEVCR); + reg = (6 << ZR36057_VSP_VsyncSize) | + (tvn->Ht << ZR36057_VSP_FrmTot); + btwrite(reg, ZR36057_VSP); + reg = ((zr->jpg_settings.img_y + tvn->VStart) << ZR36057_FVAP_NAY) | + (zr->jpg_settings.img_height << ZR36057_FVAP_PAY); + btwrite(reg, ZR36057_FVAP); + + /* horizontal */ + if (zr->card.vfe_pol.hsync_pol) + btor(ZR36057_VFEHCR_HSPol, ZR36057_VFEHCR); + else + btand(~ZR36057_VFEHCR_HSPol, ZR36057_VFEHCR); + reg = ((tvn->HSyncStart) << ZR36057_HSP_HsyncStart) | + (tvn->Wt << ZR36057_HSP_LineTot); + btwrite(reg, ZR36057_HSP); + reg = ((zr->jpg_settings.img_x + + tvn->HStart + 4) << ZR36057_FHAP_NAX) | + (zr->jpg_settings.img_width << ZR36057_FHAP_PAX); + btwrite(reg, ZR36057_FHAP); + + /* field process parameters */ + if (zr->jpg_settings.odd_even) + reg = ZR36057_FPP_Odd_Even; + else + reg = 0; + + btwrite(reg, ZR36057_FPP); + + /* Set proper VCLK Polarity, else colors will be wrong during playback */ + //btor(ZR36057_VFESPFR_VCLKPol, ZR36057_VFESPFR); + + /* code base address */ + reg = virt_to_bus(zr->stat_com); + btwrite(reg, ZR36057_JCBA); + + /* FIFO threshold (FIFO is 160. double words) */ + /* NOTE: decimal values here */ + switch (mode) { + + case BUZ_MODE_STILL_COMPRESS: + case BUZ_MODE_MOTION_COMPRESS: + if (zr->card.type != BUZ) + reg = 140; + else + reg = 60; + break; + + case BUZ_MODE_STILL_DECOMPRESS: + case BUZ_MODE_MOTION_DECOMPRESS: + reg = 20; + break; + + default: + reg = 80; + break; + + } + btwrite(reg, ZR36057_JCFT); + zr36057_adjust_vfe(zr, mode); + +} + +void +print_interrupts (struct zoran *zr) +{ + int res, noerr = 0; + + printk(KERN_INFO "%s: interrupts received:", ZR_DEVNAME(zr)); + if ((res = zr->field_counter) < -1 || res > 1) { + printk(" FD:%d", res); + } + if ((res = zr->intr_counter_GIRQ1) != 0) { + printk(" GIRQ1:%d", res); + noerr++; + } + if ((res = zr->intr_counter_GIRQ0) != 0) { + printk(" GIRQ0:%d", res); + noerr++; + } + if ((res = zr->intr_counter_CodRepIRQ) != 0) { + printk(" CodRepIRQ:%d", res); + noerr++; + } + if ((res = zr->intr_counter_JPEGRepIRQ) != 0) { + printk(" JPEGRepIRQ:%d", res); + noerr++; + } + if (zr->JPEG_max_missed) { + printk(" JPEG delays: max=%d min=%d", zr->JPEG_max_missed, + zr->JPEG_min_missed); + } + if (zr->END_event_missed) { + printk(" ENDs missed: %d", zr->END_event_missed); + } + //if (zr->jpg_queued_num) { + printk(" queue_state=%ld/%ld/%ld/%ld", zr->jpg_que_tail, + zr->jpg_dma_tail, zr->jpg_dma_head, zr->jpg_que_head); + //} + if (!noerr) { + printk(": no interrupts detected."); + } + printk("\n"); +} + +void +clear_interrupt_counters (struct zoran *zr) +{ + zr->intr_counter_GIRQ1 = 0; + zr->intr_counter_GIRQ0 = 0; + zr->intr_counter_CodRepIRQ = 0; + zr->intr_counter_JPEGRepIRQ = 0; + zr->field_counter = 0; + zr->IRQ1_in = 0; + zr->IRQ1_out = 0; + zr->JPEG_in = 0; + zr->JPEG_out = 0; + zr->JPEG_0 = 0; + zr->JPEG_1 = 0; + zr->END_event_missed = 0; + zr->JPEG_missed = 0; + zr->JPEG_max_missed = 0; + zr->JPEG_min_missed = 0x7fffffff; +} + +static u32 +count_reset_interrupt (struct zoran *zr) +{ + u32 isr; + + if ((isr = btread(ZR36057_ISR) & 0x78000000)) { + if (isr & ZR36057_ISR_GIRQ1) { + btwrite(ZR36057_ISR_GIRQ1, ZR36057_ISR); + zr->intr_counter_GIRQ1++; + } + if (isr & ZR36057_ISR_GIRQ0) { + btwrite(ZR36057_ISR_GIRQ0, ZR36057_ISR); + zr->intr_counter_GIRQ0++; + } + if (isr & ZR36057_ISR_CodRepIRQ) { + btwrite(ZR36057_ISR_CodRepIRQ, ZR36057_ISR); + zr->intr_counter_CodRepIRQ++; + } + if (isr & ZR36057_ISR_JPEGRepIRQ) { + btwrite(ZR36057_ISR_JPEGRepIRQ, ZR36057_ISR); + zr->intr_counter_JPEGRepIRQ++; + } + } + return isr; +} + +/* hack */ +extern void zr36016_write (struct videocodec *codec, + u16 reg, + u32 val); + +void +jpeg_start (struct zoran *zr) +{ + int reg; + + zr->frame_num = 0; + + /* deassert P_reset, disable code transfer, deassert Active */ + btwrite(ZR36057_JPC_P_Reset, ZR36057_JPC); + /* stop flushing the internal code buffer */ + btand(~ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + /* enable code transfer */ + btor(ZR36057_JPC_CodTrnsEn, ZR36057_JPC); + + /* clear IRQs */ + btwrite(IRQ_MASK, ZR36057_ISR); + /* enable the JPEG IRQs */ + btwrite(zr->card.jpeg_int | + ZR36057_ICR_JPEGRepIRQ | + ZR36057_ICR_IntPinEn, + ZR36057_ICR); + + set_frame(zr, 0); // \FRAME + + /* set the JPEG codec guest ID */ + reg = (zr->card.gpcs[1] << ZR36057_JCGI_JPEGuestID) | + (0 << ZR36057_JCGI_JPEGuestReg); + btwrite(reg, ZR36057_JCGI); + + if (zr->card.video_vfe == CODEC_TYPE_ZR36016 && + zr->card.video_codec == CODEC_TYPE_ZR36050) { + /* Enable processing on the ZR36016 */ + if (zr->vfe) + zr36016_write(zr->vfe, 0, 1); + + /* load the address of the GO register in the ZR36050 latch */ + post_office_write(zr, 0, 0, 0); + } + + /* assert Active */ + btor(ZR36057_JPC_Active, ZR36057_JPC); + + /* enable the Go generation */ + btor(ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(30); + + set_frame(zr, 1); // /FRAME + + dprintk(3, KERN_DEBUG "%s: jpeg_start\n", ZR_DEVNAME(zr)); +} + +void +zr36057_enable_jpg (struct zoran *zr, + enum zoran_codec_mode mode) +{ + static int zero = 0; + static int one = 1; + struct vfe_settings cap; + int field_size = + zr->jpg_buffers.buffer_size / zr->jpg_settings.field_per_buff; + + zr->codec_mode = mode; + + cap.x = zr->jpg_settings.img_x; + cap.y = zr->jpg_settings.img_y; + cap.width = zr->jpg_settings.img_width; + cap.height = zr->jpg_settings.img_height; + cap.decimation = + zr->jpg_settings.HorDcm | (zr->jpg_settings.VerDcm << 8); + cap.quality = zr->jpg_settings.jpg_comp.quality; + + switch (mode) { + + case BUZ_MODE_MOTION_COMPRESS: { + struct jpeg_app_marker app; + struct jpeg_com_marker com; + + /* In motion compress mode, the decoder output must be enabled, and + * the video bus direction set to input. + */ + set_videobus_dir(zr, 0); + decoder_command(zr, DECODER_ENABLE_OUTPUT, &one); + encoder_command(zr, ENCODER_SET_INPUT, &zero); + + /* Take the JPEG codec and the VFE out of sleep */ + jpeg_codec_sleep(zr, 0); + + /* set JPEG app/com marker */ + app.appn = zr->jpg_settings.jpg_comp.APPn; + app.len = zr->jpg_settings.jpg_comp.APP_len; + memcpy(app.data, zr->jpg_settings.jpg_comp.APP_data, 60); + zr->codec->control(zr->codec, CODEC_S_JPEG_APP_DATA, + sizeof(struct jpeg_app_marker), &app); + + com.len = zr->jpg_settings.jpg_comp.COM_len; + memcpy(com.data, zr->jpg_settings.jpg_comp.COM_data, 60); + zr->codec->control(zr->codec, CODEC_S_JPEG_COM_DATA, + sizeof(struct jpeg_com_marker), &com); + + /* Setup the JPEG codec */ + zr->codec->control(zr->codec, CODEC_S_JPEG_TDS_BYTE, + sizeof(int), &field_size); + zr->codec->set_video(zr->codec, zr->timing, &cap, + &zr->card.vfe_pol); + zr->codec->set_mode(zr->codec, CODEC_DO_COMPRESSION); + + /* Setup the VFE */ + if (zr->vfe) { + zr->vfe->control(zr->vfe, CODEC_S_JPEG_TDS_BYTE, + sizeof(int), &field_size); + zr->vfe->set_video(zr->vfe, zr->timing, &cap, + &zr->card.vfe_pol); + zr->vfe->set_mode(zr->vfe, CODEC_DO_COMPRESSION); + } + + init_jpeg_queue(zr); + zr36057_set_jpg(zr, mode); // \P_Reset, ... Video param, FIFO + + clear_interrupt_counters(zr); + dprintk(2, KERN_INFO "%s: enable_jpg(MOTION_COMPRESS)\n", + ZR_DEVNAME(zr)); + break; + } + + case BUZ_MODE_MOTION_DECOMPRESS: + /* In motion decompression mode, the decoder output must be disabled, and + * the video bus direction set to output. + */ + decoder_command(zr, DECODER_ENABLE_OUTPUT, &zero); + set_videobus_dir(zr, 1); + encoder_command(zr, ENCODER_SET_INPUT, &one); + + /* Take the JPEG codec and the VFE out of sleep */ + jpeg_codec_sleep(zr, 0); + /* Setup the VFE */ + if (zr->vfe) { + zr->vfe->set_video(zr->vfe, zr->timing, &cap, + &zr->card.vfe_pol); + zr->vfe->set_mode(zr->vfe, CODEC_DO_EXPANSION); + } + /* Setup the JPEG codec */ + zr->codec->set_video(zr->codec, zr->timing, &cap, + &zr->card.vfe_pol); + zr->codec->set_mode(zr->codec, CODEC_DO_EXPANSION); + + init_jpeg_queue(zr); + zr36057_set_jpg(zr, mode); // \P_Reset, ... Video param, FIFO + + clear_interrupt_counters(zr); + dprintk(2, KERN_INFO "%s: enable_jpg(MOTION_DECOMPRESS)\n", + ZR_DEVNAME(zr)); + break; + + case BUZ_MODE_IDLE: + default: + /* shut down processing */ + btand(~(zr->card.jpeg_int | ZR36057_ICR_JPEGRepIRQ), + ZR36057_ICR); + btwrite(zr->card.jpeg_int | ZR36057_ICR_JPEGRepIRQ, + ZR36057_ISR); + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); // \Go_en + + msleep(50); + + set_videobus_dir(zr, 0); + set_frame(zr, 1); // /FRAME + btor(ZR36057_MCTCR_CFlush, ZR36057_MCTCR); // /CFlush + btwrite(0, ZR36057_JPC); // \P_Reset,\CodTrnsEn,\Active + btand(~ZR36057_JMC_VFIFO_FB, ZR36057_JMC); + btand(~ZR36057_JMC_SyncMstr, ZR36057_JMC); + jpeg_codec_reset(zr); + jpeg_codec_sleep(zr, 1); + zr36057_adjust_vfe(zr, mode); + + decoder_command(zr, DECODER_ENABLE_OUTPUT, &one); + encoder_command(zr, ENCODER_SET_INPUT, &zero); + + dprintk(2, KERN_INFO "%s: enable_jpg(IDLE)\n", ZR_DEVNAME(zr)); + break; + + } +} + +/* when this is called the spinlock must be held */ +void +zoran_feed_stat_com (struct zoran *zr) +{ + /* move frames from pending queue to DMA */ + + int frame, i, max_stat_com; + + max_stat_com = + (zr->jpg_settings.TmpDcm == + 1) ? BUZ_NUM_STAT_COM : (BUZ_NUM_STAT_COM >> 1); + + while ((zr->jpg_dma_head - zr->jpg_dma_tail) < max_stat_com && + zr->jpg_dma_head < zr->jpg_que_head) { + + frame = zr->jpg_pend[zr->jpg_dma_head & BUZ_MASK_FRAME]; + if (zr->jpg_settings.TmpDcm == 1) { + /* fill 1 stat_com entry */ + i = (zr->jpg_dma_head - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + if (!(zr->stat_com[i] & cpu_to_le32(1))) + break; + zr->stat_com[i] = + cpu_to_le32(zr->jpg_buffers.buffer[frame].frag_tab_bus); + } else { + /* fill 2 stat_com entries */ + i = ((zr->jpg_dma_head - + zr->jpg_err_shift) & 1) * 2; + if (!(zr->stat_com[i] & cpu_to_le32(1))) + break; + zr->stat_com[i] = + cpu_to_le32(zr->jpg_buffers.buffer[frame].frag_tab_bus); + zr->stat_com[i + 1] = + cpu_to_le32(zr->jpg_buffers.buffer[frame].frag_tab_bus); + } + zr->jpg_buffers.buffer[frame].state = BUZ_STATE_DMA; + zr->jpg_dma_head++; + + } + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) + zr->jpg_queued_num++; +} + +/* when this is called the spinlock must be held */ +static void +zoran_reap_stat_com (struct zoran *zr) +{ + /* move frames from DMA queue to done queue */ + + int i; + u32 stat_com; + unsigned int seq; + unsigned int dif; + struct zoran_jpg_buffer *buffer; + int frame; + + /* In motion decompress we don't have a hardware frame counter, + * we just count the interrupts here */ + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) { + zr->jpg_seq_num++; + } + while (zr->jpg_dma_tail < zr->jpg_dma_head) { + if (zr->jpg_settings.TmpDcm == 1) + i = (zr->jpg_dma_tail - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - + zr->jpg_err_shift) & 1) * 2 + 1; + + stat_com = le32_to_cpu(zr->stat_com[i]); + + if ((stat_com & 1) == 0) { + return; + } + frame = zr->jpg_pend[zr->jpg_dma_tail & BUZ_MASK_FRAME]; + buffer = &zr->jpg_buffers.buffer[frame]; + do_gettimeofday(&buffer->bs.timestamp); + + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + buffer->bs.length = (stat_com & 0x7fffff) >> 1; + + /* update sequence number with the help of the counter in stat_com */ + + seq = ((stat_com >> 24) + zr->jpg_err_seq) & 0xff; + dif = (seq - zr->jpg_seq_num) & 0xff; + zr->jpg_seq_num += dif; + } else { + buffer->bs.length = 0; + } + buffer->bs.seq = + zr->jpg_settings.TmpDcm == + 2 ? (zr->jpg_seq_num >> 1) : zr->jpg_seq_num; + buffer->state = BUZ_STATE_DONE; + + zr->jpg_dma_tail++; + } +} + +static void +error_handler (struct zoran *zr, + u32 astat, + u32 stat) +{ + /* This is JPEG error handling part */ + if ((zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) && + (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS)) { + //dprintk(1, KERN_ERR "%s: Internal error: error handling request in mode %d\n", ZR_DEVNAME(zr), zr->codec_mode); + return; + } + + if ((stat & 1) == 0 && + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS && + zr->jpg_dma_tail - zr->jpg_que_tail >= + zr->jpg_buffers.num_buffers) { + /* No free buffers... */ + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + zr->JPEG_missed = 0; + return; + } + + if (zr->JPEG_error != 1) { + /* + * First entry: error just happened during normal operation + * + * In BUZ_MODE_MOTION_COMPRESS: + * + * Possible glitch in TV signal. In this case we should + * stop the codec and wait for good quality signal before + * restarting it to avoid further problems + * + * In BUZ_MODE_MOTION_DECOMPRESS: + * + * Bad JPEG frame: we have to mark it as processed (codec crashed + * and was not able to do it itself), and to remove it from queue. + */ + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(1); + stat = stat | (post_office_read(zr, 7, 0) & 3) << 8; + btwrite(0, ZR36057_JPC); + btor(ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + jpeg_codec_reset(zr); + jpeg_codec_sleep(zr, 1); + zr->JPEG_error = 1; + zr->num_errors++; + + /* Report error */ + if (*zr_debug > 1 && zr->num_errors <= 8) { + long frame; + frame = + zr->jpg_pend[zr->jpg_dma_tail & BUZ_MASK_FRAME]; + printk(KERN_ERR + "%s: JPEG error stat=0x%08x(0x%08x) queue_state=%ld/%ld/%ld/%ld seq=%ld frame=%ld. Codec stopped. ", + ZR_DEVNAME(zr), stat, zr->last_isr, + zr->jpg_que_tail, zr->jpg_dma_tail, + zr->jpg_dma_head, zr->jpg_que_head, + zr->jpg_seq_num, frame); + printk("stat_com frames:"); + { + int i, j; + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + for (i = 0; + i < zr->jpg_buffers.num_buffers; + i++) { + if (le32_to_cpu(zr->stat_com[j]) == + zr->jpg_buffers. + buffer[i]. + frag_tab_bus) { + printk("% d->%d", + j, i); + } + } + } + printk("\n"); + } + } + /* Find an entry in stat_com and rotate contents */ + { + int i; + + if (zr->jpg_settings.TmpDcm == 1) + i = (zr->jpg_dma_tail - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - + zr->jpg_err_shift) & 1) * 2; + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) { + /* Mimic zr36067 operation */ + zr->stat_com[i] |= cpu_to_le32(1); + if (zr->jpg_settings.TmpDcm != 1) + zr->stat_com[i + 1] |= cpu_to_le32(1); + /* Refill */ + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + /* Find an entry in stat_com again after refill */ + if (zr->jpg_settings.TmpDcm == 1) + i = (zr->jpg_dma_tail - + zr->jpg_err_shift) & + BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - + zr->jpg_err_shift) & 1) * 2; + } + if (i) { + /* Rotate stat_comm entries to make current entry first */ + int j; + u32 bus_addr[BUZ_NUM_STAT_COM]; + + /* Here we are copying the stat_com array, which + * is already in little endian format, so + * no endian conversions here + */ + memcpy(bus_addr, zr->stat_com, + sizeof(bus_addr)); + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = + bus_addr[(i + j) & + BUZ_MASK_STAT_COM]; + + } + zr->jpg_err_shift += i; + zr->jpg_err_shift &= BUZ_MASK_STAT_COM; + } + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) + zr->jpg_err_seq = zr->jpg_seq_num; /* + 1; */ + } + } + + /* Now the stat_comm buffer is ready for restart */ + do { + int status, mode; + + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + decoder_command(zr, DECODER_GET_STATUS, &status); + mode = CODEC_DO_COMPRESSION; + } else { + status = 0; + mode = CODEC_DO_EXPANSION; + } + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + (status & DECODER_STATUS_GOOD)) { + /********** RESTART code *************/ + jpeg_codec_reset(zr); + zr->codec->set_mode(zr->codec, mode); + zr36057_set_jpg(zr, zr->codec_mode); + jpeg_start(zr); + + if (zr->num_errors <= 8) + dprintk(2, KERN_INFO "%s: Restart\n", + ZR_DEVNAME(zr)); + + zr->JPEG_missed = 0; + zr->JPEG_error = 2; + /********** End RESTART code ***********/ + } + } while (0); +} + +irqreturn_t +zoran_irq (int irq, + void *dev_id, + struct pt_regs *regs) +{ + u32 stat, astat; + int count; + struct zoran *zr; + unsigned long flags; + + zr = (struct zoran *) dev_id; + count = 0; + + if (zr->testing) { + /* Testing interrupts */ + spin_lock_irqsave(&zr->spinlock, flags); + while ((stat = count_reset_interrupt(zr))) { + if (count++ > 100) { + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + dprintk(1, + KERN_ERR + "%s: IRQ lockup while testing, isr=0x%08x, cleared int mask\n", + ZR_DEVNAME(zr), stat); + wake_up_interruptible(&zr->test_q); + } + } + zr->last_isr = stat; + spin_unlock_irqrestore(&zr->spinlock, flags); + return IRQ_HANDLED; + } + + spin_lock_irqsave(&zr->spinlock, flags); + while (1) { + /* get/clear interrupt status bits */ + stat = count_reset_interrupt(zr); + astat = stat & IRQ_MASK; + if (!astat) { + break; + } + dprintk(4, + KERN_DEBUG + "zoran_irq: astat: 0x%08x, mask: 0x%08x\n", + astat, btread(ZR36057_ICR)); + if (astat & zr->card.vsync_int) { // SW + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + /* count missed interrupts */ + zr->JPEG_missed++; + } + //post_office_read(zr,1,0); + /* Interrupts may still happen when + * zr->v4l_memgrab_active is switched off. + * We simply ignore them */ + + if (zr->v4l_memgrab_active) { + + /* A lot more checks should be here ... */ + if ((btread(ZR36057_VSSFGR) & + ZR36057_VSSFGR_SnapShot) == 0) + dprintk(1, + KERN_WARNING + "%s: BuzIRQ with SnapShot off ???\n", + ZR_DEVNAME(zr)); + + if (zr->v4l_grab_frame != NO_GRAB_ACTIVE) { + /* There is a grab on a frame going on, check if it has finished */ + + if ((btread(ZR36057_VSSFGR) & + ZR36057_VSSFGR_FrameGrab) == + 0) { + /* it is finished, notify the user */ + + zr->v4l_buffers.buffer[zr->v4l_grab_frame].state = BUZ_STATE_DONE; + zr->v4l_buffers.buffer[zr->v4l_grab_frame].bs.seq = zr->v4l_grab_seq; + do_gettimeofday(&zr->v4l_buffers.buffer[zr->v4l_grab_frame].bs.timestamp); + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + zr->v4l_pend_tail++; + } + } + + if (zr->v4l_grab_frame == NO_GRAB_ACTIVE) + wake_up_interruptible(&zr->v4l_capq); + + /* Check if there is another grab queued */ + + if (zr->v4l_grab_frame == NO_GRAB_ACTIVE && + zr->v4l_pend_tail != zr->v4l_pend_head) { + + int frame = zr->v4l_pend[zr->v4l_pend_tail & + V4L_MASK_FRAME]; + u32 reg; + + zr->v4l_grab_frame = frame; + + /* Set zr36057 video front end and enable video */ + + /* Buffer address */ + + reg = + zr->v4l_buffers.buffer[frame]. + fbuffer_bus; + btwrite(reg, ZR36057_VDTR); + if (zr->v4l_settings.height > + BUZ_MAX_HEIGHT / 2) + reg += + zr->v4l_settings. + bytesperline; + btwrite(reg, ZR36057_VDBR); + + /* video stride, status, and frame grab register */ + reg = 0; + if (zr->v4l_settings.height > + BUZ_MAX_HEIGHT / 2) + reg += + zr->v4l_settings. + bytesperline; + reg = + (reg << + ZR36057_VSSFGR_DispStride); + reg |= ZR36057_VSSFGR_VidOvf; + reg |= ZR36057_VSSFGR_SnapShot; + reg |= ZR36057_VSSFGR_FrameGrab; + btwrite(reg, ZR36057_VSSFGR); + + btor(ZR36057_VDCR_VidEn, + ZR36057_VDCR); + } + } + + /* even if we don't grab, we do want to increment + * the sequence counter to see lost frames */ + zr->v4l_grab_seq++; + } +#if (IRQ_MASK & ZR36057_ISR_CodRepIRQ) + if (astat & ZR36057_ISR_CodRepIRQ) { + zr->intr_counter_CodRepIRQ++; + IDEBUG(printk + (KERN_DEBUG "%s: ZR36057_ISR_CodRepIRQ\n", + ZR_DEVNAME(zr))); + btand(~ZR36057_ICR_CodRepIRQ, ZR36057_ICR); + } +#endif /* (IRQ_MASK & ZR36057_ISR_CodRepIRQ) */ + +#if (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) + if (astat & ZR36057_ISR_JPEGRepIRQ) { + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + if (*zr_debug > 1 && + (!zr->frame_num || zr->JPEG_error)) { + printk(KERN_INFO + "%s: first frame ready: state=0x%08x odd_even=%d field_per_buff=%d delay=%d\n", + ZR_DEVNAME(zr), stat, + zr->jpg_settings.odd_even, + zr->jpg_settings. + field_per_buff, + zr->JPEG_missed); + { + char sc[] = "0000"; + char sv[5]; + int i; + strcpy(sv, sc); + for (i = 0; i < 4; i++) { + if (le32_to_cpu(zr->stat_com[i]) & 1) + sv[i] = '1'; + } + sv[4] = 0; + printk(KERN_INFO + "%s: stat_com=%s queue_state=%ld/%ld/%ld/%ld\n", + ZR_DEVNAME(zr), sv, + zr->jpg_que_tail, + zr->jpg_dma_tail, + zr->jpg_dma_head, + zr->jpg_que_head); + } + } else { + if (zr->JPEG_missed > zr->JPEG_max_missed) // Get statistics + zr->JPEG_max_missed = + zr->JPEG_missed; + if (zr->JPEG_missed < + zr->JPEG_min_missed) + zr->JPEG_min_missed = + zr->JPEG_missed; + } + + if (*zr_debug > 2 && zr->frame_num < 6) { + int i; + printk("%s: seq=%ld stat_com:", + ZR_DEVNAME(zr), zr->jpg_seq_num); + for (i = 0; i < 4; i++) { + printk(" %08x", + le32_to_cpu(zr->stat_com[i])); + } + printk("\n"); + } + zr->frame_num++; + zr->JPEG_missed = 0; + zr->JPEG_error = 0; + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + } /*else { + dprintk(1, + KERN_ERR + "%s: JPEG interrupt while not in motion (de)compress mode!\n", + ZR_DEVNAME(zr)); + }*/ + } +#endif /* (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) */ + + /* DATERR, too many fields missed, error processing */ + if ((astat & zr->card.jpeg_int) || + zr->JPEG_missed > 25 || + zr->JPEG_error == 1 || + ((zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) && + (zr->frame_num & (zr->JPEG_missed > + zr->jpg_settings.field_per_buff)))) { + error_handler(zr, astat, stat); + } + + count++; + if (count > 10) { + dprintk(2, KERN_WARNING "%s: irq loop %d\n", + ZR_DEVNAME(zr), count); + if (count > 20) { + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + dprintk(2, + KERN_ERR + "%s: IRQ lockup, cleared int mask\n", + ZR_DEVNAME(zr)); + break; + } + } + zr->last_isr = stat; + } + spin_unlock_irqrestore(&zr->spinlock, flags); + + return IRQ_HANDLED; +} + +void +zoran_set_pci_master (struct zoran *zr, + int set_master) +{ + if (set_master) { + pci_set_master(zr->pci_dev); + } else { + u16 command; + + pci_read_config_word(zr->pci_dev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_word(zr->pci_dev, PCI_COMMAND, command); + } +} + +void +zoran_init_hardware (struct zoran *zr) +{ + int j, zero = 0; + + /* Enable bus-mastering */ + zoran_set_pci_master(zr, 1); + + /* Initialize the board */ + if (zr->card.init) { + zr->card.init(zr); + } + + j = zr->card.input[zr->input].muxsel; + + decoder_command(zr, 0, NULL); + decoder_command(zr, DECODER_SET_NORM, &zr->norm); + decoder_command(zr, DECODER_SET_INPUT, &j); + + encoder_command(zr, 0, NULL); + encoder_command(zr, ENCODER_SET_NORM, &zr->norm); + encoder_command(zr, ENCODER_SET_INPUT, &zero); + + /* toggle JPEG codec sleep to sync PLL */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_sleep(zr, 0); + + /* set individual interrupt enables (without GIRQ1) + * but don't global enable until zoran_open() */ + + //btwrite(IRQ_MASK & ~ZR36057_ISR_GIRQ1, ZR36057_ICR); // SW + // It looks like using only JPEGRepIRQEn is not always reliable, + // may be when JPEG codec crashes it won't generate IRQ? So, + /*CP*/ // btwrite(IRQ_MASK, ZR36057_ICR); // Enable Vsync interrupts too. SM WHY ? LP + zr36057_init_vfe(zr); + + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + + btwrite(IRQ_MASK, ZR36057_ISR); // Clears interrupts +} + +void +zr36057_restart (struct zoran *zr) +{ + btwrite(0, ZR36057_SPGPPCR); + mdelay(1); + btor(ZR36057_SPGPPCR_SoftReset, ZR36057_SPGPPCR); + mdelay(1); + + /* assert P_Reset */ + btwrite(0, ZR36057_JPC); + /* set up GPIO direction - all output */ + btwrite(ZR36057_SPGPPCR_SoftReset | 0, ZR36057_SPGPPCR); + + /* set up GPIO pins and guest bus timing */ + btwrite((0x81 << 24) | 0x8888, ZR36057_GPPGCR1); +} + +/* + * initialize video front end + */ + +static void +zr36057_init_vfe (struct zoran *zr) +{ + u32 reg; + + reg = btread(ZR36057_VFESPFR); + reg |= ZR36057_VFESPFR_LittleEndian; + reg &= ~ZR36057_VFESPFR_VCLKPol; + reg |= ZR36057_VFESPFR_ExtFl; + reg |= ZR36057_VFESPFR_TopField; + btwrite(reg, ZR36057_VFESPFR); + reg = btread(ZR36057_VDCR); + if (pci_pci_problems & PCIPCI_TRITON) + // || zr->revision < 1) // Revision 1 has also Triton support + reg &= ~ZR36057_VDCR_Triton; + else + reg |= ZR36057_VDCR_Triton; + btwrite(reg, ZR36057_VDCR); +} + +/* + * Interface to decoder and encoder chips using i2c bus + */ + +int +decoder_command (struct zoran *zr, + int cmd, + void *data) +{ + if (zr->decoder == NULL) + return -EIO; + + if (zr->card.type == LML33 && + (cmd == DECODER_SET_NORM || DECODER_SET_INPUT)) { + int res; + + // Bt819 needs to reset its FIFO buffer using #FRST pin and + // LML33 card uses GPIO(7) for that. + GPIO(zr, 7, 0); + res = zr->decoder->driver->command(zr->decoder, cmd, data); + // Pull #FRST high. + GPIO(zr, 7, 1); + return res; + } else + return zr->decoder->driver->command(zr->decoder, cmd, + data); +} + +int +encoder_command (struct zoran *zr, + int cmd, + void *data) +{ + if (zr->encoder == NULL) + return -1; + + return zr->encoder->driver->command(zr->encoder, cmd, data); +} diff --git a/drivers/media/video/zoran_device.h b/drivers/media/video/zoran_device.h new file mode 100644 index 000000000000..f315203d7105 --- /dev/null +++ b/drivers/media/video/zoran_device.h @@ -0,0 +1,91 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ZORAN_DEVICE_H__ +#define __ZORAN_DEVICE_H__ + +/* general purpose I/O */ +extern void GPIO(struct zoran *zr, + int bit, + unsigned int value); + +/* codec (or actually: guest bus) access */ +extern int post_office_wait(struct zoran *zr); +extern int post_office_write(struct zoran *zr, + unsigned guest, + unsigned reg, + unsigned value); +extern int post_office_read(struct zoran *zr, + unsigned guest, + unsigned reg); + +extern void detect_guest_activity(struct zoran *zr); + +extern void jpeg_codec_sleep(struct zoran *zr, + int sleep); +extern int jpeg_codec_reset(struct zoran *zr); + +/* zr360x7 access to raw capture */ +extern void zr36057_overlay(struct zoran *zr, + int on); +extern void write_overlay_mask(struct file *file, + struct video_clip *vp, + int count); +extern void zr36057_set_memgrab(struct zoran *zr, + int mode); +extern int wait_grab_pending(struct zoran *zr); + +/* interrupts */ +extern void print_interrupts(struct zoran *zr); +extern void clear_interrupt_counters(struct zoran *zr); +extern irqreturn_t zoran_irq(int irq, + void *dev_id, + struct pt_regs *regs); + +/* JPEG codec access */ +extern void jpeg_start(struct zoran *zr); +extern void zr36057_enable_jpg(struct zoran *zr, + enum zoran_codec_mode mode); +extern void zoran_feed_stat_com(struct zoran *zr); + +/* general */ +extern void zoran_set_pci_master(struct zoran *zr, + int set_master); +extern void zoran_init_hardware(struct zoran *zr); +extern void zr36057_restart(struct zoran *zr); + +/* i2c */ +extern int decoder_command(struct zoran *zr, + int cmd, + void *data); +extern int encoder_command(struct zoran *zr, + int cmd, + void *data); + +#endif /* __ZORAN_DEVICE_H__ */ diff --git a/drivers/media/video/zoran_driver.c b/drivers/media/video/zoran_driver.c new file mode 100644 index 000000000000..ba838a42ec80 --- /dev/null +++ b/drivers/media/video/zoran_driver.c @@ -0,0 +1,4699 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Changes for BUZ by Wolfgang Scherr <scherr@net4you.net> + * + * Changes for DC10/DC30 by Laurent Pinchart <laurent.pinchart@skynet.be> + * + * Changes for LML33R10 by Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Changes for videodev2/v4l2 by Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * Based on + * + * Miro DC10 driver + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * + * Iomega Buz driver version 1.0 + * Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> + * + * buz.0.0.3 + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * bttv - Bt848 frame grabber driver + * Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + * & Marcus Metzler (mocm@thp.uni-koeln.de) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/wait.h> +#include <linux/byteorder/generic.h> + +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <linux/spinlock.h> +#define MAP_NR(x) virt_to_page(x) +#define ZORAN_HARDWARE VID_HARDWARE_ZR36067 +#define ZORAN_VID_TYPE ( \ + VID_TYPE_CAPTURE | \ + VID_TYPE_OVERLAY | \ + VID_TYPE_CLIPPING | \ + VID_TYPE_FRAMERAM | \ + VID_TYPE_SCALES | \ + VID_TYPE_MJPEG_DECODER | \ + VID_TYPE_MJPEG_ENCODER \ + ) + +#include <linux/videodev.h> +#include "videocodec.h" + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/proc_fs.h> + +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> +#include "zoran.h" +#include "zoran_device.h" +#include "zoran_card.h" + +#ifdef HAVE_V4L2 + /* we declare some card type definitions here, they mean + * the same as the v4l1 ZORAN_VID_TYPE above, except it's v4l2 */ +#define ZORAN_V4L2_VID_FLAGS ( \ + V4L2_CAP_STREAMING |\ + V4L2_CAP_VIDEO_CAPTURE |\ + V4L2_CAP_VIDEO_OUTPUT |\ + V4L2_CAP_VIDEO_OVERLAY \ + ) +#endif + +#include <asm/byteorder.h> + +const struct zoran_format zoran_formats[] = { + { + .name = "15-bit RGB", + .palette = VIDEO_PALETTE_RGB555, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_RGB555, +#else + .fourcc = V4L2_PIX_FMT_RGB555X, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 15, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "16-bit RGB", + .palette = VIDEO_PALETTE_RGB565, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_RGB565, +#else + .fourcc = V4L2_PIX_FMT_RGB565X, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "24-bit RGB", + .palette = VIDEO_PALETTE_RGB24, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_BGR24, +#else + .fourcc = V4L2_PIX_FMT_RGB24, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 24, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "32-bit RGB", + .palette = VIDEO_PALETTE_RGB32, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_BGR32, +#else + .fourcc = V4L2_PIX_FMT_RGB32, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 32, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "4:2:2, packed, YUYV", + .palette = VIDEO_PALETTE_YUV422, +#ifdef HAVE_V4L2 + .fourcc = V4L2_PIX_FMT_YUYV, + .colorspace = V4L2_COLORSPACE_SMPTE170M, +#endif + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "Hardware-encoded Motion-JPEG", + .palette = -1, +#ifdef HAVE_V4L2 + .fourcc = V4L2_PIX_FMT_MJPEG, + .colorspace = V4L2_COLORSPACE_SMPTE170M, +#endif + .depth = 0, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_PLAYBACK | + ZORAN_FORMAT_COMPRESSED, + } +}; +static const int zoran_num_formats = + (sizeof(zoran_formats) / sizeof(struct zoran_format)); + +// RJ: Test only - want to test BUZ_USE_HIMEM even when CONFIG_BIGPHYS_AREA is defined +#if !defined(CONFIG_BIGPHYS_AREA) +//#undef CONFIG_BIGPHYS_AREA +#define BUZ_USE_HIMEM +#endif + +#if defined(CONFIG_BIGPHYS_AREA) +# include <linux/bigphysarea.h> +#endif + +extern int *zr_debug; + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +extern int v4l_nbufs; +extern int v4l_bufsize; +extern int jpg_nbufs; +extern int jpg_bufsize; +extern int pass_through; + +static int lock_norm = 0; /* 1=Don't change TV standard (norm) */ +module_param(lock_norm, int, 0); +MODULE_PARM_DESC(lock_norm, "Users can't change norm"); + +#ifdef HAVE_V4L2 + /* small helper function for calculating buffersizes for v4l2 + * we calculate the nearest higher power-of-two, which + * will be the recommended buffersize */ +static __u32 +zoran_v4l2_calc_bufsize (struct zoran_jpg_settings *settings) +{ + __u8 div = settings->VerDcm * settings->HorDcm * settings->TmpDcm; + __u32 num = (1024 * 512) / (div); + __u32 result = 2; + + num--; + while (num) { + num >>= 1; + result <<= 1; + } + + if (result > jpg_bufsize) + return jpg_bufsize; + if (result < 8192) + return 8192; + return result; +} +#endif + +/* forward references */ +static void v4l_fbuffer_free(struct file *file); +static void jpg_fbuffer_free(struct file *file); + +/* + * Allocate the V4L grab buffers + * + * These have to be pysically contiguous. + * If v4l_bufsize <= MAX_KMALLOC_MEM we use kmalloc + * else we try to allocate them with bigphysarea_alloc_pages + * if the bigphysarea patch is present in the kernel, + * else we try to use high memory (if the user has bootet + * Linux with the necessary memory left over). + */ + +#if defined(BUZ_USE_HIMEM) && !defined(CONFIG_BIGPHYS_AREA) +static unsigned long +get_high_mem (unsigned long size) +{ +/* + * Check if there is usable memory at the end of Linux memory + * of at least size. Return the physical address of this memory, + * return 0 on failure. + * + * The idea is from Alexandro Rubini's book "Linux device drivers". + * The driver from him which is downloadable from O'Reilly's + * web site misses the "virt_to_phys(high_memory)" part + * (and therefore doesn't work at all - at least with 2.2.x kernels). + * + * It should be unnecessary to mention that THIS IS DANGEROUS, + * if more than one driver at a time has the idea to use this memory!!!! + */ + + volatile unsigned char __iomem *mem; + unsigned char c; + unsigned long hi_mem_ph; + unsigned long i; + + /* Map the high memory to user space */ + + hi_mem_ph = virt_to_phys(high_memory); + + mem = ioremap(hi_mem_ph, size); + if (!mem) { + dprintk(1, + KERN_ERR "%s: get_high_mem() - ioremap failed\n", + ZORAN_NAME); + return 0; + } + + for (i = 0; i < size; i++) { + /* Check if it is memory */ + c = i & 0xff; + writeb(c, mem + i); + if (readb(mem + i) != c) + break; + c = 255 - c; + writeb(c, mem + i); + if (readb(mem + i) != c) + break; + writeb(0, mem + i); /* zero out memory */ + + /* give the kernel air to breath */ + if ((i & 0x3ffff) == 0x3ffff) + schedule(); + } + + iounmap(mem); + + if (i != size) { + dprintk(1, + KERN_ERR + "%s: get_high_mem() - requested %lu, avail %lu\n", + ZORAN_NAME, size, i); + return 0; + } + + return hi_mem_ph; +} +#endif + +static int +v4l_fbuffer_alloc (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, off; + unsigned char *mem; +#if defined(BUZ_USE_HIMEM) && !defined(CONFIG_BIGPHYS_AREA) + unsigned long pmem = 0; +#endif + + /* we might have old buffers lying around... */ + if (fh->v4l_buffers.ready_to_be_freed) { + v4l_fbuffer_free(file); + } + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) { + if (fh->v4l_buffers.buffer[i].fbuffer) + dprintk(2, + KERN_WARNING + "%s: v4l_fbuffer_alloc() - buffer %d allready allocated!?\n", + ZR_DEVNAME(zr), i); + + //udelay(20); + if (fh->v4l_buffers.buffer_size <= MAX_KMALLOC_MEM) { + /* Use kmalloc */ + + mem = + (unsigned char *) kmalloc(fh->v4l_buffers. + buffer_size, + GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - kmalloc for V4L buf %d failed\n", + ZR_DEVNAME(zr), i); + v4l_fbuffer_free(file); + return -ENOBUFS; + } + fh->v4l_buffers.buffer[i].fbuffer = mem; + fh->v4l_buffers.buffer[i].fbuffer_phys = + virt_to_phys(mem); + fh->v4l_buffers.buffer[i].fbuffer_bus = + virt_to_bus(mem); + for (off = 0; off < fh->v4l_buffers.buffer_size; + off += PAGE_SIZE) + SetPageReserved(MAP_NR(mem + off)); + dprintk(4, + KERN_INFO + "%s: v4l_fbuffer_alloc() - V4L frame %d mem 0x%lx (bus: 0x%lx)\n", + ZR_DEVNAME(zr), i, (unsigned long) mem, + virt_to_bus(mem)); + } else { +#if defined(CONFIG_BIGPHYS_AREA) + /* Use bigphysarea_alloc_pages */ + + int n = + (fh->v4l_buffers.buffer_size + PAGE_SIZE - + 1) / PAGE_SIZE; + + mem = + (unsigned char *) bigphysarea_alloc_pages(n, 0, + GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - bigphysarea_alloc_pages for V4L buf %d failed\n", + ZR_DEVNAME(zr), i); + v4l_fbuffer_free(file); + return -ENOBUFS; + } + fh->v4l_buffers.buffer[i].fbuffer = mem; + fh->v4l_buffers.buffer[i].fbuffer_phys = + virt_to_phys(mem); + fh->v4l_buffers.buffer[i].fbuffer_bus = + virt_to_bus(mem); + dprintk(4, + KERN_INFO + "%s: Bigphysarea frame %d mem 0x%x (bus: 0x%x)\n", + ZR_DEVNAME(zr), i, (unsigned) mem, + (unsigned) virt_to_bus(mem)); + + /* Zero out the allocated memory */ + memset(fh->v4l_buffers.buffer[i].fbuffer, 0, + fh->v4l_buffers.buffer_size); +#elif defined(BUZ_USE_HIMEM) + + /* Use high memory which has been left at boot time */ + + /* Ok., Ok. this is an evil hack - we make + * the assumption that physical addresses are + * the same as bus addresses (true at least + * for Intel processors). The whole method of + * obtaining and using this memory is not very + * nice - but I hope it saves some poor users + * from kernel hacking, which might have even + * more evil results */ + + if (i == 0) { + int size = + fh->v4l_buffers.num_buffers * + fh->v4l_buffers.buffer_size; + + pmem = get_high_mem(size); + if (pmem == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - get_high_mem (size = %d KB) for V4L bufs failed\n", + ZR_DEVNAME(zr), size >> 10); + return -ENOBUFS; + } + fh->v4l_buffers.buffer[0].fbuffer = NULL; + fh->v4l_buffers.buffer[0].fbuffer_phys = pmem; + fh->v4l_buffers.buffer[0].fbuffer_bus = pmem; + dprintk(4, + KERN_INFO + "%s: v4l_fbuffer_alloc() - using %d KB high memory\n", + ZR_DEVNAME(zr), size >> 10); + } else { + fh->v4l_buffers.buffer[i].fbuffer = NULL; + fh->v4l_buffers.buffer[i].fbuffer_phys = + pmem + i * fh->v4l_buffers.buffer_size; + fh->v4l_buffers.buffer[i].fbuffer_bus = + pmem + i * fh->v4l_buffers.buffer_size; + } +#else + /* No bigphysarea present, usage of high memory disabled, + * but user wants buffers of more than MAX_KMALLOC_MEM */ + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - no bigphysarea_patch present, usage of high memory disabled,\n", + ZR_DEVNAME(zr)); + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - sorry, could not allocate %d V4L buffers of size %d KB.\n", + ZR_DEVNAME(zr), fh->v4l_buffers.num_buffers, + fh->v4l_buffers.buffer_size >> 10); + return -ENOBUFS; +#endif + } + } + + fh->v4l_buffers.allocated = 1; + + return 0; +} + +/* free the V4L grab buffers */ +static void +v4l_fbuffer_free (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, off; + unsigned char *mem; + + dprintk(4, KERN_INFO "%s: v4l_fbuffer_free()\n", ZR_DEVNAME(zr)); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) { + if (!fh->v4l_buffers.buffer[i].fbuffer) + continue; + + if (fh->v4l_buffers.buffer_size <= MAX_KMALLOC_MEM) { + mem = fh->v4l_buffers.buffer[i].fbuffer; + for (off = 0; off < fh->v4l_buffers.buffer_size; + off += PAGE_SIZE) + ClearPageReserved(MAP_NR(mem + off)); + kfree((void *) fh->v4l_buffers.buffer[i].fbuffer); + } +#if defined(CONFIG_BIGPHYS_AREA) + else + bigphysarea_free_pages((void *) fh->v4l_buffers. + buffer[i].fbuffer); +#endif + fh->v4l_buffers.buffer[i].fbuffer = NULL; + } + + fh->v4l_buffers.allocated = 0; + fh->v4l_buffers.ready_to_be_freed = 0; +} + +/* + * Allocate the MJPEG grab buffers. + * + * If the requested buffer size is smaller than MAX_KMALLOC_MEM, + * kmalloc is used to request a physically contiguous area, + * else we allocate the memory in framgents with get_zeroed_page. + * + * If a Natoma chipset is present and this is a revision 1 zr36057, + * each MJPEG buffer needs to be physically contiguous. + * (RJ: This statement is from Dave Perks' original driver, + * I could never check it because I have a zr36067) + * The driver cares about this because it reduces the buffer + * size to MAX_KMALLOC_MEM in that case (which forces contiguous allocation). + * + * RJ: The contents grab buffers needs never be accessed in the driver. + * Therefore there is no need to allocate them with vmalloc in order + * to get a contiguous virtual memory space. + * I don't understand why many other drivers first allocate them with + * vmalloc (which uses internally also get_zeroed_page, but delivers you + * virtual addresses) and then again have to make a lot of efforts + * to get the physical address. + * + * Ben Capper: + * On big-endian architectures (such as ppc) some extra steps + * are needed. When reading and writing to the stat_com array + * and fragment buffers, the device expects to see little- + * endian values. The use of cpu_to_le32() and le32_to_cpu() + * in this function (and one or two others in zoran_device.c) + * ensure that these values are always stored in little-endian + * form, regardless of architecture. The zr36057 does Very Bad + * Things on big endian architectures if the stat_com array + * and fragment buffers are not little-endian. + */ + +static int +jpg_fbuffer_alloc (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, j, off; + unsigned long mem; + + /* we might have old buffers lying around */ + if (fh->jpg_buffers.ready_to_be_freed) { + jpg_fbuffer_free(file); + } + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) { + if (fh->jpg_buffers.buffer[i].frag_tab) + dprintk(2, + KERN_WARNING + "%s: jpg_fbuffer_alloc() - buffer %d allready allocated!?\n", + ZR_DEVNAME(zr), i); + + /* Allocate fragment table for this buffer */ + + mem = get_zeroed_page(GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: jpg_fbuffer_alloc() - get_zeroed_page (frag_tab) failed for buffer %d\n", + ZR_DEVNAME(zr), i); + jpg_fbuffer_free(file); + return -ENOBUFS; + } + memset((void *) mem, 0, PAGE_SIZE); + fh->jpg_buffers.buffer[i].frag_tab = (u32 *) mem; + fh->jpg_buffers.buffer[i].frag_tab_bus = + virt_to_bus((void *) mem); + + //if (alloc_contig) { + if (fh->jpg_buffers.need_contiguous) { + mem = + (unsigned long) kmalloc(fh->jpg_buffers. + buffer_size, + GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: jpg_fbuffer_alloc() - kmalloc failed for buffer %d\n", + ZR_DEVNAME(zr), i); + jpg_fbuffer_free(file); + return -ENOBUFS; + } + fh->jpg_buffers.buffer[i].frag_tab[0] = + cpu_to_le32(virt_to_bus((void *) mem)); + fh->jpg_buffers.buffer[i].frag_tab[1] = + cpu_to_le32(((fh->jpg_buffers.buffer_size / 4) << 1) | 1); + for (off = 0; off < fh->jpg_buffers.buffer_size; + off += PAGE_SIZE) + SetPageReserved(MAP_NR(mem + off)); + } else { + /* jpg_bufsize is allreay page aligned */ + for (j = 0; + j < fh->jpg_buffers.buffer_size / PAGE_SIZE; + j++) { + mem = get_zeroed_page(GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: jpg_fbuffer_alloc() - get_zeroed_page failed for buffer %d\n", + ZR_DEVNAME(zr), i); + jpg_fbuffer_free(file); + return -ENOBUFS; + } + + fh->jpg_buffers.buffer[i].frag_tab[2 * j] = + cpu_to_le32(virt_to_bus((void *) mem)); + fh->jpg_buffers.buffer[i].frag_tab[2 * j + + 1] = + cpu_to_le32((PAGE_SIZE / 4) << 1); + SetPageReserved(MAP_NR(mem)); + } + + fh->jpg_buffers.buffer[i].frag_tab[2 * j - 1] |= cpu_to_le32(1); + } + } + + dprintk(4, + KERN_DEBUG "%s: jpg_fbuffer_alloc() - %d KB allocated\n", + ZR_DEVNAME(zr), + (fh->jpg_buffers.num_buffers * + fh->jpg_buffers.buffer_size) >> 10); + + fh->jpg_buffers.allocated = 1; + + return 0; +} + +/* free the MJPEG grab buffers */ +static void +jpg_fbuffer_free (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, j, off; + unsigned char *mem; + + dprintk(4, KERN_DEBUG "%s: jpg_fbuffer_free()\n", ZR_DEVNAME(zr)); + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) { + if (!fh->jpg_buffers.buffer[i].frag_tab) + continue; + + //if (alloc_contig) { + if (fh->jpg_buffers.need_contiguous) { + if (fh->jpg_buffers.buffer[i].frag_tab[0]) { + mem = (unsigned char *) bus_to_virt(le32_to_cpu( + fh->jpg_buffers.buffer[i].frag_tab[0])); + for (off = 0; + off < fh->jpg_buffers.buffer_size; + off += PAGE_SIZE) + ClearPageReserved(MAP_NR + (mem + off)); + kfree((void *) mem); + fh->jpg_buffers.buffer[i].frag_tab[0] = 0; + fh->jpg_buffers.buffer[i].frag_tab[1] = 0; + } + } else { + for (j = 0; + j < fh->jpg_buffers.buffer_size / PAGE_SIZE; + j++) { + if (!fh->jpg_buffers.buffer[i]. + frag_tab[2 * j]) + break; + ClearPageReserved(MAP_NR + (bus_to_virt + (le32_to_cpu + (fh->jpg_buffers. + buffer[i].frag_tab[2 * + j])))); + free_page((unsigned long) + bus_to_virt + (le32_to_cpu + (fh->jpg_buffers. + buffer[i]. + frag_tab[2 * j]))); + fh->jpg_buffers.buffer[i].frag_tab[2 * j] = + 0; + fh->jpg_buffers.buffer[i].frag_tab[2 * j + + 1] = 0; + } + } + + free_page((unsigned long) fh->jpg_buffers.buffer[i]. + frag_tab); + fh->jpg_buffers.buffer[i].frag_tab = NULL; + } + + fh->jpg_buffers.allocated = 0; + fh->jpg_buffers.ready_to_be_freed = 0; +} + +/* + * V4L Buffer grabbing + */ + +static int +zoran_v4l_set_format (struct file *file, + int width, + int height, + const struct zoran_format *format) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int bpp; + + /* Check size and format of the grab wanted */ + + if (height < BUZ_MIN_HEIGHT || width < BUZ_MIN_WIDTH || + height > BUZ_MAX_HEIGHT || width > BUZ_MAX_WIDTH) { + dprintk(1, + KERN_ERR + "%s: v4l_set_format() - wrong frame size (%dx%d)\n", + ZR_DEVNAME(zr), width, height); + return -EINVAL; + } + + bpp = (format->depth + 7) / 8; + + /* Check against available buffer size */ + if (height * width * bpp > fh->v4l_buffers.buffer_size) { + dprintk(1, + KERN_ERR + "%s: v4l_set_format() - video buffer size (%d kB) is too small\n", + ZR_DEVNAME(zr), fh->v4l_buffers.buffer_size >> 10); + return -EINVAL; + } + + /* The video front end needs 4-byte alinged line sizes */ + + if ((bpp == 2 && (width & 1)) || (bpp == 3 && (width & 3))) { + dprintk(1, + KERN_ERR + "%s: v4l_set_format() - wrong frame alingment\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + fh->v4l_settings.width = width; + fh->v4l_settings.height = height; + fh->v4l_settings.format = format; + fh->v4l_settings.bytesperline = bpp * fh->v4l_settings.width; + + return 0; +} + +static int +zoran_v4l_queue_frame (struct file *file, + int num) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + int res = 0; + + if (!fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: v4l_queue_frame() - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + } + + /* No grabbing outside the buffer range! */ + if (num >= fh->v4l_buffers.num_buffers || num < 0) { + dprintk(1, + KERN_ERR + "%s: v4l_queue_frame() - buffer %d is out of range\n", + ZR_DEVNAME(zr), num); + res = -EINVAL; + } + + spin_lock_irqsave(&zr->spinlock, flags); + + if (fh->v4l_buffers.active == ZORAN_FREE) { + if (zr->v4l_buffers.active == ZORAN_FREE) { + zr->v4l_buffers = fh->v4l_buffers; + fh->v4l_buffers.active = ZORAN_ACTIVE; + } else { + dprintk(1, + KERN_ERR + "%s: v4l_queue_frame() - another session is already capturing\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + } + } + + /* make sure a grab isn't going on currently with this buffer */ + if (!res) { + switch (zr->v4l_buffers.buffer[num].state) { + default: + case BUZ_STATE_PEND: + if (zr->v4l_buffers.active == ZORAN_FREE) { + fh->v4l_buffers.active = ZORAN_FREE; + zr->v4l_buffers.allocated = 0; + } + res = -EBUSY; /* what are you doing? */ + break; + case BUZ_STATE_DONE: + dprintk(2, + KERN_WARNING + "%s: v4l_queue_frame() - queueing buffer %d in state DONE!?\n", + ZR_DEVNAME(zr), num); + case BUZ_STATE_USER: + /* since there is at least one unused buffer there's room for at least + * one more pend[] entry */ + zr->v4l_pend[zr->v4l_pend_head++ & + V4L_MASK_FRAME] = num; + zr->v4l_buffers.buffer[num].state = BUZ_STATE_PEND; + zr->v4l_buffers.buffer[num].bs.length = + fh->v4l_settings.bytesperline * + zr->v4l_settings.height; + fh->v4l_buffers.buffer[num] = + zr->v4l_buffers.buffer[num]; + break; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + if (!res && zr->v4l_buffers.active == ZORAN_FREE) + zr->v4l_buffers.active = fh->v4l_buffers.active; + + return res; +} + +static int +v4l_grab (struct file *file, + struct video_mmap *mp) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int res = 0, i; + + for (i = 0; i < zoran_num_formats; i++) { + if (zoran_formats[i].palette == mp->format && + zoran_formats[i].flags & ZORAN_FORMAT_CAPTURE && + !(zoran_formats[i].flags & ZORAN_FORMAT_COMPRESSED)) + break; + } + if (i == zoran_num_formats || zoran_formats[i].depth == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_grab() - wrong bytes-per-pixel format\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + /* + * To minimize the time spent in the IRQ routine, we avoid setting up + * the video front end there. + * If this grab has different parameters from a running streaming capture + * we stop the streaming capture and start it over again. + */ + if (zr->v4l_memgrab_active && + (zr->v4l_settings.width != mp->width || + zr->v4l_settings.height != mp->height || + zr->v4l_settings.format->palette != mp->format)) { + res = wait_grab_pending(zr); + if (res) + return res; + } + if ((res = zoran_v4l_set_format(file, + mp->width, + mp->height, + &zoran_formats[i]))) + return res; + zr->v4l_settings = fh->v4l_settings; + + /* queue the frame in the pending queue */ + if ((res = zoran_v4l_queue_frame(file, mp->frame))) { + fh->v4l_buffers.active = ZORAN_FREE; + return res; + } + + /* put the 36057 into frame grabbing mode */ + if (!res && !zr->v4l_memgrab_active) + zr36057_set_memgrab(zr, 1); + + //dprintk(4, KERN_INFO "%s: Frame grab 3...\n", ZR_DEVNAME(zr)); + + return res; +} + +/* + * Sync on a V4L buffer + */ + +static int +v4l_sync (struct file *file, + int frame) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + + if (fh->v4l_buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: v4l_sync() - no grab active for this session\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + /* check passed-in frame number */ + if (frame >= fh->v4l_buffers.num_buffers || frame < 0) { + dprintk(1, + KERN_ERR "%s: v4l_sync() - frame %d is invalid\n", + ZR_DEVNAME(zr), frame); + return -EINVAL; + } + + /* Check if is buffer was queued at all */ + if (zr->v4l_buffers.buffer[frame].state == BUZ_STATE_USER) { + dprintk(1, + KERN_ERR + "%s: v4l_sync() - attempt to sync on a buffer which was not queued?\n", + ZR_DEVNAME(zr)); + return -EPROTO; + } + + /* wait on this buffer to get ready */ + if (!wait_event_interruptible_timeout(zr->v4l_capq, + (zr->v4l_buffers.buffer[frame].state != BUZ_STATE_PEND), + 10*HZ)) + return -ETIME; + if (signal_pending(current)) + return -ERESTARTSYS; + + /* buffer should now be in BUZ_STATE_DONE */ + if (zr->v4l_buffers.buffer[frame].state != BUZ_STATE_DONE) + dprintk(2, + KERN_ERR "%s: v4l_sync() - internal state error\n", + ZR_DEVNAME(zr)); + + zr->v4l_buffers.buffer[frame].state = BUZ_STATE_USER; + fh->v4l_buffers.buffer[frame] = zr->v4l_buffers.buffer[frame]; + + spin_lock_irqsave(&zr->spinlock, flags); + + /* Check if streaming capture has finished */ + if (zr->v4l_pend_tail == zr->v4l_pend_head) { + zr36057_set_memgrab(zr, 0); + if (zr->v4l_buffers.active == ZORAN_ACTIVE) { + fh->v4l_buffers.active = zr->v4l_buffers.active = + ZORAN_FREE; + zr->v4l_buffers.allocated = 0; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +/* + * Queue a MJPEG buffer for capture/playback + */ + +static int +zoran_jpg_queue_frame (struct file *file, + int num, + enum zoran_codec_mode mode) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + int res = 0; + + /* Check if buffers are allocated */ + if (!fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + return -ENOMEM; + } + + /* No grabbing outside the buffer range! */ + if (num >= fh->jpg_buffers.num_buffers || num < 0) { + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - buffer %d out of range\n", + ZR_DEVNAME(zr), num); + return -EINVAL; + } + + /* what is the codec mode right now? */ + if (zr->codec_mode == BUZ_MODE_IDLE) { + zr->jpg_settings = fh->jpg_settings; + } else if (zr->codec_mode != mode) { + /* wrong codec mode active - invalid */ + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - codec in wrong mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + spin_lock_irqsave(&zr->spinlock, flags); + + if (fh->jpg_buffers.active == ZORAN_FREE) { + if (zr->jpg_buffers.active == ZORAN_FREE) { + zr->jpg_buffers = fh->jpg_buffers; + fh->jpg_buffers.active = ZORAN_ACTIVE; + } else { + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - another session is already capturing\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + } + } + + if (!res && zr->codec_mode == BUZ_MODE_IDLE) { + /* Ok load up the jpeg codec */ + zr36057_enable_jpg(zr, mode); + } + + if (!res) { + switch (zr->jpg_buffers.buffer[num].state) { + case BUZ_STATE_DONE: + dprintk(2, + KERN_WARNING + "%s: jpg_queue_frame() - queing frame in BUZ_STATE_DONE state!?\n", + ZR_DEVNAME(zr)); + case BUZ_STATE_USER: + /* since there is at least one unused buffer there's room for at + *least one more pend[] entry */ + zr->jpg_pend[zr->jpg_que_head++ & BUZ_MASK_FRAME] = + num; + zr->jpg_buffers.buffer[num].state = BUZ_STATE_PEND; + fh->jpg_buffers.buffer[num] = + zr->jpg_buffers.buffer[num]; + zoran_feed_stat_com(zr); + break; + default: + case BUZ_STATE_DMA: + case BUZ_STATE_PEND: + if (zr->jpg_buffers.active == ZORAN_FREE) { + fh->jpg_buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + } + res = -EBUSY; /* what are you doing? */ + break; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + if (!res && zr->jpg_buffers.active == ZORAN_FREE) { + zr->jpg_buffers.active = fh->jpg_buffers.active; + } + + return res; +} + +static int +jpg_qbuf (struct file *file, + int frame, + enum zoran_codec_mode mode) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int res = 0; + + /* Does the user want to stop streaming? */ + if (frame < 0) { + if (zr->codec_mode == mode) { + if (fh->jpg_buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: jpg_qbuf(-1) - session not active\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + fh->jpg_buffers.active = zr->jpg_buffers.active = + ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + return 0; + } else { + dprintk(1, + KERN_ERR + "%s: jpg_qbuf() - stop streaming but not in streaming mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + } + + if ((res = zoran_jpg_queue_frame(file, frame, mode))) + return res; + + /* Start the jpeg codec when the first frame is queued */ + if (!res && zr->jpg_que_head == 1) + jpeg_start(zr); + + return res; +} + +/* + * Sync on a MJPEG buffer + */ + +static int +jpg_sync (struct file *file, + struct zoran_sync *bs) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + int frame; + + if (fh->jpg_buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: jpg_sync() - capture is not currently active\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS && + zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) { + dprintk(1, + KERN_ERR + "%s: jpg_sync() - codec not in streaming mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (!wait_event_interruptible_timeout(zr->jpg_capq, + (zr->jpg_que_tail != zr->jpg_dma_tail || + zr->jpg_dma_tail == zr->jpg_dma_head), + 10*HZ)) { + int isr; + + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(1); + zr->codec->control(zr->codec, CODEC_G_STATUS, + sizeof(isr), &isr); + dprintk(1, + KERN_ERR + "%s: jpg_sync() - timeout: codec isr=0x%02x\n", + ZR_DEVNAME(zr), isr); + + return -ETIME; + + } + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&zr->spinlock, flags); + + if (zr->jpg_dma_tail != zr->jpg_dma_head) + frame = zr->jpg_pend[zr->jpg_que_tail++ & BUZ_MASK_FRAME]; + else + frame = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + + /* buffer should now be in BUZ_STATE_DONE */ + if (*zr_debug > 0) + if (zr->jpg_buffers.buffer[frame].state != BUZ_STATE_DONE) + dprintk(2, + KERN_ERR + "%s: jpg_sync() - internal state error\n", + ZR_DEVNAME(zr)); + + *bs = zr->jpg_buffers.buffer[frame].bs; + bs->frame = frame; + zr->jpg_buffers.buffer[frame].state = BUZ_STATE_USER; + fh->jpg_buffers.buffer[frame] = zr->jpg_buffers.buffer[frame]; + + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +static void +zoran_open_init_session (struct file *file) +{ + int i; + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* Per default, map the V4L Buffers */ + fh->map_mode = ZORAN_MAP_MODE_RAW; + + /* take over the card's current settings */ + fh->overlay_settings = zr->overlay_settings; + fh->overlay_settings.is_set = 0; + fh->overlay_settings.format = zr->overlay_settings.format; + fh->overlay_active = ZORAN_FREE; + + /* v4l settings */ + fh->v4l_settings = zr->v4l_settings; + + /* v4l_buffers */ + memset(&fh->v4l_buffers, 0, sizeof(struct zoran_v4l_struct)); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + fh->v4l_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + fh->v4l_buffers.buffer[i].bs.frame = i; + } + fh->v4l_buffers.allocated = 0; + fh->v4l_buffers.ready_to_be_freed = 0; + fh->v4l_buffers.active = ZORAN_FREE; + fh->v4l_buffers.buffer_size = v4l_bufsize; + fh->v4l_buffers.num_buffers = v4l_nbufs; + + /* jpg settings */ + fh->jpg_settings = zr->jpg_settings; + + /* jpg_buffers */ + memset(&fh->jpg_buffers, 0, sizeof(struct zoran_jpg_struct)); + for (i = 0; i < BUZ_MAX_FRAME; i++) { + fh->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + fh->jpg_buffers.buffer[i].bs.frame = i; + } + fh->jpg_buffers.need_contiguous = zr->jpg_buffers.need_contiguous; + fh->jpg_buffers.allocated = 0; + fh->jpg_buffers.ready_to_be_freed = 0; + fh->jpg_buffers.active = ZORAN_FREE; + fh->jpg_buffers.buffer_size = jpg_bufsize; + fh->jpg_buffers.num_buffers = jpg_nbufs; +} + +static void +zoran_close_end_session (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* overlay */ + if (fh->overlay_active != ZORAN_FREE) { + fh->overlay_active = zr->overlay_active = ZORAN_FREE; + zr->v4l_overlay_active = 0; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + } + + /* v4l capture */ + if (fh->v4l_buffers.active != ZORAN_FREE) { + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = fh->v4l_buffers.active = + ZORAN_FREE; + } + + /* v4l buffers */ + if (fh->v4l_buffers.allocated || + fh->v4l_buffers.ready_to_be_freed) { + v4l_fbuffer_free(file); + } + + /* jpg capture */ + if (fh->jpg_buffers.active != ZORAN_FREE) { + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = fh->jpg_buffers.active = + ZORAN_FREE; + } + + /* jpg buffers */ + if (fh->jpg_buffers.allocated || + fh->jpg_buffers.ready_to_be_freed) { + jpg_fbuffer_free(file); + } +} + +/* + * Open a zoran card. Right now the flags stuff is just playing + */ + +static int +zoran_open (struct inode *inode, + struct file *file) +{ + unsigned int minor = iminor(inode); + struct zoran *zr = NULL; + struct zoran_fh *fh; + int i, res, first_open = 0, have_module_locks = 0; + + /* find the device */ + for (i = 0; i < zoran_num; i++) { + if (zoran[i].video_dev->minor == minor) { + zr = &zoran[i]; + break; + } + } + + if (!zr) { + dprintk(1, KERN_ERR "%s: device not found!\n", ZORAN_NAME); + res = -ENODEV; + goto open_unlock_and_return; + } + + /* see fs/device.c - the kernel already locks during open(), + * so locking ourselves only causes deadlocks */ + /*down(&zr->resource_lock);*/ + + if (!zr->decoder) { + dprintk(1, + KERN_ERR "%s: no TV decoder loaded for device!\n", + ZR_DEVNAME(zr)); + res = -EIO; + goto open_unlock_and_return; + } + + /* try to grab a module lock */ + if (!try_module_get(THIS_MODULE)) { + dprintk(1, + KERN_ERR + "%s: failed to acquire my own lock! PANIC!\n", + ZR_DEVNAME(zr)); + res = -ENODEV; + goto open_unlock_and_return; + } + if (!try_module_get(zr->decoder->driver->owner)) { + dprintk(1, + KERN_ERR + "%s: failed to grab ownership of i2c decoder\n", + ZR_DEVNAME(zr)); + res = -EIO; + module_put(THIS_MODULE); + goto open_unlock_and_return; + } + if (zr->encoder && + !try_module_get(zr->encoder->driver->owner)) { + dprintk(1, + KERN_ERR + "%s: failed to grab ownership of i2c encoder\n", + ZR_DEVNAME(zr)); + res = -EIO; + module_put(zr->decoder->driver->owner); + module_put(THIS_MODULE); + goto open_unlock_and_return; + } + + have_module_locks = 1; + + if (zr->user >= 2048) { + dprintk(1, KERN_ERR "%s: too many users (%d) on device\n", + ZR_DEVNAME(zr), zr->user); + res = -EBUSY; + goto open_unlock_and_return; + } + + dprintk(1, KERN_INFO "%s: zoran_open(%s, pid=[%d]), users(-)=%d\n", + ZR_DEVNAME(zr), current->comm, current->pid, zr->user); + + /* now, create the open()-specific file_ops struct */ + fh = kmalloc(sizeof(struct zoran_fh), GFP_KERNEL); + if (!fh) { + dprintk(1, + KERN_ERR + "%s: zoran_open() - allocation of zoran_fh failed\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + goto open_unlock_and_return; + } + memset(fh, 0, sizeof(struct zoran_fh)); + /* used to be BUZ_MAX_WIDTH/HEIGHT, but that gives overflows + * on norm-change! */ + fh->overlay_mask = + kmalloc(((768 + 31) / 32) * 576 * 4, GFP_KERNEL); + if (!fh->overlay_mask) { + dprintk(1, + KERN_ERR + "%s: zoran_open() - allocation of overlay_mask failed\n", + ZR_DEVNAME(zr)); + kfree(fh); + res = -ENOMEM; + goto open_unlock_and_return; + } + + if (zr->user++ == 0) + first_open = 1; + + /*up(&zr->resource_lock);*/ + + /* default setup - TODO: look at flags */ + if (first_open) { /* First device open */ + zr36057_restart(zr); + zoran_open_init_params(zr); + zoran_init_hardware(zr); + + btor(ZR36057_ICR_IntPinEn, ZR36057_ICR); + } + + /* set file_ops stuff */ + file->private_data = fh; + fh->zr = zr; + zoran_open_init_session(file); + + return 0; + +open_unlock_and_return: + /* if we grabbed locks, release them accordingly */ + if (have_module_locks) { + module_put(zr->decoder->driver->owner); + if (zr->encoder) { + module_put(zr->encoder->driver->owner); + } + module_put(THIS_MODULE); + } + + /* if there's no device found, we didn't obtain the lock either */ + if (zr) { + /*up(&zr->resource_lock);*/ + } + + return res; +} + +static int +zoran_close (struct inode *inode, + struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + dprintk(1, KERN_INFO "%s: zoran_close(%s, pid=[%d]), users(+)=%d\n", + ZR_DEVNAME(zr), current->comm, current->pid, zr->user); + + /* kernel locks (fs/device.c), so don't do that ourselves + * (prevents deadlocks) */ + /*down(&zr->resource_lock);*/ + + zoran_close_end_session(file); + + if (zr->user-- == 1) { /* Last process */ + /* Clean up JPEG process */ + wake_up_interruptible(&zr->jpg_capq); + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = ZORAN_FREE; + + /* disable interrupts */ + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + + if (*zr_debug > 1) + print_interrupts(zr); + + /* Overlay off */ + zr->v4l_overlay_active = 0; + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + + /* capture off */ + wake_up_interruptible(&zr->v4l_capq); + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = ZORAN_FREE; + zoran_set_pci_master(zr, 0); + + if (!pass_through) { /* Switch to color bar */ + int zero = 0, two = 2; + decoder_command(zr, DECODER_ENABLE_OUTPUT, &zero); + encoder_command(zr, ENCODER_SET_INPUT, &two); + } + } + + file->private_data = NULL; + kfree(fh->overlay_mask); + kfree(fh); + + /* release locks on the i2c modules */ + module_put(zr->decoder->driver->owner); + if (zr->encoder) { + module_put(zr->encoder->driver->owner); + } + module_put(THIS_MODULE); + + /*up(&zr->resource_lock);*/ + + dprintk(4, KERN_INFO "%s: zoran_close() done\n", ZR_DEVNAME(zr)); + + return 0; +} + + +static ssize_t +zoran_read (struct file *file, + char __user *data, + size_t count, + loff_t *ppos) +{ + /* we simply don't support read() (yet)... */ + + return -EINVAL; +} + +static ssize_t +zoran_write (struct file *file, + const char __user *data, + size_t count, + loff_t *ppos) +{ + /* ...and the same goes for write() */ + + return -EINVAL; +} + +static int +setup_fbuffer (struct file *file, + void *base, + const struct zoran_format *fmt, + int width, + int height, + int bytesperline) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* (Ronald) v4l/v4l2 guidelines */ + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* we need a bytesperline value, even if not given */ + if (!bytesperline) + bytesperline = width * ((fmt->depth + 7) & ~7) / 8; + +#if 0 + if (zr->overlay_active) { + /* dzjee... stupid users... don't even bother to turn off + * overlay before changing the memory location... + * normally, we would return errors here. However, one of + * the tools that does this is... xawtv! and since xawtv + * is used by +/- 99% of the users, we'd rather be user- + * friendly and silently do as if nothing went wrong */ + dprintk(3, + KERN_ERR + "%s: setup_fbuffer() - forced overlay turnoff because framebuffer changed\n", + ZR_DEVNAME(zr)); + zr36057_overlay(zr, 0); + } +#endif + + if (!(fmt->flags & ZORAN_FORMAT_OVERLAY)) { + dprintk(1, + KERN_ERR + "%s: setup_fbuffer() - no valid overlay format given\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (height <= 0 || width <= 0 || bytesperline <= 0) { + dprintk(1, + KERN_ERR + "%s: setup_fbuffer() - invalid height/width/bpl value (%d|%d|%d)\n", + ZR_DEVNAME(zr), width, height, bytesperline); + return -EINVAL; + } + if (bytesperline & 3) { + dprintk(1, + KERN_ERR + "%s: setup_fbuffer() - bytesperline (%d) must be 4-byte aligned\n", + ZR_DEVNAME(zr), bytesperline); + return -EINVAL; + } + + zr->buffer.base = (void *) ((unsigned long) base & ~3); + zr->buffer.height = height; + zr->buffer.width = width; + zr->buffer.depth = fmt->depth; + zr->overlay_settings.format = fmt; + zr->buffer.bytesperline = bytesperline; + + /* The user should set new window parameters */ + zr->overlay_settings.is_set = 0; + + return 0; +} + + +static int +setup_window (struct file *file, + int x, + int y, + int width, + int height, + struct video_clip __user *clips, + int clipcount, + void __user *bitmap) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + struct video_clip *vcp = NULL; + int on, end; + + + if (!zr->buffer.base) { + dprintk(1, + KERN_ERR + "%s: setup_window() - frame buffer has to be set first\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + if (!fh->overlay_settings.format) { + dprintk(1, + KERN_ERR + "%s: setup_window() - no overlay format set\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + /* + * The video front end needs 4-byte alinged line sizes, we correct that + * silently here if necessary + */ + if (zr->buffer.depth == 15 || zr->buffer.depth == 16) { + end = (x + width) & ~1; /* round down */ + x = (x + 1) & ~1; /* round up */ + width = end - x; + } + + if (zr->buffer.depth == 24) { + end = (x + width) & ~3; /* round down */ + x = (x + 3) & ~3; /* round up */ + width = end - x; + } + + if (width > BUZ_MAX_WIDTH) + width = BUZ_MAX_WIDTH; + if (height > BUZ_MAX_HEIGHT) + height = BUZ_MAX_HEIGHT; + + /* Check for vaild parameters */ + if (width < BUZ_MIN_WIDTH || height < BUZ_MIN_HEIGHT || + width > BUZ_MAX_WIDTH || height > BUZ_MAX_HEIGHT) { + dprintk(1, + KERN_ERR + "%s: setup_window() - width = %d or height = %d invalid\n", + ZR_DEVNAME(zr), width, height); + return -EINVAL; + } + + fh->overlay_settings.x = x; + fh->overlay_settings.y = y; + fh->overlay_settings.width = width; + fh->overlay_settings.height = height; + fh->overlay_settings.clipcount = clipcount; + + /* + * If an overlay is running, we have to switch it off + * and switch it on again in order to get the new settings in effect. + * + * We also want to avoid that the overlay mask is written + * when an overlay is running. + */ + + on = zr->v4l_overlay_active && !zr->v4l_memgrab_active && + zr->overlay_active != ZORAN_FREE && + fh->overlay_active != ZORAN_FREE; + if (on) + zr36057_overlay(zr, 0); + + /* + * Write the overlay mask if clips are wanted. + * We prefer a bitmap. + */ + if (bitmap) { + /* fake value - it just means we want clips */ + fh->overlay_settings.clipcount = 1; + + if (copy_from_user(fh->overlay_mask, bitmap, + (width * height + 7) / 8)) { + return -EFAULT; + } + } else if (clipcount > 0) { + /* write our own bitmap from the clips */ + vcp = vmalloc(sizeof(struct video_clip) * (clipcount + 4)); + if (vcp == NULL) { + dprintk(1, + KERN_ERR + "%s: setup_window() - Alloc of clip mask failed\n", + ZR_DEVNAME(zr)); + return -ENOMEM; + } + if (copy_from_user + (vcp, clips, sizeof(struct video_clip) * clipcount)) { + vfree(vcp); + return -EFAULT; + } + write_overlay_mask(file, vcp, clipcount); + vfree(vcp); + } + + fh->overlay_settings.is_set = 1; + if (fh->overlay_active != ZORAN_FREE && + zr->overlay_active != ZORAN_FREE) + zr->overlay_settings = fh->overlay_settings; + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + return wait_grab_pending(zr); +} + +static int +setup_overlay (struct file *file, + int on) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* If there is nothing to do, return immediatly */ + if ((on && fh->overlay_active != ZORAN_FREE) || + (!on && fh->overlay_active == ZORAN_FREE)) + return 0; + + /* check whether we're touching someone else's overlay */ + if (on && zr->overlay_active != ZORAN_FREE && + fh->overlay_active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - overlay is already active for another session\n", + ZR_DEVNAME(zr)); + return -EBUSY; + } + if (!on && zr->overlay_active != ZORAN_FREE && + fh->overlay_active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - you cannot cancel someone else's session\n", + ZR_DEVNAME(zr)); + return -EPERM; + } + + if (on == 0) { + zr->overlay_active = fh->overlay_active = ZORAN_FREE; + zr->v4l_overlay_active = 0; + /* When a grab is running, the video simply + * won't be switched on any more */ + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + } else { + if (!zr->buffer.base || !fh->overlay_settings.is_set) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - buffer or window not set\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (!fh->overlay_settings.format) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - no overlay format set\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + zr->overlay_active = fh->overlay_active = ZORAN_LOCKED; + zr->v4l_overlay_active = 1; + zr->overlay_mask = fh->overlay_mask; + zr->overlay_settings = fh->overlay_settings; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 1); + /* When a grab is running, the video will be + * switched on when grab is finished */ + } + + /* Make sure the changes come into effect */ + return wait_grab_pending(zr); +} + +#ifdef HAVE_V4L2 + /* get the status of a buffer in the clients buffer queue */ +static int +zoran_v4l2_buffer_status (struct file *file, + struct v4l2_buffer *buf, + int num) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + buf->flags = V4L2_BUF_FLAG_MAPPED; + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + + /* check range */ + if (num < 0 || num >= fh->v4l_buffers.num_buffers || + !fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: v4l2_buffer_status() - wrong number or buffers not allocated\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->length = fh->v4l_buffers.buffer_size; + + /* get buffer */ + buf->bytesused = fh->v4l_buffers.buffer[num].bs.length; + if (fh->v4l_buffers.buffer[num].state == BUZ_STATE_DONE || + fh->v4l_buffers.buffer[num].state == BUZ_STATE_USER) { + buf->sequence = fh->v4l_buffers.buffer[num].bs.seq; + buf->flags |= V4L2_BUF_FLAG_DONE; + buf->timestamp = + fh->v4l_buffers.buffer[num].bs.timestamp; + } else { + buf->flags |= V4L2_BUF_FLAG_QUEUED; + } + + if (fh->v4l_settings.height <= BUZ_MAX_HEIGHT / 2) + buf->field = V4L2_FIELD_TOP; + else + buf->field = V4L2_FIELD_INTERLACED; + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + /* check range */ + if (num < 0 || num >= fh->jpg_buffers.num_buffers || + !fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: v4l2_buffer_status() - wrong number or buffers not allocated\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + buf->type = (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) ? + V4L2_BUF_TYPE_VIDEO_CAPTURE : + V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf->length = fh->jpg_buffers.buffer_size; + + /* these variables are only written after frame has been captured */ + if (fh->jpg_buffers.buffer[num].state == BUZ_STATE_DONE || + fh->jpg_buffers.buffer[num].state == BUZ_STATE_USER) { + buf->sequence = fh->jpg_buffers.buffer[num].bs.seq; + buf->timestamp = + fh->jpg_buffers.buffer[num].bs.timestamp; + buf->bytesused = + fh->jpg_buffers.buffer[num].bs.length; + buf->flags |= V4L2_BUF_FLAG_DONE; + } else { + buf->flags |= V4L2_BUF_FLAG_QUEUED; + } + + /* which fields are these? */ + if (fh->jpg_settings.TmpDcm != 1) + buf->field = + fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; + else + buf->field = + fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_TB : + V4L2_FIELD_SEQ_BT; + + break; + + default: + + dprintk(5, + KERN_ERR + "%s: v4l2_buffer_status() - invalid buffer type|map_mode (%d|%d)\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + return -EINVAL; + } + + buf->memory = V4L2_MEMORY_MMAP; + buf->index = num; + buf->m.offset = buf->length * num; + + return 0; +} +#endif + +static int +zoran_set_norm (struct zoran *zr, + int norm) /* VIDEO_MODE_* */ +{ + int norm_encoder, on; + + if (zr->v4l_buffers.active != ZORAN_FREE || + zr->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: set_norm() called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + return -EBUSY; + } + + if (lock_norm && norm != zr->norm) { + if (lock_norm > 1) { + dprintk(1, + KERN_WARNING + "%s: set_norm() - TV standard is locked, can not switch norm\n", + ZR_DEVNAME(zr)); + return -EPERM; + } else { + dprintk(1, + KERN_WARNING + "%s: set_norm() - TV standard is locked, norm was not changed\n", + ZR_DEVNAME(zr)); + norm = zr->norm; + } + } + + if (norm != VIDEO_MODE_AUTO && + (norm < 0 || norm >= zr->card.norms || + !zr->card.tvn[norm])) { + dprintk(1, + KERN_ERR "%s: set_norm() - unsupported norm %d\n", + ZR_DEVNAME(zr), norm); + return -EINVAL; + } + + if (norm == VIDEO_MODE_AUTO) { + int status; + + /* if we have autodetect, ... */ + struct video_decoder_capability caps; + decoder_command(zr, DECODER_GET_CAPABILITIES, &caps); + if (!(caps.flags & VIDEO_DECODER_AUTO)) { + dprintk(1, KERN_ERR "%s: norm=auto unsupported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + decoder_command(zr, DECODER_SET_NORM, &norm); + + /* let changes come into effect */ + ssleep(2); + + decoder_command(zr, DECODER_GET_STATUS, &status); + if (!(status & DECODER_STATUS_GOOD)) { + dprintk(1, + KERN_ERR + "%s: set_norm() - no norm detected\n", + ZR_DEVNAME(zr)); + /* reset norm */ + decoder_command(zr, DECODER_SET_NORM, &zr->norm); + return -EIO; + } + + if (status & DECODER_STATUS_NTSC) + norm = VIDEO_MODE_NTSC; + else if (status & DECODER_STATUS_SECAM) + norm = VIDEO_MODE_SECAM; + else + norm = VIDEO_MODE_PAL; + } + zr->timing = zr->card.tvn[norm]; + norm_encoder = norm; + + /* We switch overlay off and on since a change in the + * norm needs different VFE settings */ + on = zr->overlay_active && !zr->v4l_memgrab_active; + if (on) + zr36057_overlay(zr, 0); + + decoder_command(zr, DECODER_SET_NORM, &norm); + encoder_command(zr, ENCODER_SET_NORM, &norm_encoder); + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + zr->norm = norm; + + return 0; +} + +static int +zoran_set_input (struct zoran *zr, + int input) +{ + int realinput; + + if (input == zr->input) { + return 0; + } + + if (zr->v4l_buffers.active != ZORAN_FREE || + zr->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: set_input() called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + return -EBUSY; + } + + if (input < 0 || input >= zr->card.inputs) { + dprintk(1, + KERN_ERR + "%s: set_input() - unnsupported input %d\n", + ZR_DEVNAME(zr), input); + return -EINVAL; + } + + realinput = zr->card.input[input].muxsel; + zr->input = input; + + decoder_command(zr, DECODER_SET_INPUT, &realinput); + + return 0; +} + +/* + * ioctl routine + */ + +static int +zoran_do_ioctl (struct inode *inode, + struct file *file, + unsigned int cmd, + void *arg) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + /* CAREFUL: used in multiple places here */ + struct zoran_jpg_settings settings; + + /* we might have older buffers lying around... We don't want + * to wait, but we do want to try cleaning them up ASAP. So + * we try to obtain the lock and free them. If that fails, we + * don't do anything and wait for the next turn. In the end, + * zoran_close() or a new allocation will still free them... + * This is just a 'the sooner the better' extra 'feature' + * + * We don't free the buffers right on munmap() because that + * causes oopses (kfree() inside munmap() oopses for no + * apparent reason - it's also not reproduceable in any way, + * but moving the free code outside the munmap() handler fixes + * all this... If someone knows why, please explain me (Ronald) + */ + if (!down_trylock(&zr->resource_lock)) { + /* we obtained it! Let's try to free some things */ + if (fh->jpg_buffers.ready_to_be_freed) + jpg_fbuffer_free(file); + if (fh->v4l_buffers.ready_to_be_freed) + v4l_fbuffer_free(file); + + up(&zr->resource_lock); + } + + switch (cmd) { + + case VIDIOCGCAP: + { + struct video_capability *vcap = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGCAP\n", ZR_DEVNAME(zr)); + + memset(vcap, 0, sizeof(struct video_capability)); + strncpy(vcap->name, ZR_DEVNAME(zr), sizeof(vcap->name)); + vcap->type = ZORAN_VID_TYPE; + + vcap->channels = zr->card.inputs; + vcap->audios = 0; + down(&zr->resource_lock); + vcap->maxwidth = BUZ_MAX_WIDTH; + vcap->maxheight = BUZ_MAX_HEIGHT; + vcap->minwidth = BUZ_MIN_WIDTH; + vcap->minheight = BUZ_MIN_HEIGHT; + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOCGCHAN: + { + struct video_channel *vchan = arg; + int channel = vchan->channel; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGCHAN - channel=%d\n", + ZR_DEVNAME(zr), vchan->channel); + + memset(vchan, 0, sizeof(struct video_channel)); + if (channel > zr->card.inputs || channel < 0) { + dprintk(1, + KERN_ERR + "%s: VIDIOCGCHAN on not existing channel %d\n", + ZR_DEVNAME(zr), channel); + return -EINVAL; + } + + strcpy(vchan->name, zr->card.input[channel].name); + + vchan->tuners = 0; + vchan->flags = 0; + vchan->type = VIDEO_TYPE_CAMERA; + down(&zr->resource_lock); + vchan->norm = zr->norm; + up(&zr->resource_lock); + vchan->channel = channel; + + return 0; + } + break; + + /* RJ: the documentation at http://roadrunner.swansea.linux.org.uk/v4lapi.shtml says: + * + * * "The VIDIOCSCHAN ioctl takes an integer argument and switches the capture to this input." + * * ^^^^^^^ + * * The famos BTTV driver has it implemented with a struct video_channel argument + * * and we follow it for compatibility reasons + * * + * * BTW: this is the only way the user can set the norm! + */ + + case VIDIOCSCHAN: + { + struct video_channel *vchan = arg; + int res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSCHAN - channel=%d, norm=%d\n", + ZR_DEVNAME(zr), vchan->channel, vchan->norm); + + down(&zr->resource_lock); + if ((res = zoran_set_input(zr, vchan->channel))) + goto schan_unlock_and_return; + if ((res = zoran_set_norm(zr, vchan->norm))) + goto schan_unlock_and_return; + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + schan_unlock_and_return: + up(&zr->resource_lock); + return res; + } + break; + + case VIDIOCGPICT: + { + struct video_picture *vpict = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGPICT\n", ZR_DEVNAME(zr)); + + memset(vpict, 0, sizeof(struct video_picture)); + down(&zr->resource_lock); + vpict->hue = zr->hue; + vpict->brightness = zr->brightness; + vpict->contrast = zr->contrast; + vpict->colour = zr->saturation; + if (fh->overlay_settings.format) { + vpict->depth = fh->overlay_settings.format->depth; + vpict->palette = fh->overlay_settings.format->palette; + } else { + vpict->depth = 0; + } + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOCSPICT: + { + struct video_picture *vpict = arg; + int i; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSPICT - bri=%d, hue=%d, col=%d, con=%d, dep=%d, pal=%d\n", + ZR_DEVNAME(zr), vpict->brightness, vpict->hue, + vpict->colour, vpict->contrast, vpict->depth, + vpict->palette); + + for (i = 0; i < zoran_num_formats; i++) { + const struct zoran_format *fmt = &zoran_formats[i]; + + if (fmt->palette != -1 && + fmt->flags & ZORAN_FORMAT_OVERLAY && + fmt->palette == vpict->palette && + fmt->depth == vpict->depth) + break; + } + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOCSPICT - Invalid palette %d\n", + ZR_DEVNAME(zr), vpict->palette); + return -EINVAL; + } + + down(&zr->resource_lock); + + decoder_command(zr, DECODER_SET_PICTURE, vpict); + + zr->hue = vpict->hue; + zr->contrast = vpict->contrast; + zr->saturation = vpict->colour; + zr->brightness = vpict->brightness; + + fh->overlay_settings.format = &zoran_formats[i]; + + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOCCAPTURE: + { + int *on = arg, res; + + dprintk(3, KERN_DEBUG "%s: VIDIOCCAPTURE - on=%d\n", + ZR_DEVNAME(zr), *on); + + down(&zr->resource_lock); + res = setup_overlay(file, *on); + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCGWIN: + { + struct video_window *vwin = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGWIN\n", ZR_DEVNAME(zr)); + + memset(vwin, 0, sizeof(struct video_window)); + down(&zr->resource_lock); + vwin->x = fh->overlay_settings.x; + vwin->y = fh->overlay_settings.y; + vwin->width = fh->overlay_settings.width; + vwin->height = fh->overlay_settings.height; + up(&zr->resource_lock); + vwin->clipcount = 0; + return 0; + } + break; + + case VIDIOCSWIN: + { + struct video_window *vwin = arg; + int res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSWIN - x=%d, y=%d, w=%d, h=%d, clipcount=%d\n", + ZR_DEVNAME(zr), vwin->x, vwin->y, vwin->width, + vwin->height, vwin->clipcount); + + down(&zr->resource_lock); + res = + setup_window(file, vwin->x, vwin->y, vwin->width, + vwin->height, vwin->clips, + vwin->clipcount, NULL); + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCGFBUF: + { + struct video_buffer *vbuf = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGFBUF\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + *vbuf = zr->buffer; + up(&zr->resource_lock); + return 0; + } + break; + + case VIDIOCSFBUF: + { + struct video_buffer *vbuf = arg; + int i, res = 0; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSFBUF - base=%p, w=%d, h=%d, depth=%d, bpl=%d\n", + ZR_DEVNAME(zr), vbuf->base, vbuf->width, + vbuf->height, vbuf->depth, vbuf->bytesperline); + + for (i = 0; i < zoran_num_formats; i++) + if (zoran_formats[i].depth == vbuf->depth) + break; + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOCSFBUF - invalid fbuf depth %d\n", + ZR_DEVNAME(zr), vbuf->depth); + return -EINVAL; + } + + down(&zr->resource_lock); + res = + setup_fbuffer(file, vbuf->base, &zoran_formats[i], + vbuf->width, vbuf->height, + vbuf->bytesperline); + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCSYNC: + { + int *frame = arg, res; + + dprintk(3, KERN_DEBUG "%s: VIDIOCSYNC - frame=%d\n", + ZR_DEVNAME(zr), *frame); + + down(&zr->resource_lock); + res = v4l_sync(file, *frame); + up(&zr->resource_lock); + if (!res) + zr->v4l_sync_tail++; + return res; + } + break; + + case VIDIOCMCAPTURE: + { + struct video_mmap *vmap = arg; + int res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCMCAPTURE - frame=%d, geom=%dx%d, fmt=%d\n", + ZR_DEVNAME(zr), vmap->frame, vmap->width, vmap->height, + vmap->format); + + down(&zr->resource_lock); + res = v4l_grab(file, vmap); + up(&zr->resource_lock); + return res; + } + break; + + case VIDIOCGMBUF: + { + struct video_mbuf *vmbuf = arg; + int i, res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGMBUF\n", ZR_DEVNAME(zr)); + + vmbuf->size = + fh->v4l_buffers.num_buffers * + fh->v4l_buffers.buffer_size; + vmbuf->frames = fh->v4l_buffers.num_buffers; + for (i = 0; i < vmbuf->frames; i++) { + vmbuf->offsets[i] = + i * fh->v4l_buffers.buffer_size; + } + + down(&zr->resource_lock); + + if (fh->jpg_buffers.allocated || fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOCGMBUF - buffers already allocated\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto v4l1reqbuf_unlock_and_return; + } + + if (v4l_fbuffer_alloc(file)) { + res = -ENOMEM; + goto v4l1reqbuf_unlock_and_return; + } + + /* The next mmap will map the V4L buffers */ + fh->map_mode = ZORAN_MAP_MODE_RAW; + v4l1reqbuf_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCGUNIT: + { + struct video_unit *vunit = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGUNIT\n", ZR_DEVNAME(zr)); + + vunit->video = zr->video_dev->minor; + vunit->vbi = VIDEO_NO_UNIT; + vunit->radio = VIDEO_NO_UNIT; + vunit->audio = VIDEO_NO_UNIT; + vunit->teletext = VIDEO_NO_UNIT; + + return 0; + } + break; + + /* + * RJ: In principal we could support subcaptures for V4L grabbing. + * Not even the famous BTTV driver has them, however. + * If there should be a strong demand, one could consider + * to implement them. + */ + case VIDIOCGCAPTURE: + { + dprintk(3, KERN_ERR "%s: VIDIOCGCAPTURE not supported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + break; + + case VIDIOCSCAPTURE: + { + dprintk(3, KERN_ERR "%s: VIDIOCSCAPTURE not supported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + break; + + case BUZIOC_G_PARAMS: + { + struct zoran_params *bparams = arg; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_G_PARAMS\n", ZR_DEVNAME(zr)); + + memset(bparams, 0, sizeof(struct zoran_params)); + bparams->major_version = MAJOR_VERSION; + bparams->minor_version = MINOR_VERSION; + + down(&zr->resource_lock); + + bparams->norm = zr->norm; + bparams->input = zr->input; + + bparams->decimation = fh->jpg_settings.decimation; + bparams->HorDcm = fh->jpg_settings.HorDcm; + bparams->VerDcm = fh->jpg_settings.VerDcm; + bparams->TmpDcm = fh->jpg_settings.TmpDcm; + bparams->field_per_buff = fh->jpg_settings.field_per_buff; + bparams->img_x = fh->jpg_settings.img_x; + bparams->img_y = fh->jpg_settings.img_y; + bparams->img_width = fh->jpg_settings.img_width; + bparams->img_height = fh->jpg_settings.img_height; + bparams->odd_even = fh->jpg_settings.odd_even; + + bparams->quality = fh->jpg_settings.jpg_comp.quality; + bparams->APPn = fh->jpg_settings.jpg_comp.APPn; + bparams->APP_len = fh->jpg_settings.jpg_comp.APP_len; + memcpy(bparams->APP_data, + fh->jpg_settings.jpg_comp.APP_data, + sizeof(bparams->APP_data)); + bparams->COM_len = zr->jpg_settings.jpg_comp.COM_len; + memcpy(bparams->COM_data, + fh->jpg_settings.jpg_comp.COM_data, + sizeof(bparams->COM_data)); + bparams->jpeg_markers = + fh->jpg_settings.jpg_comp.jpeg_markers; + + up(&zr->resource_lock); + + bparams->VFIFO_FB = 0; + + return 0; + } + break; + + case BUZIOC_S_PARAMS: + { + struct zoran_params *bparams = arg; + int res = 0; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_S_PARAMS\n", ZR_DEVNAME(zr)); + + settings.decimation = bparams->decimation; + settings.HorDcm = bparams->HorDcm; + settings.VerDcm = bparams->VerDcm; + settings.TmpDcm = bparams->TmpDcm; + settings.field_per_buff = bparams->field_per_buff; + settings.img_x = bparams->img_x; + settings.img_y = bparams->img_y; + settings.img_width = bparams->img_width; + settings.img_height = bparams->img_height; + settings.odd_even = bparams->odd_even; + + settings.jpg_comp.quality = bparams->quality; + settings.jpg_comp.APPn = bparams->APPn; + settings.jpg_comp.APP_len = bparams->APP_len; + memcpy(settings.jpg_comp.APP_data, bparams->APP_data, + sizeof(bparams->APP_data)); + settings.jpg_comp.COM_len = bparams->COM_len; + memcpy(settings.jpg_comp.COM_data, bparams->COM_data, + sizeof(bparams->COM_data)); + settings.jpg_comp.jpeg_markers = bparams->jpeg_markers; + + down(&zr->resource_lock); + + if (zr->codec_mode != BUZ_MODE_IDLE) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_S_PARAMS called, but Buz in capture/playback mode\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto sparams_unlock_and_return; + } + + /* Check the params first before overwriting our + * nternal values */ + if (zoran_check_jpg_settings(zr, &settings)) { + res = -EINVAL; + goto sparams_unlock_and_return; + } + + fh->jpg_settings = settings; + sparams_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_REQBUFS: + { + struct zoran_requestbuffers *breq = arg; + int res = 0; + + dprintk(3, + KERN_DEBUG + "%s: BUZIOC_REQBUFS - count=%lu, size=%lu\n", + ZR_DEVNAME(zr), breq->count, breq->size); + + /* Enforce reasonable lower and upper limits */ + if (breq->count < 4) + breq->count = 4; /* Could be choosen smaller */ + if (breq->count > jpg_nbufs) + breq->count = jpg_nbufs; + breq->size = PAGE_ALIGN(breq->size); + if (breq->size < 8192) + breq->size = 8192; /* Arbitrary */ + /* breq->size is limited by 1 page for the stat_com + * tables to a Maximum of 2 MB */ + if (breq->size > jpg_bufsize) + breq->size = jpg_bufsize; + if (fh->jpg_buffers.need_contiguous && + breq->size > MAX_KMALLOC_MEM) + breq->size = MAX_KMALLOC_MEM; + + down(&zr->resource_lock); + + if (fh->jpg_buffers.allocated || fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_REQBUFS - buffers allready allocated\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto jpgreqbuf_unlock_and_return; + } + + fh->jpg_buffers.num_buffers = breq->count; + fh->jpg_buffers.buffer_size = breq->size; + + if (jpg_fbuffer_alloc(file)) { + res = -ENOMEM; + goto jpgreqbuf_unlock_and_return; + } + + /* The next mmap will map the MJPEG buffers - could + * also be *_PLAY, but it doesn't matter here */ + fh->map_mode = ZORAN_MAP_MODE_JPG_REC; + jpgreqbuf_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_QBUF_CAPT: + { + int *frame = arg, res; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_QBUF_CAPT - frame=%d\n", + ZR_DEVNAME(zr), *frame); + + down(&zr->resource_lock); + res = jpg_qbuf(file, *frame, BUZ_MODE_MOTION_COMPRESS); + up(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_QBUF_PLAY: + { + int *frame = arg, res; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_QBUF_PLAY - frame=%d\n", + ZR_DEVNAME(zr), *frame); + + down(&zr->resource_lock); + res = jpg_qbuf(file, *frame, BUZ_MODE_MOTION_DECOMPRESS); + up(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_SYNC: + { + struct zoran_sync *bsync = arg; + int res; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_SYNC\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + res = jpg_sync(file, bsync); + up(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_G_STATUS: + { + struct zoran_status *bstat = arg; + int norm, input, status, res = 0; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_G_STATUS\n", ZR_DEVNAME(zr)); + + if (zr->codec_mode != BUZ_MODE_IDLE) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_G_STATUS called but Buz in capture/playback mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + input = zr->card.input[bstat->input].muxsel; + norm = VIDEO_MODE_AUTO; + + down(&zr->resource_lock); + + if (zr->codec_mode != BUZ_MODE_IDLE) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_G_STATUS called, but Buz in capture/playback mode\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto gstat_unlock_and_return; + } + + decoder_command(zr, DECODER_SET_INPUT, &input); + decoder_command(zr, DECODER_SET_NORM, &norm); + + /* sleep 1 second */ + ssleep(1); + + /* Get status of video decoder */ + decoder_command(zr, DECODER_GET_STATUS, &status); + + /* restore previous input and norm */ + input = zr->card.input[zr->input].muxsel; + decoder_command(zr, DECODER_SET_INPUT, &input); + decoder_command(zr, DECODER_SET_NORM, &zr->norm); + gstat_unlock_and_return: + up(&zr->resource_lock); + + if (!res) { + bstat->signal = + (status & DECODER_STATUS_GOOD) ? 1 : 0; + if (status & DECODER_STATUS_NTSC) + bstat->norm = VIDEO_MODE_NTSC; + else if (status & DECODER_STATUS_SECAM) + bstat->norm = VIDEO_MODE_SECAM; + else + bstat->norm = VIDEO_MODE_PAL; + + bstat->color = + (status & DECODER_STATUS_COLOR) ? 1 : 0; + } + + return res; + } + break; + +#ifdef HAVE_V4L2 + + /* The new video4linux2 capture interface - much nicer than video4linux1, since + * it allows for integrating the JPEG capturing calls inside standard v4l2 + */ + + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_QUERYCAP\n", ZR_DEVNAME(zr)); + + memset(cap, 0, sizeof(*cap)); + strncpy(cap->card, ZR_DEVNAME(zr), sizeof(cap->card)); + strncpy(cap->driver, "zoran", sizeof(cap->driver)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(zr->pci_dev)); + cap->version = + KERNEL_VERSION(MAJOR_VERSION, MINOR_VERSION, + RELEASE_VERSION); + cap->capabilities = ZORAN_V4L2_VID_FLAGS; + + return 0; + } + break; + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *fmt = arg; + int index = fmt->index, num = -1, i, flag = 0, type = + fmt->type; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUM_FMT - index=%d\n", + ZR_DEVNAME(zr), fmt->index); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + flag = ZORAN_FORMAT_CAPTURE; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + flag = ZORAN_FORMAT_PLAYBACK; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + flag = ZORAN_FORMAT_OVERLAY; + break; + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_ENUM_FMT - unknown type %d\n", + ZR_DEVNAME(zr), fmt->type); + return -EINVAL; + } + + for (i = 0; i < zoran_num_formats; i++) { + if (zoran_formats[i].flags & flag) + num++; + if (num == fmt->index) + break; + } + if (fmt->index < 0 /* late, but not too late */ || + i == zoran_num_formats) + return -EINVAL; + + memset(fmt, 0, sizeof(*fmt)); + fmt->index = index; + fmt->type = type; + strncpy(fmt->description, zoran_formats[i].name, 31); + fmt->pixelformat = zoran_formats[i].fourcc; + if (zoran_formats[i].flags & ZORAN_FORMAT_COMPRESSED) + fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; + + return 0; + } + break; + + case VIDIOC_G_FMT: + { + struct v4l2_format *fmt = arg; + int type = fmt->type; + + dprintk(5, KERN_DEBUG "%s: VIDIOC_G_FMT\n", ZR_DEVNAME(zr)); + + memset(fmt, 0, sizeof(*fmt)); + fmt->type = type; + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + + down(&zr->resource_lock); + + fmt->fmt.win.w.left = fh->overlay_settings.x; + fmt->fmt.win.w.top = fh->overlay_settings.y; + fmt->fmt.win.w.width = fh->overlay_settings.width; + fmt->fmt.win.w.height = + fh->overlay_settings.height; + if (fh->overlay_settings.width * 2 > + BUZ_MAX_HEIGHT) + fmt->fmt.win.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.win.field = V4L2_FIELD_TOP; + + up(&zr->resource_lock); + + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + + down(&zr->resource_lock); + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + fh->map_mode == ZORAN_MAP_MODE_RAW) { + + fmt->fmt.pix.width = + fh->v4l_settings.width; + fmt->fmt.pix.height = + fh->v4l_settings.height; + fmt->fmt.pix.sizeimage = + fh->v4l_buffers.buffer_size; + fmt->fmt.pix.pixelformat = + fh->v4l_settings.format->fourcc; + fmt->fmt.pix.colorspace = + fh->v4l_settings.format->colorspace; + fmt->fmt.pix.bytesperline = 0; + if (BUZ_MAX_HEIGHT < + (fh->v4l_settings.height * 2)) + fmt->fmt.pix.field = + V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = + V4L2_FIELD_TOP; + + } else { + + fmt->fmt.pix.width = + fh->jpg_settings.img_width / + fh->jpg_settings.HorDcm; + fmt->fmt.pix.height = + fh->jpg_settings.img_height / + (fh->jpg_settings.VerDcm * + fh->jpg_settings.TmpDcm); + fmt->fmt.pix.sizeimage = + zoran_v4l2_calc_bufsize(&fh-> + jpg_settings); + fmt->fmt.pix.pixelformat = + V4L2_PIX_FMT_MJPEG; + if (fh->jpg_settings.TmpDcm == 1) + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_BT : + V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : + V4L2_FIELD_BOTTOM); + + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.colorspace = + V4L2_COLORSPACE_SMPTE170M; + } + + up(&zr->resource_lock); + + break; + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_FMT - unsupported type %d\n", + ZR_DEVNAME(zr), fmt->type); + return -EINVAL; + } + return 0; + } + break; + + case VIDIOC_S_FMT: + { + struct v4l2_format *fmt = arg; + int i, res = 0; + __u32 printformat; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_FMT - type=%d, ", + ZR_DEVNAME(zr), fmt->type); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + + dprintk(3, "x=%d, y=%d, w=%d, h=%d, cnt=%d, map=0x%p\n", + fmt->fmt.win.w.left, fmt->fmt.win.w.top, + fmt->fmt.win.w.width, + fmt->fmt.win.w.height, + fmt->fmt.win.clipcount, + fmt->fmt.win.bitmap); + down(&zr->resource_lock); + res = + setup_window(file, fmt->fmt.win.w.left, + fmt->fmt.win.w.top, + fmt->fmt.win.w.width, + fmt->fmt.win.w.height, + (struct video_clip __user *) + fmt->fmt.win.clips, + fmt->fmt.win.clipcount, + fmt->fmt.win.bitmap); + up(&zr->resource_lock); + return res; + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + + printformat = + __cpu_to_le32(fmt->fmt.pix.pixelformat); + dprintk(3, "size=%dx%d, fmt=0x%x (%4.4s)\n", + fmt->fmt.pix.width, fmt->fmt.pix.height, + fmt->fmt.pix.pixelformat, + (char *) &printformat); + + if (fmt->fmt.pix.bytesperline > 0) { + dprintk(5, + KERN_ERR "%s: bpl not supported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + /* we can be requested to do JPEG/raw playback/capture */ + if (! + (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && + fmt->fmt.pix.pixelformat == + V4L2_PIX_FMT_MJPEG))) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - unknown type %d/0x%x(%4.4s) combination\n", + ZR_DEVNAME(zr), fmt->type, + fmt->fmt.pix.pixelformat, + (char *) &printformat); + return -EINVAL; + } + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { + down(&zr->resource_lock); + + settings = fh->jpg_settings; + + if (fh->v4l_buffers.allocated || + fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - cannot change capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sfmtjpg_unlock_and_return; + } + + /* we actually need to set 'real' parameters now */ + if ((fmt->fmt.pix.height * 2) > + BUZ_MAX_HEIGHT) + settings.TmpDcm = 1; + else + settings.TmpDcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= + fh->jpg_settings.img_height / 2) + settings.VerDcm = 2; + else + settings.VerDcm = 1; + if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 4) + settings.HorDcm = 4; + else if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 2) + settings.HorDcm = 2; + else + settings.HorDcm = 1; + if (settings.TmpDcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + /* check */ + if ((res = + zoran_check_jpg_settings(zr, + &settings))) + goto sfmtjpg_unlock_and_return; + + /* it's ok, so set them */ + fh->jpg_settings = settings; + + /* tell the user what we actually did */ + fmt->fmt.pix.width = + settings.img_width / settings.HorDcm; + fmt->fmt.pix.height = + settings.img_height * 2 / + (settings.TmpDcm * settings.VerDcm); + if (settings.TmpDcm == 1) + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_TB : + V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : + V4L2_FIELD_BOTTOM); + fh->jpg_buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh-> + jpg_settings); + fmt->fmt.pix.sizeimage = + fh->jpg_buffers.buffer_size; + + /* we hereby abuse this variable to show that + * we're gonna do mjpeg capture */ + fh->map_mode = + (fmt->type == + V4L2_BUF_TYPE_VIDEO_CAPTURE) ? + ZORAN_MAP_MODE_JPG_REC : + ZORAN_MAP_MODE_JPG_PLAY; + sfmtjpg_unlock_and_return: + up(&zr->resource_lock); + } else { + for (i = 0; i < zoran_num_formats; i++) + if (fmt->fmt.pix.pixelformat == + zoran_formats[i].fourcc) + break; + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - unknown/unsupported format 0x%x (%4.4s)\n", + ZR_DEVNAME(zr), + fmt->fmt.pix.pixelformat, + (char *) &printformat); + return -EINVAL; + } + down(&zr->resource_lock); + if (fh->jpg_buffers.allocated || + (fh->v4l_buffers.allocated && + fh->v4l_buffers.active != + ZORAN_FREE)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - cannot change capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sfmtv4l_unlock_and_return; + } + if (fmt->fmt.pix.height > BUZ_MAX_HEIGHT) + fmt->fmt.pix.height = + BUZ_MAX_HEIGHT; + if (fmt->fmt.pix.width > BUZ_MAX_WIDTH) + fmt->fmt.pix.width = BUZ_MAX_WIDTH; + + if ((res = + zoran_v4l_set_format(file, + fmt->fmt.pix. + width, + fmt->fmt.pix. + height, + &zoran_formats + [i]))) + goto sfmtv4l_unlock_and_return; + + /* tell the user the + * results/missing stuff */ + fmt->fmt.pix.sizeimage = fh->v4l_buffers.buffer_size /*zr->gbpl * zr->gheight */ + ; + if (BUZ_MAX_HEIGHT < + (fh->v4l_settings.height * 2)) + fmt->fmt.pix.field = + V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = + V4L2_FIELD_TOP; + + fh->map_mode = ZORAN_MAP_MODE_RAW; + sfmtv4l_unlock_and_return: + up(&zr->resource_lock); + } + + break; + + default: + dprintk(3, "unsupported\n"); + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - unsupported type %d\n", + ZR_DEVNAME(zr), fmt->type); + return -EINVAL; + } + + return res; + } + break; + + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_FBUF\n", ZR_DEVNAME(zr)); + + memset(fb, 0, sizeof(*fb)); + down(&zr->resource_lock); + fb->base = zr->buffer.base; + fb->fmt.width = zr->buffer.width; + fb->fmt.height = zr->buffer.height; + if (zr->overlay_settings.format) { + fb->fmt.pixelformat = + fh->overlay_settings.format->fourcc; + } + fb->fmt.bytesperline = zr->buffer.bytesperline; + up(&zr->resource_lock); + fb->fmt.colorspace = V4L2_COLORSPACE_SRGB; + fb->fmt.field = V4L2_FIELD_INTERLACED; + fb->flags = V4L2_FBUF_FLAG_OVERLAY; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + + return 0; + } + break; + + case VIDIOC_S_FBUF: + { + int i, res = 0; + struct v4l2_framebuffer *fb = arg; + __u32 printformat = __cpu_to_le32(fb->fmt.pixelformat); + + dprintk(3, + KERN_DEBUG + "%s: VIDIOC_S_FBUF - base=0x%p, size=%dx%d, bpl=%d, fmt=0x%x (%4.4s)\n", + ZR_DEVNAME(zr), fb->base, fb->fmt.width, fb->fmt.height, + fb->fmt.bytesperline, fb->fmt.pixelformat, + (char *) &printformat); + + for (i = 0; i < zoran_num_formats; i++) + if (zoran_formats[i].fourcc == fb->fmt.pixelformat) + break; + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FBUF - format=0x%x (%4.4s) not allowed\n", + ZR_DEVNAME(zr), fb->fmt.pixelformat, + (char *) &printformat); + return -EINVAL; + } + + down(&zr->resource_lock); + res = + setup_fbuffer(file, fb->base, &zoran_formats[i], + fb->fmt.width, fb->fmt.height, + fb->fmt.bytesperline); + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_OVERLAY: + { + int *on = arg, res; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_PREVIEW - on=%d\n", + ZR_DEVNAME(zr), *on); + + down(&zr->resource_lock); + res = setup_overlay(file, *on); + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + int res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_REQBUFS - type=%d\n", + ZR_DEVNAME(zr), req->type); + + if (req->memory != V4L2_MEMORY_MMAP) { + dprintk(1, + KERN_ERR + "%s: only MEMORY_MMAP capture is supported, not %d\n", + ZR_DEVNAME(zr), req->memory); + return -EINVAL; + } + + down(&zr->resource_lock); + + if (fh->v4l_buffers.allocated || fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_REQBUFS - buffers allready allocated\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto v4l2reqbuf_unlock_and_return; + } + + if (fh->map_mode == ZORAN_MAP_MODE_RAW && + req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + + /* control user input */ + if (req->count < 2) + req->count = 2; + if (req->count > v4l_nbufs) + req->count = v4l_nbufs; + fh->v4l_buffers.num_buffers = req->count; + + if (v4l_fbuffer_alloc(file)) { + res = -ENOMEM; + goto v4l2reqbuf_unlock_and_return; + } + + /* The next mmap will map the V4L buffers */ + fh->map_mode = ZORAN_MAP_MODE_RAW; + + } else if (fh->map_mode == ZORAN_MAP_MODE_JPG_REC || + fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) { + + /* we need to calculate size ourselves now */ + if (req->count < 4) + req->count = 4; + if (req->count > jpg_nbufs) + req->count = jpg_nbufs; + fh->jpg_buffers.num_buffers = req->count; + fh->jpg_buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh->jpg_settings); + + if (jpg_fbuffer_alloc(file)) { + res = -ENOMEM; + goto v4l2reqbuf_unlock_and_return; + } + + /* The next mmap will map the MJPEG buffers */ + if (req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + fh->map_mode = ZORAN_MAP_MODE_JPG_REC; + else + fh->map_mode = ZORAN_MAP_MODE_JPG_PLAY; + + } else { + dprintk(1, + KERN_ERR + "%s: VIDIOC_REQBUFS - unknown type %d\n", + ZR_DEVNAME(zr), req->type); + res = -EINVAL; + goto v4l2reqbuf_unlock_and_return; + } + v4l2reqbuf_unlock_and_return: + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + __u32 type = buf->type; + int index = buf->index, res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOC_QUERYBUF - index=%d, type=%d\n", + ZR_DEVNAME(zr), buf->index, buf->type); + + memset(buf, 0, sizeof(buf)); + buf->type = type; + buf->index = index; + + down(&zr->resource_lock); + res = zoran_v4l2_buffer_status(file, buf, buf->index); + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int res = 0, codec_mode, buf_type; + + dprintk(3, + KERN_DEBUG "%s: VIDIOC_QBUF - type=%d, index=%d\n", + ZR_DEVNAME(zr), buf->type, buf->index); + + down(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + + res = zoran_v4l_queue_frame(file, buf->index); + if (res) + goto qbuf_unlock_and_return; + if (!zr->v4l_memgrab_active && + fh->v4l_buffers.active == ZORAN_LOCKED) + zr36057_set_memgrab(zr, 1); + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) { + buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + codec_mode = BUZ_MODE_MOTION_DECOMPRESS; + } else { + buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + codec_mode = BUZ_MODE_MOTION_COMPRESS; + } + + if (buf->type != buf_type) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + + res = + zoran_jpg_queue_frame(file, buf->index, + codec_mode); + if (res != 0) + goto qbuf_unlock_and_return; + if (zr->codec_mode == BUZ_MODE_IDLE && + fh->jpg_buffers.active == ZORAN_LOCKED) { + zr36057_enable_jpg(zr, codec_mode); + } + break; + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - unsupported type %d\n", + ZR_DEVNAME(zr), buf->type); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + qbuf_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int res = 0, buf_type, num = -1; /* compiler borks here (?) */ + + dprintk(3, KERN_DEBUG "%s: VIDIOC_DQBUF - type=%d\n", + ZR_DEVNAME(zr), buf->type); + + down(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + + num = zr->v4l_pend[zr->v4l_sync_tail & V4L_MASK_FRAME]; + if (file->f_flags & O_NONBLOCK && + zr->v4l_buffers.buffer[num].state != + BUZ_STATE_DONE) { + res = -EAGAIN; + goto dqbuf_unlock_and_return; + } + res = v4l_sync(file, num); + if (res) + goto dqbuf_unlock_and_return; + else + zr->v4l_sync_tail++; + res = zoran_v4l2_buffer_status(file, buf, num); + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + { + struct zoran_sync bs; + + if (fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) + buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + else + buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (buf->type != buf_type) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + + num = + zr->jpg_pend[zr-> + jpg_que_tail & BUZ_MASK_FRAME]; + + if (file->f_flags & O_NONBLOCK && + zr->jpg_buffers.buffer[num].state != + BUZ_STATE_DONE) { + res = -EAGAIN; + goto dqbuf_unlock_and_return; + } + res = jpg_sync(file, &bs); + if (res) + goto dqbuf_unlock_and_return; + res = + zoran_v4l2_buffer_status(file, buf, bs.frame); + break; + } + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_DQBUF - unsupported type %d\n", + ZR_DEVNAME(zr), buf->type); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + dqbuf_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_STREAMON: + { + int res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_STREAMON\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: /* raw capture */ + if (zr->v4l_buffers.active != ZORAN_ACTIVE || + fh->v4l_buffers.active != ZORAN_ACTIVE) { + res = -EBUSY; + goto strmon_unlock_and_return; + } + + zr->v4l_buffers.active = fh->v4l_buffers.active = + ZORAN_LOCKED; + zr->v4l_settings = fh->v4l_settings; + + zr->v4l_sync_tail = zr->v4l_pend_tail; + if (!zr->v4l_memgrab_active && + zr->v4l_pend_head != zr->v4l_pend_tail) { + zr36057_set_memgrab(zr, 1); + } + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + /* what is the codec mode right now? */ + if (zr->jpg_buffers.active != ZORAN_ACTIVE || + fh->jpg_buffers.active != ZORAN_ACTIVE) { + res = -EBUSY; + goto strmon_unlock_and_return; + } + + zr->jpg_buffers.active = fh->jpg_buffers.active = + ZORAN_LOCKED; + + if (zr->jpg_que_head != zr->jpg_que_tail) { + /* Start the jpeg codec when the first frame is queued */ + jpeg_start(zr); + } + + break; + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_STREAMON - invalid map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = -EINVAL; + goto strmon_unlock_and_return; + } + strmon_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_STREAMOFF: + { + int i, res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_STREAMOFF\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: /* raw capture */ + if (fh->v4l_buffers.active == ZORAN_FREE && + zr->v4l_buffers.active != ZORAN_FREE) { + res = -EPERM; /* stay off other's settings! */ + goto strmoff_unlock_and_return; + } + if (zr->v4l_buffers.active == ZORAN_FREE) + goto strmoff_unlock_and_return; + + /* unload capture */ + if (zr->v4l_memgrab_active) + zr36057_set_memgrab(zr, 0); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) + zr->v4l_buffers.buffer[i].state = + BUZ_STATE_USER; + fh->v4l_buffers = zr->v4l_buffers; + + zr->v4l_buffers.active = fh->v4l_buffers.active = + ZORAN_FREE; + + zr->v4l_grab_seq = 0; + zr->v4l_pend_head = zr->v4l_pend_tail = 0; + zr->v4l_sync_tail = 0; + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->jpg_buffers.active == ZORAN_FREE && + zr->jpg_buffers.active != ZORAN_FREE) { + res = -EPERM; /* stay off other's settings! */ + goto strmoff_unlock_and_return; + } + if (zr->jpg_buffers.active == ZORAN_FREE) + goto strmoff_unlock_and_return; + + res = + jpg_qbuf(file, -1, + (fh->map_mode == + ZORAN_MAP_MODE_JPG_REC) ? + BUZ_MODE_MOTION_COMPRESS : + BUZ_MODE_MOTION_DECOMPRESS); + if (res) + goto strmoff_unlock_and_return; + break; + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_STREAMOFF - invalid map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = -EINVAL; + goto strmoff_unlock_and_return; + } + strmoff_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_QUERYCTRL - id=%d\n", + ZR_DEVNAME(zr), ctrl->id); + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + else { + int id = ctrl->id; + memset(ctrl, 0, sizeof(*ctrl)); + ctrl->id = id; + } + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + strncpy(ctrl->name, "Brightness", 31); + break; + case V4L2_CID_CONTRAST: + strncpy(ctrl->name, "Contrast", 31); + break; + case V4L2_CID_SATURATION: + strncpy(ctrl->name, "Saturation", 31); + break; + case V4L2_CID_HUE: + strncpy(ctrl->name, "Hue", 31); + break; + } + + ctrl->minimum = 0; + ctrl->maximum = 65535; + ctrl->step = 1; + ctrl->default_value = 32768; + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + + return 0; + } + break; + + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_CTRL - id=%d\n", + ZR_DEVNAME(zr), ctrl->id); + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + down(&zr->resource_lock); + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = zr->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = zr->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = zr->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = zr->hue; + break; + } + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + struct video_picture pict; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_CTRL - id=%d\n", + ZR_DEVNAME(zr), ctrl->id); + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + if (ctrl->value < 0 || ctrl->value > 65535) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_CTRL - invalid value %d for id=%d\n", + ZR_DEVNAME(zr), ctrl->value, ctrl->id); + return -EINVAL; + } + + down(&zr->resource_lock); + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + zr->brightness = ctrl->value; + break; + case V4L2_CID_CONTRAST: + zr->contrast = ctrl->value; + break; + case V4L2_CID_SATURATION: + zr->saturation = ctrl->value; + break; + case V4L2_CID_HUE: + zr->hue = ctrl->value; + break; + } + pict.brightness = zr->brightness; + pict.contrast = zr->contrast; + pict.colour = zr->saturation; + pict.hue = zr->hue; + + decoder_command(zr, DECODER_SET_PICTURE, &pict); + + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *std = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUMSTD - index=%d\n", + ZR_DEVNAME(zr), std->index); + + if (std->index < 0 || std->index >= (zr->card.norms + 1)) + return -EINVAL; + else { + int id = std->index; + memset(std, 0, sizeof(*std)); + std->index = id; + } + + if (std->index == zr->card.norms) { + /* if we have autodetect, ... */ + struct video_decoder_capability caps; + decoder_command(zr, DECODER_GET_CAPABILITIES, + &caps); + if (caps.flags & VIDEO_DECODER_AUTO) { + std->id = V4L2_STD_ALL; + strncpy(std->name, "Autodetect", 31); + return 0; + } else + return -EINVAL; + } + switch (std->index) { + case 0: + std->id = V4L2_STD_PAL; + strncpy(std->name, "PAL", 31); + std->frameperiod.numerator = 1; + std->frameperiod.denominator = 25; + std->framelines = zr->card.tvn[0]->Ht; + break; + case 1: + std->id = V4L2_STD_NTSC; + strncpy(std->name, "NTSC", 31); + std->frameperiod.numerator = 1001; + std->frameperiod.denominator = 30000; + std->framelines = zr->card.tvn[1]->Ht; + break; + case 2: + std->id = V4L2_STD_SECAM; + strncpy(std->name, "SECAM", 31); + std->frameperiod.numerator = 1; + std->frameperiod.denominator = 25; + std->framelines = zr->card.tvn[2]->Ht; + break; + } + + return 0; + } + break; + + case VIDIOC_G_STD: + { + v4l2_std_id *std = arg; + int norm; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_STD\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + norm = zr->norm; + up(&zr->resource_lock); + + switch (norm) { + case VIDEO_MODE_PAL: + *std = V4L2_STD_PAL; + break; + case VIDEO_MODE_NTSC: + *std = V4L2_STD_NTSC; + break; + case VIDEO_MODE_SECAM: + *std = V4L2_STD_SECAM; + break; + } + + return 0; + } + break; + + case VIDIOC_S_STD: + { + int norm = -1, res = 0; + v4l2_std_id *std = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_STD - norm=0x%llx\n", + ZR_DEVNAME(zr), (unsigned long long)*std); + + if (*std == V4L2_STD_PAL) + norm = VIDEO_MODE_PAL; + else if (*std == V4L2_STD_NTSC) + norm = VIDEO_MODE_NTSC; + else if (*std == V4L2_STD_SECAM) + norm = VIDEO_MODE_SECAM; + else if (*std == V4L2_STD_ALL) + norm = VIDEO_MODE_AUTO; + else { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_STD - invalid norm 0x%llx\n", + ZR_DEVNAME(zr), (unsigned long long)*std); + return -EINVAL; + } + + down(&zr->resource_lock); + if ((res = zoran_set_norm(zr, norm))) + goto sstd_unlock_and_return; + + res = wait_grab_pending(zr); + sstd_unlock_and_return: + up(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *inp = arg; + int status; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUMINPUT - index=%d\n", + ZR_DEVNAME(zr), inp->index); + + if (inp->index < 0 || inp->index >= zr->card.inputs) + return -EINVAL; + else { + int id = inp->index; + memset(inp, 0, sizeof(*inp)); + inp->index = id; + } + + strncpy(inp->name, zr->card.input[inp->index].name, + sizeof(inp->name) - 1); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = V4L2_STD_ALL; + + /* Get status of video decoder */ + down(&zr->resource_lock); + decoder_command(zr, DECODER_GET_STATUS, &status); + up(&zr->resource_lock); + + if (!(status & DECODER_STATUS_GOOD)) { + inp->status |= V4L2_IN_ST_NO_POWER; + inp->status |= V4L2_IN_ST_NO_SIGNAL; + } + if (!(status & DECODER_STATUS_COLOR)) + inp->status |= V4L2_IN_ST_NO_COLOR; + + return 0; + } + break; + + case VIDIOC_G_INPUT: + { + int *input = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_INPUT\n", ZR_DEVNAME(zr)); + + down(&zr->resource_lock); + *input = zr->input; + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_S_INPUT: + { + int *input = arg, res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_INPUT - input=%d\n", + ZR_DEVNAME(zr), *input); + + down(&zr->resource_lock); + if ((res = zoran_set_input(zr, *input))) + goto sinput_unlock_and_return; + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + sinput_unlock_and_return: + up(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *outp = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUMOUTPUT - index=%d\n", + ZR_DEVNAME(zr), outp->index); + + if (outp->index != 0) + return -EINVAL; + + memset(outp, 0, sizeof(*outp)); + outp->index = 0; + outp->type = V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY; + strncpy(outp->name, "Autodetect", 31); + + return 0; + } + break; + + case VIDIOC_G_OUTPUT: + { + int *output = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_OUTPUT\n", ZR_DEVNAME(zr)); + + *output = 0; + + return 0; + } + break; + + case VIDIOC_S_OUTPUT: + { + int *output = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_OUTPUT - output=%d\n", + ZR_DEVNAME(zr), *output); + + if (*output != 0) + return -EINVAL; + + return 0; + } + break; + + /* cropping (sub-frame capture) */ + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cropcap = arg; + int type = cropcap->type, res = 0; + + dprintk(3, KERN_ERR "%s: VIDIOC_CROPCAP - type=%d\n", + ZR_DEVNAME(zr), cropcap->type); + + memset(cropcap, 0, sizeof(*cropcap)); + cropcap->type = type; + + down(&zr->resource_lock); + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_CROPCAP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto cropcap_unlock_and_return; + } + + cropcap->bounds.top = cropcap->bounds.left = 0; + cropcap->bounds.width = BUZ_MAX_WIDTH; + cropcap->bounds.height = BUZ_MAX_HEIGHT; + cropcap->defrect.top = cropcap->defrect.left = 0; + cropcap->defrect.width = BUZ_MIN_WIDTH; + cropcap->defrect.height = BUZ_MIN_HEIGHT; + cropcap_unlock_and_return: + up(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + int type = crop->type, res = 0; + + dprintk(3, KERN_ERR "%s: VIDIOC_G_CROP - type=%d\n", + ZR_DEVNAME(zr), crop->type); + + memset(crop, 0, sizeof(*crop)); + crop->type = type; + + down(&zr->resource_lock); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_CROP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto gcrop_unlock_and_return; + } + + crop->c.top = fh->jpg_settings.img_y; + crop->c.left = fh->jpg_settings.img_x; + crop->c.width = fh->jpg_settings.img_width; + crop->c.height = fh->jpg_settings.img_height; + + gcrop_unlock_and_return: + up(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + int res = 0; + + settings = fh->jpg_settings; + + dprintk(3, + KERN_ERR + "%s: VIDIOC_S_CROP - type=%d, x=%d,y=%d,w=%d,h=%d\n", + ZR_DEVNAME(zr), crop->type, crop->c.left, crop->c.top, + crop->c.width, crop->c.height); + + down(&zr->resource_lock); + + if (fh->jpg_buffers.allocated || fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_CROP - cannot change settings while active\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto scrop_unlock_and_return; + } + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_CROP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto scrop_unlock_and_return; + } + + /* move into a form that we understand */ + settings.img_x = crop->c.left; + settings.img_y = crop->c.top; + settings.img_width = crop->c.width; + settings.img_height = crop->c.height; + + /* check validity */ + if ((res = zoran_check_jpg_settings(zr, &settings))) + goto scrop_unlock_and_return; + + /* accept */ + fh->jpg_settings = settings; + + scrop_unlock_and_return: + up(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_G_JPEGCOMP: + { + struct v4l2_jpegcompression *params = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_JPEGCOMP\n", + ZR_DEVNAME(zr)); + + memset(params, 0, sizeof(*params)); + + down(&zr->resource_lock); + + params->quality = fh->jpg_settings.jpg_comp.quality; + params->APPn = fh->jpg_settings.jpg_comp.APPn; + memcpy(params->APP_data, + fh->jpg_settings.jpg_comp.APP_data, + fh->jpg_settings.jpg_comp.APP_len); + params->APP_len = fh->jpg_settings.jpg_comp.APP_len; + memcpy(params->COM_data, + fh->jpg_settings.jpg_comp.COM_data, + fh->jpg_settings.jpg_comp.COM_len); + params->COM_len = fh->jpg_settings.jpg_comp.COM_len; + params->jpeg_markers = + fh->jpg_settings.jpg_comp.jpeg_markers; + + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_S_JPEGCOMP: + { + struct v4l2_jpegcompression *params = arg; + int res = 0; + + settings = fh->jpg_settings; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOC_S_JPEGCOMP - quality=%d, APPN=%d, APP_len=%d, COM_len=%d\n", + ZR_DEVNAME(zr), params->quality, params->APPn, + params->APP_len, params->COM_len); + + settings.jpg_comp = *params; + + down(&zr->resource_lock); + + if (fh->v4l_buffers.active != ZORAN_FREE || + fh->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: VIDIOC_S_JPEGCOMP called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sjpegc_unlock_and_return; + } + + if ((res = zoran_check_jpg_settings(zr, &settings))) + goto sjpegc_unlock_and_return; + if (!fh->jpg_buffers.allocated) + fh->jpg_buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh->jpg_settings); + fh->jpg_settings.jpg_comp = *params = settings.jpg_comp; + sjpegc_unlock_and_return: + up(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_QUERYSTD: /* why is this useful? */ + { + v4l2_std_id *std = arg; + + dprintk(3, + KERN_DEBUG "%s: VIDIOC_QUERY_STD - std=0x%llx\n", + ZR_DEVNAME(zr), (unsigned long long)*std); + + if (*std == V4L2_STD_ALL || *std == V4L2_STD_NTSC || + *std == V4L2_STD_PAL || (*std == V4L2_STD_SECAM && + zr->card.norms == 3)) { + return 0; + } + + return -EINVAL; + } + break; + + case VIDIOC_TRY_FMT: + { + struct v4l2_format *fmt = arg; + int res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_TRY_FMT - type=%d\n", + ZR_DEVNAME(zr), fmt->type); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + down(&zr->resource_lock); + + if (fmt->fmt.win.w.width > BUZ_MAX_WIDTH) + fmt->fmt.win.w.width = BUZ_MAX_WIDTH; + if (fmt->fmt.win.w.width < BUZ_MIN_WIDTH) + fmt->fmt.win.w.width = BUZ_MIN_WIDTH; + if (fmt->fmt.win.w.height > BUZ_MAX_HEIGHT) + fmt->fmt.win.w.height = BUZ_MAX_HEIGHT; + if (fmt->fmt.win.w.height < BUZ_MIN_HEIGHT) + fmt->fmt.win.w.height = BUZ_MIN_HEIGHT; + + up(&zr->resource_lock); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (fmt->fmt.pix.bytesperline > 0) + return -EINVAL; + + down(&zr->resource_lock); + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { + settings = fh->jpg_settings; + + /* we actually need to set 'real' parameters now */ + if ((fmt->fmt.pix.height * 2) > + BUZ_MAX_HEIGHT) + settings.TmpDcm = 1; + else + settings.TmpDcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= + fh->jpg_settings.img_height / 2) + settings.VerDcm = 2; + else + settings.VerDcm = 1; + if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 4) + settings.HorDcm = 4; + else if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 2) + settings.HorDcm = 2; + else + settings.HorDcm = 1; + if (settings.TmpDcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + /* check */ + if ((res = + zoran_check_jpg_settings(zr, + &settings))) + goto tryfmt_unlock_and_return; + + /* tell the user what we actually did */ + fmt->fmt.pix.width = + settings.img_width / settings.HorDcm; + fmt->fmt.pix.height = + settings.img_height * 2 / + (settings.TmpDcm * settings.VerDcm); + if (settings.TmpDcm == 1) + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_TB : + V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : + V4L2_FIELD_BOTTOM); + + fmt->fmt.pix.sizeimage = + zoran_v4l2_calc_bufsize(&settings); + } else if (fmt->type == + V4L2_BUF_TYPE_VIDEO_CAPTURE) { + int i; + + for (i = 0; i < zoran_num_formats; i++) + if (zoran_formats[i].fourcc == + fmt->fmt.pix.pixelformat) + break; + if (i == zoran_num_formats) { + res = -EINVAL; + goto tryfmt_unlock_and_return; + } + + if (fmt->fmt.pix.width > BUZ_MAX_WIDTH) + fmt->fmt.pix.width = BUZ_MAX_WIDTH; + if (fmt->fmt.pix.width < BUZ_MIN_WIDTH) + fmt->fmt.pix.width = BUZ_MIN_WIDTH; + if (fmt->fmt.pix.height > BUZ_MAX_HEIGHT) + fmt->fmt.pix.height = + BUZ_MAX_HEIGHT; + if (fmt->fmt.pix.height < BUZ_MIN_HEIGHT) + fmt->fmt.pix.height = + BUZ_MIN_HEIGHT; + } else { + res = -EINVAL; + goto tryfmt_unlock_and_return; + } + tryfmt_unlock_and_return: + up(&zr->resource_lock); + + return res; + break; + + default: + return -EINVAL; + } + + return 0; + } + break; +#endif + + default: + dprintk(1, KERN_DEBUG "%s: UNKNOWN ioctl cmd: 0x%x\n", + ZR_DEVNAME(zr), cmd); + return -ENOIOCTLCMD; + break; + + } + return 0; +} + + +static int +zoran_ioctl (struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, zoran_do_ioctl); +} + +static unsigned int +zoran_poll (struct file *file, + poll_table *wait) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + wait_queue_head_t *queue = NULL; + int res = 0, frame; + + /* we should check whether buffers are ready to be synced on + * (w/o waits - O_NONBLOCK) here + * if ready for read (sync), return POLLIN|POLLRDNORM, + * if ready for write (sync), return POLLOUT|POLLWRNORM, + * if error, return POLLERR, + * if no buffers queued or so, return POLLNVAL + */ + + down(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (fh->v4l_buffers.active == ZORAN_FREE || + zr->v4l_pend_head == zr->v4l_pend_tail) { + dprintk(1, + "%s: zoran_poll() - no buffers queued\n", + ZR_DEVNAME(zr)); + res = POLLNVAL; + goto poll_unlock_and_return; + } + queue = &zr->v4l_capq; + frame = zr->v4l_pend[zr->v4l_pend_tail & V4L_MASK_FRAME]; + poll_wait(file, queue, wait); + if (fh->v4l_buffers.buffer[frame].state == BUZ_STATE_DONE) + res = POLLIN | POLLRDNORM; + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->jpg_buffers.active == ZORAN_FREE || + zr->jpg_que_head == zr->jpg_que_tail) { + dprintk(1, + "%s: zoran_poll() - no buffers queued\n", + ZR_DEVNAME(zr)); + res = POLLNVAL; + goto poll_unlock_and_return; + } + queue = &zr->jpg_capq; + frame = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + poll_wait(file, queue, wait); + if (fh->jpg_buffers.buffer[frame].state == BUZ_STATE_DONE) { + if (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) + res = POLLIN | POLLRDNORM; + else + res = POLLOUT | POLLWRNORM; + } + break; + + default: + dprintk(1, + "%s: zoran_poll() - internal error, unknown map_mode=%d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = POLLNVAL; + goto poll_unlock_and_return; + } + +poll_unlock_and_return: + up(&zr->resource_lock); + + return res; +} + + +/* + * This maps the buffers to user space. + * + * Depending on the state of fh->map_mode + * the V4L or the MJPEG buffers are mapped + * per buffer or all together + * + * Note that we need to connect to some + * unmap signal event to unmap the de-allocate + * the buffer accordingly (zoran_vm_close()) + */ + +static void +zoran_vm_open (struct vm_area_struct *vma) +{ + struct zoran_mapping *map = vma->vm_private_data; + + map->count++; +} + +static void +zoran_vm_close (struct vm_area_struct *vma) +{ + struct zoran_mapping *map = vma->vm_private_data; + struct file *file = map->file; + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i; + + map->count--; + if (map->count == 0) { + switch (fh->map_mode) { + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + dprintk(3, KERN_INFO "%s: munmap(MJPEG)\n", + ZR_DEVNAME(zr)); + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) { + if (fh->jpg_buffers.buffer[i].map == map) { + fh->jpg_buffers.buffer[i].map = + NULL; + } + } + kfree(map); + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) + if (fh->jpg_buffers.buffer[i].map) + break; + if (i == fh->jpg_buffers.num_buffers) { + down(&zr->resource_lock); + + if (fh->jpg_buffers.active != ZORAN_FREE) { + jpg_qbuf(file, -1, zr->codec_mode); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = + fh->jpg_buffers.active = + ZORAN_FREE; + } + //jpg_fbuffer_free(file); + fh->jpg_buffers.allocated = 0; + fh->jpg_buffers.ready_to_be_freed = 1; + + up(&zr->resource_lock); + } + + break; + + case ZORAN_MAP_MODE_RAW: + + dprintk(3, KERN_INFO "%s: munmap(V4L)\n", + ZR_DEVNAME(zr)); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) { + if (fh->v4l_buffers.buffer[i].map == map) { + /* unqueue/unmap */ + fh->v4l_buffers.buffer[i].map = + NULL; + } + } + kfree(map); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) + if (fh->v4l_buffers.buffer[i].map) + break; + if (i == fh->v4l_buffers.num_buffers) { + down(&zr->resource_lock); + + if (fh->v4l_buffers.active != ZORAN_FREE) { + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = + fh->v4l_buffers.active = + ZORAN_FREE; + } + //v4l_fbuffer_free(file); + fh->v4l_buffers.allocated = 0; + fh->v4l_buffers.ready_to_be_freed = 1; + + up(&zr->resource_lock); + } + + break; + + default: + printk(KERN_ERR + "%s: munmap() - internal error - unknown map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + break; + + } + } +} + +static struct vm_operations_struct zoran_vm_ops = { + .open = zoran_vm_open, + .close = zoran_vm_close, +}; + +static int +zoran_mmap (struct file *file, + struct vm_area_struct *vma) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long size = (vma->vm_end - vma->vm_start); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int i, j; + unsigned long page, start = vma->vm_start, todo, pos, fraglen; + int first, last; + struct zoran_mapping *map; + int res = 0; + + dprintk(3, + KERN_INFO "%s: mmap(%s) of 0x%08lx-0x%08lx (size=%lu)\n", + ZR_DEVNAME(zr), + fh->map_mode == ZORAN_MAP_MODE_RAW ? "V4L" : "MJPEG", + vma->vm_start, vma->vm_end, size); + + if (!(vma->vm_flags & VM_SHARED) || !(vma->vm_flags & VM_READ) || + !(vma->vm_flags & VM_WRITE)) { + dprintk(1, + KERN_ERR + "%s: mmap() - no MAP_SHARED/PROT_{READ,WRITE} given\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + switch (fh->map_mode) { + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + /* lock */ + down(&zr->resource_lock); + + /* Map the MJPEG buffers */ + if (!fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(MJPEG) - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + goto jpg_mmap_unlock_and_return; + } + + first = offset / fh->jpg_buffers.buffer_size; + last = first - 1 + size / fh->jpg_buffers.buffer_size; + if (offset % fh->jpg_buffers.buffer_size != 0 || + size % fh->jpg_buffers.buffer_size != 0 || first < 0 || + last < 0 || first >= fh->jpg_buffers.num_buffers || + last >= fh->jpg_buffers.num_buffers) { + dprintk(1, + KERN_ERR + "%s: mmap(MJPEG) - offset=%lu or size=%lu invalid for bufsize=%d and numbufs=%d\n", + ZR_DEVNAME(zr), offset, size, + fh->jpg_buffers.buffer_size, + fh->jpg_buffers.num_buffers); + res = -EINVAL; + goto jpg_mmap_unlock_and_return; + } + for (i = first; i <= last; i++) { + if (fh->jpg_buffers.buffer[i].map) { + dprintk(1, + KERN_ERR + "%s: mmap(MJPEG) - buffer %d already mapped\n", + ZR_DEVNAME(zr), i); + res = -EBUSY; + goto jpg_mmap_unlock_and_return; + } + } + + /* map these buffers (v4l_buffers[i]) */ + map = kmalloc(sizeof(struct zoran_mapping), GFP_KERNEL); + if (!map) { + res = -ENOMEM; + goto jpg_mmap_unlock_and_return; + } + map->file = file; + map->count = 1; + + vma->vm_ops = &zoran_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + for (i = first; i <= last; i++) { + for (j = 0; + j < fh->jpg_buffers.buffer_size / PAGE_SIZE; + j++) { + fraglen = + (le32_to_cpu(fh->jpg_buffers.buffer[i]. + frag_tab[2 * j + 1]) & ~1) << 1; + todo = size; + if (todo > fraglen) + todo = fraglen; + pos = + le32_to_cpu((unsigned long) fh->jpg_buffers. + buffer[i].frag_tab[2 * j]); + /* should just be pos on i386 */ + page = virt_to_phys(bus_to_virt(pos)) + >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, page, + todo, PAGE_SHARED)) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(V4L) - remap_pfn_range failed\n", + ZR_DEVNAME(zr)); + res = -EAGAIN; + goto jpg_mmap_unlock_and_return; + } + size -= todo; + start += todo; + if (size == 0) + break; + if (le32_to_cpu(fh->jpg_buffers.buffer[i]. + frag_tab[2 * j + 1]) & 1) + break; /* was last fragment */ + } + fh->jpg_buffers.buffer[i].map = map; + if (size == 0) + break; + + } + jpg_mmap_unlock_and_return: + up(&zr->resource_lock); + + break; + + case ZORAN_MAP_MODE_RAW: + + down(&zr->resource_lock); + + /* Map the V4L buffers */ + if (!fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(V4L) - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + goto v4l_mmap_unlock_and_return; + } + + first = offset / fh->v4l_buffers.buffer_size; + last = first - 1 + size / fh->v4l_buffers.buffer_size; + if (offset % fh->v4l_buffers.buffer_size != 0 || + size % fh->v4l_buffers.buffer_size != 0 || first < 0 || + last < 0 || first >= fh->v4l_buffers.num_buffers || + last >= fh->v4l_buffers.buffer_size) { + dprintk(1, + KERN_ERR + "%s: mmap(V4L) - offset=%lu or size=%lu invalid for bufsize=%d and numbufs=%d\n", + ZR_DEVNAME(zr), offset, size, + fh->v4l_buffers.buffer_size, + fh->v4l_buffers.num_buffers); + res = -EINVAL; + goto v4l_mmap_unlock_and_return; + } + for (i = first; i <= last; i++) { + if (fh->v4l_buffers.buffer[i].map) { + dprintk(1, + KERN_ERR + "%s: mmap(V4L) - buffer %d already mapped\n", + ZR_DEVNAME(zr), i); + res = -EBUSY; + goto v4l_mmap_unlock_and_return; + } + } + + /* map these buffers (v4l_buffers[i]) */ + map = kmalloc(sizeof(struct zoran_mapping), GFP_KERNEL); + if (!map) { + res = -ENOMEM; + goto v4l_mmap_unlock_and_return; + } + map->file = file; + map->count = 1; + + vma->vm_ops = &zoran_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + for (i = first; i <= last; i++) { + todo = size; + if (todo > fh->v4l_buffers.buffer_size) + todo = fh->v4l_buffers.buffer_size; + page = fh->v4l_buffers.buffer[i].fbuffer_phys; + if (remap_pfn_range(vma, start, page >> PAGE_SHIFT, + todo, PAGE_SHARED)) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(V4L)i - remap_pfn_range failed\n", + ZR_DEVNAME(zr)); + res = -EAGAIN; + goto v4l_mmap_unlock_and_return; + } + size -= todo; + start += todo; + fh->v4l_buffers.buffer[i].map = map; + if (size == 0) + break; + } + v4l_mmap_unlock_and_return: + up(&zr->resource_lock); + + break; + + default: + dprintk(1, + KERN_ERR + "%s: zoran_mmap() - internal error - unknown map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + break; + } + + return 0; +} + +static struct file_operations zoran_fops = { + .owner = THIS_MODULE, + .open = zoran_open, + .release = zoran_close, + .ioctl = zoran_ioctl, + .llseek = no_llseek, + .read = zoran_read, + .write = zoran_write, + .mmap = zoran_mmap, + .poll = zoran_poll, +}; + +struct video_device zoran_template __devinitdata = { + .name = ZORAN_NAME, + .type = ZORAN_VID_TYPE, +#ifdef HAVE_V4L2 + .type2 = ZORAN_V4L2_VID_FLAGS, +#endif + .hardware = ZORAN_HARDWARE, + .fops = &zoran_fops, + .release = &zoran_vdev_release, + .minor = -1 +}; + diff --git a/drivers/media/video/zoran_procfs.c b/drivers/media/video/zoran_procfs.c new file mode 100644 index 000000000000..f0d9b13c3c6c --- /dev/null +++ b/drivers/media/video/zoran_procfs.c @@ -0,0 +1,233 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles the procFS entries (/proc/ZORAN[%d]) + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/vmalloc.h> + +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/sem.h> +#include <linux/seq_file.h> + +#include <linux/ctype.h> +#include <asm/io.h> + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_procfs.h" + +extern int *zr_debug; + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +#ifdef CONFIG_PROC_FS +struct procfs_params_zr36067 { + char *name; + short reg; + u32 mask; + short bit; +}; + +static const struct procfs_params_zr36067 zr67[] = { + {"HSPol", 0x000, 1, 30}, + {"HStart", 0x000, 0x3ff, 10}, + {"HEnd", 0x000, 0x3ff, 0}, + + {"VSPol", 0x004, 1, 30}, + {"VStart", 0x004, 0x3ff, 10}, + {"VEnd", 0x004, 0x3ff, 0}, + + {"ExtFl", 0x008, 1, 26}, + {"TopField", 0x008, 1, 25}, + {"VCLKPol", 0x008, 1, 24}, + {"DupFld", 0x008, 1, 20}, + {"LittleEndian", 0x008, 1, 0}, + + {"HsyncStart", 0x10c, 0xffff, 16}, + {"LineTot", 0x10c, 0xffff, 0}, + + {"NAX", 0x110, 0xffff, 16}, + {"PAX", 0x110, 0xffff, 0}, + + {"NAY", 0x114, 0xffff, 16}, + {"PAY", 0x114, 0xffff, 0}, + + /* {"",,,}, */ + + {NULL, 0, 0, 0}, +}; + +static void +setparam (struct zoran *zr, + char *name, + char *sval) +{ + int i = 0, reg0, reg, val; + + while (zr67[i].name != NULL) { + if (!strncmp(name, zr67[i].name, strlen(zr67[i].name))) { + reg = reg0 = btread(zr67[i].reg); + reg &= ~(zr67[i].mask << zr67[i].bit); + if (!isdigit(sval[0])) + break; + val = simple_strtoul(sval, NULL, 0); + if ((val & ~zr67[i].mask)) + break; + reg |= (val & zr67[i].mask) << zr67[i].bit; + dprintk(4, + KERN_INFO + "%s: setparam: setting ZR36067 register 0x%03x: 0x%08x=>0x%08x %s=%d\n", + ZR_DEVNAME(zr), zr67[i].reg, reg0, reg, + zr67[i].name, val); + btwrite(reg, zr67[i].reg); + break; + } + i++; + } +} + +static int zoran_show(struct seq_file *p, void *v) +{ + struct zoran *zr = p->private; + int i; + + seq_printf(p, "ZR36067 registers:\n"); + for (i = 0; i < 0x130; i += 16) + seq_printf(p, "%03X %08X %08X %08X %08X \n", i, + btread(i), btread(i+4), btread(i+8), btread(i+12)); + return 0; +} + +static int zoran_open(struct inode *inode, struct file *file) +{ + struct zoran *data = PDE(inode)->data; + return single_open(file, zoran_show, data); +} + +static ssize_t zoran_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct zoran *zr = PDE(file->f_dentry->d_inode)->data; + char *string, *sp; + char *line, *ldelim, *varname, *svar, *tdelim; + + if (count > 32768) /* Stupidity filter */ + return -EINVAL; + + string = sp = vmalloc(count + 1); + if (!string) { + dprintk(1, + KERN_ERR + "%s: write_proc: can not allocate memory\n", + ZR_DEVNAME(zr)); + return -ENOMEM; + } + if (copy_from_user(string, buffer, count)) { + vfree (string); + return -EFAULT; + } + string[count] = 0; + dprintk(4, KERN_INFO "%s: write_proc: name=%s count=%zu zr=%p\n", + ZR_DEVNAME(zr), file->f_dentry->d_name.name, count, zr); + ldelim = " \t\n"; + tdelim = "="; + line = strpbrk(sp, ldelim); + while (line) { + *line = 0; + svar = strpbrk(sp, tdelim); + if (svar) { + *svar = 0; + varname = sp; + svar++; + setparam(zr, varname, svar); + } + sp = line + 1; + line = strpbrk(sp, ldelim); + } + vfree(string); + + return count; +} + +static struct file_operations zoran_operations = { + .open = zoran_open, + .read = seq_read, + .write = zoran_write, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +int +zoran_proc_init (struct zoran *zr) +{ +#ifdef CONFIG_PROC_FS + char name[8]; + + snprintf(name, 7, "zoran%d", zr->id); + if ((zr->zoran_proc = create_proc_entry(name, 0, NULL))) { + zr->zoran_proc->data = zr; + zr->zoran_proc->owner = THIS_MODULE; + zr->zoran_proc->proc_fops = &zoran_operations; + dprintk(2, + KERN_INFO + "%s: procfs entry /proc/%s allocated. data=%p\n", + ZR_DEVNAME(zr), name, zr->zoran_proc->data); + } else { + dprintk(1, KERN_ERR "%s: Unable to initialise /proc/%s\n", + ZR_DEVNAME(zr), name); + return 1; + } +#endif + return 0; +} + +void +zoran_proc_cleanup (struct zoran *zr) +{ +#ifdef CONFIG_PROC_FS + char name[8]; + + snprintf(name, 7, "zoran%d", zr->id); + if (zr->zoran_proc) + remove_proc_entry(name, NULL); + zr->zoran_proc = NULL; +#endif +} diff --git a/drivers/media/video/zoran_procfs.h b/drivers/media/video/zoran_procfs.h new file mode 100644 index 000000000000..8904fc959555 --- /dev/null +++ b/drivers/media/video/zoran_procfs.h @@ -0,0 +1,36 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ZORAN_PROCFS_H__ +#define __ZORAN_PROCFS_H__ + +extern int zoran_proc_init(struct zoran *zr); +extern void zoran_proc_cleanup(struct zoran *zr); + +#endif /* __ZORAN_PROCFS_H__ */ diff --git a/drivers/media/video/zr36016.c b/drivers/media/video/zr36016.c new file mode 100644 index 000000000000..d4740a89cea1 --- /dev/null +++ b/drivers/media/video/zr36016.c @@ -0,0 +1,532 @@ +/* + * Zoran ZR36016 basic configuration functions + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36016.c,v 1.1.2.14 2003/08/20 19:46:55 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ------------------------------------------------------------------------ + */ + +#define ZR016_VERSION "v0.7" + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/types.h> +#include <linux/wait.h> + +/* includes for structures and defines regarding video + #include<linux/videodev.h> */ + +/* I/O commands, error codes */ +#include<asm/io.h> +//#include<errno.h> + +/* v4l API */ +#include<linux/videodev.h> + +/* headerfile of this module */ +#include"zr36016.h" + +/* codec io API */ +#include"videocodec.h" + +/* it doesn't make sense to have more than 20 or so, + just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36016_codecs = 0; + +/* debugging is available via module parameter */ + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ========================================================================= + Local hardware I/O functions: + + read/write via codec layer (registers are located in the master device) + ========================================================================= */ + +/* read and write functions */ +static u8 +zr36016_read (struct zr36016 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = + (ptr->codec->master_data-> + readreg(ptr->codec, reg)) & 0xFF; + else + dprintk(1, + KERN_ERR "%s: invalid I/O setup, nothing read!\n", + ptr->name); + + dprintk(4, "%s: reading from 0x%04x: %02x\n", ptr->name, reg, + value); + + return value; +} + +static void +zr36016_write (struct zr36016 *ptr, + u16 reg, + u8 value) +{ + dprintk(4, "%s: writing 0x%02x to 0x%04x\n", ptr->name, value, + reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) { + ptr->codec->master_data->writereg(ptr->codec, reg, value); + } else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* indirect read and write functions */ +/* the 016 supports auto-addr-increment, but + * writing it all time cost not much and is safer... */ +static u8 +zr36016_readi (struct zr36016 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if ((ptr->codec->master_data->writereg) && + (ptr->codec->master_data->readreg)) { + ptr->codec->master_data->writereg(ptr->codec, ZR016_IADDR, reg & 0x0F); // ADDR + value = (ptr->codec->master_data->readreg(ptr->codec, ZR016_IDATA)) & 0xFF; // DATA + } else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing read (i)!\n", + ptr->name); + + dprintk(4, "%s: reading indirect from 0x%04x: %02x\n", ptr->name, + reg, value); + return value; +} + +static void +zr36016_writei (struct zr36016 *ptr, + u16 reg, + u8 value) +{ + dprintk(4, "%s: writing indirect 0x%02x to 0x%04x\n", ptr->name, + value, reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) { + ptr->codec->master_data->writereg(ptr->codec, ZR016_IADDR, reg & 0x0F); // ADDR + ptr->codec->master_data->writereg(ptr->codec, ZR016_IDATA, value & 0x0FF); // DATA + } else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written (i)!\n", + ptr->name); +} + +/* ========================================================================= + Local helper function: + + version read + ========================================================================= */ + +/* version kept in datastructure */ +static u8 +zr36016_read_version (struct zr36016 *ptr) +{ + ptr->version = zr36016_read(ptr, 0) >> 4; + return ptr->version; +} + +/* ========================================================================= + Local helper function: + + basic test of "connectivity", writes/reads to/from PAX-Lo register + ========================================================================= */ + +static int +zr36016_basic_test (struct zr36016 *ptr) +{ + if (debug) { + int i; + zr36016_writei(ptr, ZR016I_PAX_LO, 0x55); + dprintk(1, KERN_INFO "%s: registers: ", ptr->name); + for (i = 0; i <= 0x0b; i++) + dprintk(1, "%02x ", zr36016_readi(ptr, i)); + dprintk(1, "\n"); + } + // for testing just write 0, then the default value to a register and read + // it back in both cases + zr36016_writei(ptr, ZR016I_PAX_LO, 0x00); + if (zr36016_readi(ptr, ZR016I_PAX_LO) != 0x0) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to vfe processor!\n", + ptr->name); + return -ENXIO; + } + zr36016_writei(ptr, ZR016I_PAX_LO, 0x0d0); + if (zr36016_readi(ptr, ZR016I_PAX_LO) != 0x0d0) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to vfe processor!\n", + ptr->name); + return -ENXIO; + } + // we allow version numbers from 0-3, should be enough, though + zr36016_read_version(ptr); + if (ptr->version & 0x0c) { + dprintk(1, + KERN_ERR + "%s: attach failed, suspicious version %d found...\n", + ptr->name, ptr->version); + return -ENXIO; + } + + return 0; /* looks good! */ +} + +/* ========================================================================= + Local helper function: + + simple loop for pushing the init datasets - NO USE -- + ========================================================================= */ + +#if 0 +static int zr36016_pushit (struct zr36016 *ptr, + u16 startreg, + u16 len, + const char *data) +{ + int i=0; + + dprintk(4, "%s: write data block to 0x%04x (len=%d)\n", + ptr->name, startreg,len); + while (i<len) { + zr36016_writei(ptr, startreg++, data[i++]); + } + + return i; +} +#endif + +/* ========================================================================= + Basic datasets & init: + + //TODO// + ========================================================================= */ + +// needed offset values PAL NTSC SECAM +static const int zr016_xoff[] = { 20, 20, 20 }; +static const int zr016_yoff[] = { 8, 9, 7 }; + +static void +zr36016_init (struct zr36016 *ptr) +{ + // stop any processing + zr36016_write(ptr, ZR016_GOSTOP, 0); + + // mode setup (yuv422 in and out, compression/expansuon due to mode) + zr36016_write(ptr, ZR016_MODE, + ZR016_YUV422 | ZR016_YUV422_YUV422 | + (ptr->mode == CODEC_DO_COMPRESSION ? + ZR016_COMPRESSION : ZR016_EXPANSION)); + + // misc setup + zr36016_writei(ptr, ZR016I_SETUP1, + (ptr->xdec ? (ZR016_HRFL | ZR016_HORZ) : 0) | + (ptr->ydec ? ZR016_VERT : 0) | ZR016_CNTI); + zr36016_writei(ptr, ZR016I_SETUP2, ZR016_CCIR); + + // Window setup + // (no extra offset for now, norm defines offset, default width height) + zr36016_writei(ptr, ZR016I_PAX_HI, ptr->width >> 8); + zr36016_writei(ptr, ZR016I_PAX_LO, ptr->width & 0xFF); + zr36016_writei(ptr, ZR016I_PAY_HI, ptr->height >> 8); + zr36016_writei(ptr, ZR016I_PAY_LO, ptr->height & 0xFF); + zr36016_writei(ptr, ZR016I_NAX_HI, ptr->xoff >> 8); + zr36016_writei(ptr, ZR016I_NAX_LO, ptr->xoff & 0xFF); + zr36016_writei(ptr, ZR016I_NAY_HI, ptr->yoff >> 8); + zr36016_writei(ptr, ZR016I_NAY_LO, ptr->yoff & 0xFF); + + /* shall we continue now, please? */ + zr36016_write(ptr, ZR016_GOSTOP, 1); +} + +/* ========================================================================= + CODEC API FUNCTIONS + + this functions are accessed by the master via the API structure + ========================================================================= */ + +/* set compression/expansion mode and launches codec - + this should be the last call from the master before starting processing */ +static int +zr36016_set_mode (struct videocodec *codec, + int mode) +{ + struct zr36016 *ptr = (struct zr36016 *) codec->data; + + dprintk(2, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36016_init(ptr); + + return 0; +} + +/* set picture size */ +static int +zr36016_set_video (struct videocodec *codec, + struct tvnorm *norm, + struct vfe_settings *cap, + struct vfe_polarity *pol) +{ + struct zr36016 *ptr = (struct zr36016 *) codec->data; + + dprintk(2, "%s: set_video %d.%d, %d/%d-%dx%d (0x%x) call\n", + ptr->name, norm->HStart, norm->VStart, + cap->x, cap->y, cap->width, cap->height, + cap->decimation); + + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y for now ... */ + ptr->width = cap->width; + ptr->height = cap->height; + /* (Ronald) This is ugly. zoran_device.c, line 387 + * already mentions what happens if HStart is even + * (blue faces, etc., cr/cb inversed). There's probably + * some good reason why HStart is 0 instead of 1, so I'm + * leaving it to this for now, but really... This can be + * done a lot simpler */ + ptr->xoff = (norm->HStart ? norm->HStart : 1) + cap->x; + /* Something to note here (I don't understand it), setting + * VStart too high will cause the codec to 'not work'. I + * really don't get it. values of 16 (VStart) already break + * it here. Just '0' seems to work. More testing needed! */ + ptr->yoff = norm->VStart + cap->y; + /* (Ronald) dzjeeh, can't this thing do hor_decimation = 4? */ + ptr->xdec = ((cap->decimation & 0xff) == 1) ? 0 : 1; + ptr->ydec = (((cap->decimation >> 8) & 0xff) == 1) ? 0 : 1; + + return 0; +} + +/* additional control functions */ +static int +zr36016_control (struct videocodec *codec, + int type, + int size, + void *data) +{ + struct zr36016 *ptr = (struct zr36016 *) codec->data; + int *ival = (int *) data; + + dprintk(2, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status - we don't know it ... */ + if (size != sizeof(int)) + return -EFAULT; + *ival = 0; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = 0; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != 0) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + Exit and unregister function: + + Deinitializes Zoran's JPEG processor + ========================================================================= */ + +static int +zr36016_unset (struct videocodec *codec) +{ + struct zr36016 *ptr = codec->data; + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + dprintk(1, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36016_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + Setup and registry function: + + Initializes Zoran's JPEG processor + + Also sets pixel size, average code size, mode (compr./decompr.) + (the given size is determined by the processor with the video interface) + ========================================================================= */ + +static int +zr36016_setup (struct videocodec *codec) +{ + struct zr36016 *ptr; + int res; + + dprintk(2, "zr36016: initializing VFE subsystem #%d.\n", + zr36016_codecs); + + if (zr36016_codecs == MAX_CODECS) { + dprintk(1, + KERN_ERR "zr36016: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + codec->data = ptr = kmalloc(sizeof(struct zr36016), GFP_KERNEL); + if (NULL == ptr) { + dprintk(1, KERN_ERR "zr36016: Can't get enough memory!\n"); + return -ENOMEM; + } + memset(ptr, 0, sizeof(struct zr36016)); + + snprintf(ptr->name, sizeof(ptr->name), "zr36016[%d]", + zr36016_codecs); + ptr->num = zr36016_codecs++; + ptr->codec = codec; + + //testing + res = zr36016_basic_test(ptr); + if (res < 0) { + zr36016_unset(codec); + return res; + } + //final setup + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 768; + ptr->height = 288; + ptr->xdec = 1; + ptr->ydec = 0; + zr36016_init(ptr); + + dprintk(1, KERN_INFO "%s: codec v%d attached and running\n", + ptr->name, ptr->version); + + return 0; +} + +static const struct videocodec zr36016_codec = { + .owner = THIS_MODULE, + .name = "zr36016", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_HARDWARE | CODEC_FLAG_VFE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER, + .type = CODEC_TYPE_ZR36016, + .setup = zr36016_setup, // functionality + .unset = zr36016_unset, + .set_mode = zr36016_set_mode, + .set_video = zr36016_set_video, + .control = zr36016_control, + // others are not used +}; + +/* ========================================================================= + HOOK IN DRIVER AS KERNEL MODULE + ========================================================================= */ + +static int __init +zr36016_init_module (void) +{ + //dprintk(1, "ZR36016 driver %s\n",ZR016_VERSION); + zr36016_codecs = 0; + return videocodec_register(&zr36016_codec); +} + +static void __exit +zr36016_cleanup_module (void) +{ + if (zr36016_codecs) { + dprintk(1, + "zr36016: something's wrong - %d codecs left somehow.\n", + zr36016_codecs); + } + videocodec_unregister(&zr36016_codec); +} + +module_init(zr36016_init_module); +module_exit(zr36016_cleanup_module); + +MODULE_AUTHOR("Wolfgang Scherr <scherr@net4you.at>"); +MODULE_DESCRIPTION("Driver module for ZR36016 video frontends " + ZR016_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/zr36016.h b/drivers/media/video/zr36016.h new file mode 100644 index 000000000000..8c79229f69d1 --- /dev/null +++ b/drivers/media/video/zr36016.h @@ -0,0 +1,111 @@ +/* + * Zoran ZR36016 basic configuration functions - header file + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36016.h,v 1.1.2.3 2003/01/14 21:18:07 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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. + * + * ------------------------------------------------------------------------ + */ + +#ifndef ZR36016_H +#define ZR36016_H + +/* data stored for each zoran jpeg codec chip */ +struct zr36016 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // coder status + __u8 version; + // actual coder setup + int mode; + + __u16 xoff; + __u16 yoff; + __u16 width; + __u16 height; + __u16 xdec; + __u16 ydec; +}; + +/* direct register addresses */ +#define ZR016_GOSTOP 0x00 +#define ZR016_MODE 0x01 +#define ZR016_IADDR 0x02 +#define ZR016_IDATA 0x03 + +/* indirect register addresses */ +#define ZR016I_SETUP1 0x00 +#define ZR016I_SETUP2 0x01 +#define ZR016I_NAX_LO 0x02 +#define ZR016I_NAX_HI 0x03 +#define ZR016I_PAX_LO 0x04 +#define ZR016I_PAX_HI 0x05 +#define ZR016I_NAY_LO 0x06 +#define ZR016I_NAY_HI 0x07 +#define ZR016I_PAY_LO 0x08 +#define ZR016I_PAY_HI 0x09 +#define ZR016I_NOL_LO 0x0a +#define ZR016I_NOL_HI 0x0b + +/* possible values for mode register */ +#define ZR016_RGB444_YUV444 0x00 +#define ZR016_RGB444_YUV422 0x01 +#define ZR016_RGB444_YUV411 0x02 +#define ZR016_RGB444_Y400 0x03 +#define ZR016_RGB444_RGB444 0x04 +#define ZR016_YUV444_YUV444 0x08 +#define ZR016_YUV444_YUV422 0x09 +#define ZR016_YUV444_YUV411 0x0a +#define ZR016_YUV444_Y400 0x0b +#define ZR016_YUV444_RGB444 0x0c +#define ZR016_YUV422_YUV422 0x11 +#define ZR016_YUV422_YUV411 0x12 +#define ZR016_YUV422_Y400 0x13 +#define ZR016_YUV411_YUV411 0x16 +#define ZR016_YUV411_Y400 0x17 +#define ZR016_4444_4444 0x19 +#define ZR016_100_100 0x1b + +#define ZR016_RGB444 0x00 +#define ZR016_YUV444 0x20 +#define ZR016_YUV422 0x40 + +#define ZR016_COMPRESSION 0x80 +#define ZR016_EXPANSION 0x80 + +/* possible values for setup 1 register */ +#define ZR016_CKRT 0x80 +#define ZR016_VERT 0x40 +#define ZR016_HORZ 0x20 +#define ZR016_HRFL 0x10 +#define ZR016_DSFL 0x08 +#define ZR016_SBFL 0x04 +#define ZR016_RSTR 0x02 +#define ZR016_CNTI 0x01 + +/* possible values for setup 2 register */ +#define ZR016_SYEN 0x40 +#define ZR016_CCIR 0x04 +#define ZR016_SIGN 0x02 +#define ZR016_YMCS 0x01 + +#endif /*fndef ZR36016_H */ diff --git a/drivers/media/video/zr36050.c b/drivers/media/video/zr36050.c new file mode 100644 index 000000000000..13b1e7b6fd6e --- /dev/null +++ b/drivers/media/video/zr36050.c @@ -0,0 +1,907 @@ +/* + * Zoran ZR36050 basic configuration functions + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36050.c,v 1.1.2.11 2003/08/03 14:54:53 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ------------------------------------------------------------------------ + */ + +#define ZR050_VERSION "v0.7.1" + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/types.h> +#include <linux/wait.h> + +/* includes for structures and defines regarding video + #include<linux/videodev.h> */ + +/* I/O commands, error codes */ +#include<asm/io.h> +//#include<errno.h> + +/* headerfile of this module */ +#include"zr36050.h" + +/* codec io API */ +#include"videocodec.h" + +/* it doesn't make sense to have more than 20 or so, + just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36050_codecs = 0; + +/* debugging is available via module parameter */ + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ========================================================================= + Local hardware I/O functions: + + read/write via codec layer (registers are located in the master device) + ========================================================================= */ + +/* read and write functions */ +static u8 +zr36050_read (struct zr36050 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, + reg)) & 0xFF; + else + dprintk(1, + KERN_ERR "%s: invalid I/O setup, nothing read!\n", + ptr->name); + + dprintk(4, "%s: reading from 0x%04x: %02x\n", ptr->name, reg, + value); + + return value; +} + +static void +zr36050_write (struct zr36050 *ptr, + u16 reg, + u8 value) +{ + dprintk(4, "%s: writing 0x%02x to 0x%04x\n", ptr->name, value, + reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* ========================================================================= + Local helper function: + + status read + ========================================================================= */ + +/* status is kept in datastructure */ +static u8 +zr36050_read_status1 (struct zr36050 *ptr) +{ + ptr->status1 = zr36050_read(ptr, ZR050_STATUS_1); + + zr36050_read(ptr, 0); + return ptr->status1; +} + +/* ========================================================================= + Local helper function: + + scale factor read + ========================================================================= */ + +/* scale factor is kept in datastructure */ +static u16 +zr36050_read_scalefactor (struct zr36050 *ptr) +{ + ptr->scalefact = (zr36050_read(ptr, ZR050_SF_HI) << 8) | + (zr36050_read(ptr, ZR050_SF_LO) & 0xFF); + + /* leave 0 selected for an eventually GO from master */ + zr36050_read(ptr, 0); + return ptr->scalefact; +} + +/* ========================================================================= + Local helper function: + + wait if codec is ready to proceed (end of processing) or time is over + ========================================================================= */ + +static void +zr36050_wait_end (struct zr36050 *ptr) +{ + int i = 0; + + while (!(zr36050_read_status1(ptr) & 0x4)) { + udelay(1); + if (i++ > 200000) { // 200ms, there is for shure something wrong!!! + dprintk(1, + "%s: timout at wait_end (last status: 0x%02x)\n", + ptr->name, ptr->status1); + break; + } + } +} + +/* ========================================================================= + Local helper function: + + basic test of "connectivity", writes/reads to/from memory the SOF marker + ========================================================================= */ + +static int +zr36050_basic_test (struct zr36050 *ptr) +{ + zr36050_write(ptr, ZR050_SOF_IDX, 0x00); + zr36050_write(ptr, ZR050_SOF_IDX + 1, 0x00); + if ((zr36050_read(ptr, ZR050_SOF_IDX) | + zr36050_read(ptr, ZR050_SOF_IDX + 1)) != 0x0000) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + zr36050_write(ptr, ZR050_SOF_IDX, 0xff); + zr36050_write(ptr, ZR050_SOF_IDX + 1, 0xc0); + if (((zr36050_read(ptr, ZR050_SOF_IDX) << 8) | + zr36050_read(ptr, ZR050_SOF_IDX + 1)) != 0xffc0) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + + zr36050_wait_end(ptr); + if ((ptr->status1 & 0x4) == 0) { + dprintk(1, + KERN_ERR + "%s: attach failed, jpeg processor failed (end flag)!\n", + ptr->name); + return -EBUSY; + } + + return 0; /* looks good! */ +} + +/* ========================================================================= + Local helper function: + + simple loop for pushing the init datasets + ========================================================================= */ + +static int +zr36050_pushit (struct zr36050 *ptr, + u16 startreg, + u16 len, + const char *data) +{ + int i = 0; + + dprintk(4, "%s: write data block to 0x%04x (len=%d)\n", ptr->name, + startreg, len); + while (i < len) { + zr36050_write(ptr, startreg++, data[i++]); + } + + return i; +} + +/* ========================================================================= + Basic datasets: + + jpeg baseline setup data (you find it on lots places in internet, or just + extract it from any regular .jpg image...) + + Could be variable, but until it's not needed it they are just fixed to save + memory. Otherwise expand zr36050 structure with arrays, push the values to + it and initalize from there, as e.g. the linux zr36057/60 driver does it. + ========================================================================= */ + +static const char zr36050_dqt[0x86] = { + 0xff, 0xdb, //Marker: DQT + 0x00, 0x84, //Length: 2*65+2 + 0x00, //Pq,Tq first table + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0x01, //Pq,Tq second table + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const char zr36050_dht[0x1a4] = { + 0xff, 0xc4, //Marker: DHT + 0x01, 0xa2, //Length: 2*AC, 2*DC + 0x00, //DC first table + 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, + 0x01, //DC second table + 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, + 0x10, //AC first table + 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, + 0x11, //AC second table + 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 +}; + +/* jpeg baseline setup, this is just fixed in this driver (YUV pictures) */ +#define NO_OF_COMPONENTS 0x3 //Y,U,V +#define BASELINE_PRECISION 0x8 //MCU size (?) +static const char zr36050_tq[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's QT +static const char zr36050_td[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's DC +static const char zr36050_ta[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's AC + +/* horizontal 422 decimation setup (maybe we support 411 or so later, too) */ +static const char zr36050_decimation_h[8] = { 2, 1, 1, 0, 0, 0, 0, 0 }; +static const char zr36050_decimation_v[8] = { 1, 1, 1, 0, 0, 0, 0, 0 }; + +/* ========================================================================= + Local helper functions: + + calculation and setup of parameter-dependent JPEG baseline segments + (needed for compression only) + ========================================================================= */ + +/* ------------------------------------------------------------------------- */ + +/* SOF (start of frame) segment depends on width, height and sampling ratio + of each color component */ + +static int +zr36050_set_sof (struct zr36050 *ptr) +{ + char sof_data[34]; // max. size of register set + int i; + + dprintk(3, "%s: write SOF (%dx%d, %d components)\n", ptr->name, + ptr->width, ptr->height, NO_OF_COMPONENTS); + sof_data[0] = 0xff; + sof_data[1] = 0xc0; + sof_data[2] = 0x00; + sof_data[3] = (3 * NO_OF_COMPONENTS) + 8; + sof_data[4] = BASELINE_PRECISION; // only '8' possible with zr36050 + sof_data[5] = (ptr->height) >> 8; + sof_data[6] = (ptr->height) & 0xff; + sof_data[7] = (ptr->width) >> 8; + sof_data[8] = (ptr->width) & 0xff; + sof_data[9] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sof_data[10 + (i * 3)] = i; // index identifier + sof_data[11 + (i * 3)] = (ptr->h_samp_ratio[i] << 4) | (ptr->v_samp_ratio[i]); // sampling ratios + sof_data[12 + (i * 3)] = zr36050_tq[i]; // Q table selection + } + return zr36050_pushit(ptr, ZR050_SOF_IDX, + (3 * NO_OF_COMPONENTS) + 10, sof_data); +} + +/* ------------------------------------------------------------------------- */ + +/* SOS (start of scan) segment depends on the used scan components + of each color component */ + +static int +zr36050_set_sos (struct zr36050 *ptr) +{ + char sos_data[16]; // max. size of register set + int i; + + dprintk(3, "%s: write SOS\n", ptr->name); + sos_data[0] = 0xff; + sos_data[1] = 0xda; + sos_data[2] = 0x00; + sos_data[3] = 2 + 1 + (2 * NO_OF_COMPONENTS) + 3; + sos_data[4] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sos_data[5 + (i * 2)] = i; // index + sos_data[6 + (i * 2)] = (zr36050_td[i] << 4) | zr36050_ta[i]; // AC/DC tbl.sel. + } + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 2] = 00; // scan start + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 3] = 0x3F; + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 4] = 00; + return zr36050_pushit(ptr, ZR050_SOS1_IDX, + 4 + 1 + (2 * NO_OF_COMPONENTS) + 3, + sos_data); +} + +/* ------------------------------------------------------------------------- */ + +/* DRI (define restart interval) */ + +static int +zr36050_set_dri (struct zr36050 *ptr) +{ + char dri_data[6]; // max. size of register set + + dprintk(3, "%s: write DRI\n", ptr->name); + dri_data[0] = 0xff; + dri_data[1] = 0xdd; + dri_data[2] = 0x00; + dri_data[3] = 0x04; + dri_data[4] = ptr->dri >> 8; + dri_data[5] = ptr->dri & 0xff; + return zr36050_pushit(ptr, ZR050_DRI_IDX, 6, dri_data); +} + +/* ========================================================================= + Setup function: + + Setup compression/decompression of Zoran's JPEG processor + ( see also zoran 36050 manual ) + + ... sorry for the spaghetti code ... + ========================================================================= */ +static void +zr36050_init (struct zr36050 *ptr) +{ + int sum = 0; + long bitcnt, tmp; + + if (ptr->mode == CODEC_DO_COMPRESSION) { + dprintk(2, "%s: COMPRESSION SETUP\n", ptr->name); + + /* 050 communicates with 057 in master mode */ + zr36050_write(ptr, ZR050_HARDWARE, ZR050_HW_MSTR); + + /* encoding table preload for compression */ + zr36050_write(ptr, ZR050_MODE, + ZR050_MO_COMP | ZR050_MO_TLM); + zr36050_write(ptr, ZR050_OPTIONS, 0); + + /* disable all IRQs */ + zr36050_write(ptr, ZR050_INT_REQ_0, 0); + zr36050_write(ptr, ZR050_INT_REQ_1, 3); // low 2 bits always 1 + + /* volume control settings */ + /*zr36050_write(ptr, ZR050_MBCV, ptr->max_block_vol);*/ + zr36050_write(ptr, ZR050_SF_HI, ptr->scalefact >> 8); + zr36050_write(ptr, ZR050_SF_LO, ptr->scalefact & 0xff); + + zr36050_write(ptr, ZR050_AF_HI, 0xff); + zr36050_write(ptr, ZR050_AF_M, 0xff); + zr36050_write(ptr, ZR050_AF_LO, 0xff); + + /* setup the variable jpeg tables */ + sum += zr36050_set_sof(ptr); + sum += zr36050_set_sos(ptr); + sum += zr36050_set_dri(ptr); + + /* setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) */ + dprintk(3, "%s: write DQT, DHT, APP\n", ptr->name); + sum += zr36050_pushit(ptr, ZR050_DQT_IDX, + sizeof(zr36050_dqt), zr36050_dqt); + sum += zr36050_pushit(ptr, ZR050_DHT_IDX, + sizeof(zr36050_dht), zr36050_dht); + zr36050_write(ptr, ZR050_APP_IDX, 0xff); + zr36050_write(ptr, ZR050_APP_IDX + 1, 0xe0 + ptr->app.appn); + zr36050_write(ptr, ZR050_APP_IDX + 2, 0x00); + zr36050_write(ptr, ZR050_APP_IDX + 3, ptr->app.len + 2); + sum += zr36050_pushit(ptr, ZR050_APP_IDX + 4, 60, + ptr->app.data) + 4; + zr36050_write(ptr, ZR050_COM_IDX, 0xff); + zr36050_write(ptr, ZR050_COM_IDX + 1, 0xfe); + zr36050_write(ptr, ZR050_COM_IDX + 2, 0x00); + zr36050_write(ptr, ZR050_COM_IDX + 3, ptr->com.len + 2); + sum += zr36050_pushit(ptr, ZR050_COM_IDX + 4, 60, + ptr->com.data) + 4; + + /* do the internal huffman table preload */ + zr36050_write(ptr, ZR050_MARKERS_EN, ZR050_ME_DHTI); + + zr36050_write(ptr, ZR050_GO, 1); // launch codec + zr36050_wait_end(ptr); + dprintk(2, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status1); + + if ((ptr->status1 & 0x4) == 0) { + dprintk(1, KERN_ERR "%s: init aborted!\n", + ptr->name); + return; // something is wrong, its timed out!!!! + } + + /* setup misc. data for compression (target code sizes) */ + + /* size of compressed code to reach without header data */ + sum = ptr->real_code_vol - sum; + bitcnt = sum << 3; /* need the size in bits */ + + tmp = bitcnt >> 16; + dprintk(3, + "%s: code: csize=%d, tot=%d, bit=%ld, highbits=%ld\n", + ptr->name, sum, ptr->real_code_vol, bitcnt, tmp); + zr36050_write(ptr, ZR050_TCV_NET_HI, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_NET_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36050_write(ptr, ZR050_TCV_NET_ML, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_NET_LO, tmp & 0xff); + + bitcnt -= bitcnt >> 7; // bits without stuffing + bitcnt -= ((bitcnt * 5) >> 6); // bits without eob + + tmp = bitcnt >> 16; + dprintk(3, "%s: code: nettobit=%ld, highnettobits=%ld\n", + ptr->name, bitcnt, tmp); + zr36050_write(ptr, ZR050_TCV_DATA_HI, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_DATA_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36050_write(ptr, ZR050_TCV_DATA_ML, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_DATA_LO, tmp & 0xff); + + /* compression setup with or without bitrate control */ + zr36050_write(ptr, ZR050_MODE, + ZR050_MO_COMP | ZR050_MO_PASS2 | + (ptr->bitrate_ctrl ? ZR050_MO_BRC : 0)); + + /* this headers seem to deliver "valid AVI" jpeg frames */ + zr36050_write(ptr, ZR050_MARKERS_EN, + ZR050_ME_DQT | ZR050_ME_DHT | + ((ptr->app.len > 0) ? ZR050_ME_APP : 0) | + ((ptr->com.len > 0) ? ZR050_ME_COM : 0)); + } else { + dprintk(2, "%s: EXPANSION SETUP\n", ptr->name); + + /* 050 communicates with 055 in master mode */ + zr36050_write(ptr, ZR050_HARDWARE, + ZR050_HW_MSTR | ZR050_HW_CFIS_2_CLK); + + /* encoding table preload */ + zr36050_write(ptr, ZR050_MODE, ZR050_MO_TLM); + + /* disable all IRQs */ + zr36050_write(ptr, ZR050_INT_REQ_0, 0); + zr36050_write(ptr, ZR050_INT_REQ_1, 3); // low 2 bits always 1 + + dprintk(3, "%s: write DHT\n", ptr->name); + zr36050_pushit(ptr, ZR050_DHT_IDX, sizeof(zr36050_dht), + zr36050_dht); + + /* do the internal huffman table preload */ + zr36050_write(ptr, ZR050_MARKERS_EN, ZR050_ME_DHTI); + + zr36050_write(ptr, ZR050_GO, 1); // launch codec + zr36050_wait_end(ptr); + dprintk(2, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status1); + + if ((ptr->status1 & 0x4) == 0) { + dprintk(1, KERN_ERR "%s: init aborted!\n", + ptr->name); + return; // something is wrong, its timed out!!!! + } + + /* setup misc. data for expansion */ + zr36050_write(ptr, ZR050_MODE, 0); + zr36050_write(ptr, ZR050_MARKERS_EN, 0); + } + + /* adr on selected, to allow GO from master */ + zr36050_read(ptr, 0); +} + +/* ========================================================================= + CODEC API FUNCTIONS + + this functions are accessed by the master via the API structure + ========================================================================= */ + +/* set compression/expansion mode and launches codec - + this should be the last call from the master before starting processing */ +static int +zr36050_set_mode (struct videocodec *codec, + int mode) +{ + struct zr36050 *ptr = (struct zr36050 *) codec->data; + + dprintk(2, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36050_init(ptr); + + return 0; +} + +/* set picture size (norm is ignored as the codec doesn't know about it) */ +static int +zr36050_set_video (struct videocodec *codec, + struct tvnorm *norm, + struct vfe_settings *cap, + struct vfe_polarity *pol) +{ + struct zr36050 *ptr = (struct zr36050 *) codec->data; + int size; + + dprintk(2, "%s: set_video %d.%d, %d/%d-%dx%d (0x%x) q%d call\n", + ptr->name, norm->HStart, norm->VStart, + cap->x, cap->y, cap->width, cap->height, + cap->decimation, cap->quality); + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y and norm for now ... */ + ptr->width = cap->width / (cap->decimation & 0xff); + ptr->height = cap->height / ((cap->decimation >> 8) & 0xff); + + /* (KM) JPEG quality */ + size = ptr->width * ptr->height; + size *= 16; /* size in bits */ + /* apply quality setting */ + size = size * cap->quality / 200; + + /* Minimum: 1kb */ + if (size < 8192) + size = 8192; + /* Maximum: 7/8 of code buffer */ + if (size > ptr->total_code_vol * 7) + size = ptr->total_code_vol * 7; + + ptr->real_code_vol = size >> 3; /* in bytes */ + + /* Set max_block_vol here (previously in zr36050_init, moved + * here for consistency with zr36060 code */ + zr36050_write(ptr, ZR050_MBCV, ptr->max_block_vol); + + return 0; +} + +/* additional control functions */ +static int +zr36050_control (struct videocodec *codec, + int type, + int size, + void *data) +{ + struct zr36050 *ptr = (struct zr36050 *) codec->data; + int *ival = (int *) data; + + dprintk(2, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status */ + if (size != sizeof(int)) + return -EFAULT; + zr36050_read_status1(ptr); + *ival = ptr->status1; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = CODEC_MODE_BJPG; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != CODEC_MODE_BJPG) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + /* not needed, do nothing */ + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + case CODEC_G_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + *ival = ptr->total_code_vol; + break; + + case CODEC_S_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + ptr->total_code_vol = *ival; + /* (Kieran Morrissey) + * code copied from zr36060.c to ensure proper bitrate */ + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + break; + + case CODEC_G_JPEG_SCALE: /* get scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + *ival = zr36050_read_scalefactor(ptr); + break; + + case CODEC_S_JPEG_SCALE: /* set scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + ptr->scalefact = *ival; + break; + + case CODEC_G_JPEG_APP_DATA: { /* get appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + *app = ptr->app; + break; + } + + case CODEC_S_JPEG_APP_DATA: { /* set appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + ptr->app = *app; + break; + } + + case CODEC_G_JPEG_COM_DATA: { /* get comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + *com = ptr->com; + break; + } + + case CODEC_S_JPEG_COM_DATA: { /* set comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + ptr->com = *com; + break; + } + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + Exit and unregister function: + + Deinitializes Zoran's JPEG processor + ========================================================================= */ + +static int +zr36050_unset (struct videocodec *codec) +{ + struct zr36050 *ptr = codec->data; + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + dprintk(1, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36050_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + Setup and registry function: + + Initializes Zoran's JPEG processor + + Also sets pixel size, average code size, mode (compr./decompr.) + (the given size is determined by the processor with the video interface) + ========================================================================= */ + +static int +zr36050_setup (struct videocodec *codec) +{ + struct zr36050 *ptr; + int res; + + dprintk(2, "zr36050: initializing MJPEG subsystem #%d.\n", + zr36050_codecs); + + if (zr36050_codecs == MAX_CODECS) { + dprintk(1, + KERN_ERR "zr36050: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + codec->data = ptr = kmalloc(sizeof(struct zr36050), GFP_KERNEL); + if (NULL == ptr) { + dprintk(1, KERN_ERR "zr36050: Can't get enough memory!\n"); + return -ENOMEM; + } + memset(ptr, 0, sizeof(struct zr36050)); + + snprintf(ptr->name, sizeof(ptr->name), "zr36050[%d]", + zr36050_codecs); + ptr->num = zr36050_codecs++; + ptr->codec = codec; + + //testing + res = zr36050_basic_test(ptr); + if (res < 0) { + zr36050_unset(codec); + return res; + } + //final setup + memcpy(ptr->h_samp_ratio, zr36050_decimation_h, 8); + memcpy(ptr->v_samp_ratio, zr36050_decimation_v, 8); + + ptr->bitrate_ctrl = 0; /* 0 or 1 - fixed file size flag + * (what is the difference?) */ + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 384; + ptr->height = 288; + ptr->total_code_vol = 16000; + ptr->max_block_vol = 240; + ptr->scalefact = 0x100; + ptr->dri = 1; + + /* no app/com marker by default */ + ptr->app.appn = 0; + ptr->app.len = 0; + ptr->com.len = 0; + + zr36050_init(ptr); + + dprintk(1, KERN_INFO "%s: codec attached and running\n", + ptr->name); + + return 0; +} + +static const struct videocodec zr36050_codec = { + .owner = THIS_MODULE, + .name = "zr36050", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_JPEG | CODEC_FLAG_HARDWARE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER, + .type = CODEC_TYPE_ZR36050, + .setup = zr36050_setup, // functionality + .unset = zr36050_unset, + .set_mode = zr36050_set_mode, + .set_video = zr36050_set_video, + .control = zr36050_control, + // others are not used +}; + +/* ========================================================================= + HOOK IN DRIVER AS KERNEL MODULE + ========================================================================= */ + +static int __init +zr36050_init_module (void) +{ + //dprintk(1, "ZR36050 driver %s\n",ZR050_VERSION); + zr36050_codecs = 0; + return videocodec_register(&zr36050_codec); +} + +static void __exit +zr36050_cleanup_module (void) +{ + if (zr36050_codecs) { + dprintk(1, + "zr36050: something's wrong - %d codecs left somehow.\n", + zr36050_codecs); + } + videocodec_unregister(&zr36050_codec); +} + +module_init(zr36050_init_module); +module_exit(zr36050_cleanup_module); + +MODULE_AUTHOR("Wolfgang Scherr <scherr@net4you.at>"); +MODULE_DESCRIPTION("Driver module for ZR36050 jpeg processors " + ZR050_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/zr36050.h b/drivers/media/video/zr36050.h new file mode 100644 index 000000000000..9f52f0cdde50 --- /dev/null +++ b/drivers/media/video/zr36050.h @@ -0,0 +1,184 @@ +/* + * Zoran ZR36050 basic configuration functions - header file + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36050.h,v 1.1.2.2 2003/01/14 21:18:22 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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. + * + * ------------------------------------------------------------------------ + */ + +#ifndef ZR36050_H +#define ZR36050_H + +#include "videocodec.h" + +/* data stored for each zoran jpeg codec chip */ +struct zr36050 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // last coder status + __u8 status1; + // actual coder setup + int mode; + + __u16 width; + __u16 height; + + __u16 bitrate_ctrl; + + __u32 total_code_vol; + __u32 real_code_vol; + __u16 max_block_vol; + + __u8 h_samp_ratio[8]; + __u8 v_samp_ratio[8]; + __u16 scalefact; + __u16 dri; + + /* com/app marker */ + struct jpeg_com_marker com; + struct jpeg_app_marker app; +}; + +/* zr36050 register addresses */ +#define ZR050_GO 0x000 +#define ZR050_HARDWARE 0x002 +#define ZR050_MODE 0x003 +#define ZR050_OPTIONS 0x004 +#define ZR050_MBCV 0x005 +#define ZR050_MARKERS_EN 0x006 +#define ZR050_INT_REQ_0 0x007 +#define ZR050_INT_REQ_1 0x008 +#define ZR050_TCV_NET_HI 0x009 +#define ZR050_TCV_NET_MH 0x00a +#define ZR050_TCV_NET_ML 0x00b +#define ZR050_TCV_NET_LO 0x00c +#define ZR050_TCV_DATA_HI 0x00d +#define ZR050_TCV_DATA_MH 0x00e +#define ZR050_TCV_DATA_ML 0x00f +#define ZR050_TCV_DATA_LO 0x010 +#define ZR050_SF_HI 0x011 +#define ZR050_SF_LO 0x012 +#define ZR050_AF_HI 0x013 +#define ZR050_AF_M 0x014 +#define ZR050_AF_LO 0x015 +#define ZR050_ACV_HI 0x016 +#define ZR050_ACV_MH 0x017 +#define ZR050_ACV_ML 0x018 +#define ZR050_ACV_LO 0x019 +#define ZR050_ACT_HI 0x01a +#define ZR050_ACT_MH 0x01b +#define ZR050_ACT_ML 0x01c +#define ZR050_ACT_LO 0x01d +#define ZR050_ACV_TRUN_HI 0x01e +#define ZR050_ACV_TRUN_MH 0x01f +#define ZR050_ACV_TRUN_ML 0x020 +#define ZR050_ACV_TRUN_LO 0x021 +#define ZR050_STATUS_0 0x02e +#define ZR050_STATUS_1 0x02f + +#define ZR050_SOF_IDX 0x040 +#define ZR050_SOS1_IDX 0x07a +#define ZR050_SOS2_IDX 0x08a +#define ZR050_SOS3_IDX 0x09a +#define ZR050_SOS4_IDX 0x0aa +#define ZR050_DRI_IDX 0x0c0 +#define ZR050_DNL_IDX 0x0c6 +#define ZR050_DQT_IDX 0x0cc +#define ZR050_DHT_IDX 0x1d4 +#define ZR050_APP_IDX 0x380 +#define ZR050_COM_IDX 0x3c0 + +/* zr36050 hardware register bits */ + +#define ZR050_HW_BSWD 0x80 +#define ZR050_HW_MSTR 0x40 +#define ZR050_HW_DMA 0x20 +#define ZR050_HW_CFIS_1_CLK 0x00 +#define ZR050_HW_CFIS_2_CLK 0x04 +#define ZR050_HW_CFIS_3_CLK 0x08 +#define ZR050_HW_CFIS_4_CLK 0x0C +#define ZR050_HW_CFIS_5_CLK 0x10 +#define ZR050_HW_CFIS_6_CLK 0x14 +#define ZR050_HW_CFIS_7_CLK 0x18 +#define ZR050_HW_CFIS_8_CLK 0x1C +#define ZR050_HW_BELE 0x01 + +/* zr36050 mode register bits */ + +#define ZR050_MO_COMP 0x80 +#define ZR050_MO_COMP 0x80 +#define ZR050_MO_ATP 0x40 +#define ZR050_MO_PASS2 0x20 +#define ZR050_MO_TLM 0x10 +#define ZR050_MO_DCONLY 0x08 +#define ZR050_MO_BRC 0x04 + +#define ZR050_MO_ATP 0x40 +#define ZR050_MO_PASS2 0x20 +#define ZR050_MO_TLM 0x10 +#define ZR050_MO_DCONLY 0x08 + +/* zr36050 option register bits */ + +#define ZR050_OP_NSCN_1 0x00 +#define ZR050_OP_NSCN_2 0x20 +#define ZR050_OP_NSCN_3 0x40 +#define ZR050_OP_NSCN_4 0x60 +#define ZR050_OP_NSCN_5 0x80 +#define ZR050_OP_NSCN_6 0xA0 +#define ZR050_OP_NSCN_7 0xC0 +#define ZR050_OP_NSCN_8 0xE0 +#define ZR050_OP_OVF 0x10 + + +/* zr36050 markers-enable register bits */ + +#define ZR050_ME_APP 0x80 +#define ZR050_ME_COM 0x40 +#define ZR050_ME_DRI 0x20 +#define ZR050_ME_DQT 0x10 +#define ZR050_ME_DHT 0x08 +#define ZR050_ME_DNL 0x04 +#define ZR050_ME_DQTI 0x02 +#define ZR050_ME_DHTI 0x01 + +/* zr36050 status0/1 register bit masks */ + +#define ZR050_ST_RST_MASK 0x20 +#define ZR050_ST_SOF_MASK 0x02 +#define ZR050_ST_SOS_MASK 0x02 +#define ZR050_ST_DATRDY_MASK 0x80 +#define ZR050_ST_MRKDET_MASK 0x40 +#define ZR050_ST_RFM_MASK 0x10 +#define ZR050_ST_RFD_MASK 0x08 +#define ZR050_ST_END_MASK 0x04 +#define ZR050_ST_TCVOVF_MASK 0x02 +#define ZR050_ST_DATOVF_MASK 0x01 + +/* pixel component idx */ + +#define ZR050_Y_COMPONENT 0 +#define ZR050_U_COMPONENT 1 +#define ZR050_V_COMPONENT 2 + +#endif /*fndef ZR36050_H */ diff --git a/drivers/media/video/zr36057.h b/drivers/media/video/zr36057.h new file mode 100644 index 000000000000..159abfa034d9 --- /dev/null +++ b/drivers/media/video/zr36057.h @@ -0,0 +1,168 @@ +/* + * zr36057.h - zr36057 register offsets + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _ZR36057_H_ +#define _ZR36057_H_ + + +/* Zoran ZR36057 registers */ + +#define ZR36057_VFEHCR 0x000 /* Video Front End, Horizontal Configuration Register */ +#define ZR36057_VFEHCR_HSPol (1<<30) +#define ZR36057_VFEHCR_HStart 10 +#define ZR36057_VFEHCR_HEnd 0 +#define ZR36057_VFEHCR_Hmask 0x3ff + +#define ZR36057_VFEVCR 0x004 /* Video Front End, Vertical Configuration Register */ +#define ZR36057_VFEVCR_VSPol (1<<30) +#define ZR36057_VFEVCR_VStart 10 +#define ZR36057_VFEVCR_VEnd 0 +#define ZR36057_VFEVCR_Vmask 0x3ff + +#define ZR36057_VFESPFR 0x008 /* Video Front End, Scaler and Pixel Format Register */ +#define ZR36057_VFESPFR_ExtFl (1<<26) +#define ZR36057_VFESPFR_TopField (1<<25) +#define ZR36057_VFESPFR_VCLKPol (1<<24) +#define ZR36057_VFESPFR_HFilter 21 +#define ZR36057_VFESPFR_HorDcm 14 +#define ZR36057_VFESPFR_VerDcm 8 +#define ZR36057_VFESPFR_DispMode 6 +#define ZR36057_VFESPFR_YUV422 (0<<3) +#define ZR36057_VFESPFR_RGB888 (1<<3) +#define ZR36057_VFESPFR_RGB565 (2<<3) +#define ZR36057_VFESPFR_RGB555 (3<<3) +#define ZR36057_VFESPFR_ErrDif (1<<2) +#define ZR36057_VFESPFR_Pack24 (1<<1) +#define ZR36057_VFESPFR_LittleEndian (1<<0) + +#define ZR36057_VDTR 0x00c /* Video Display "Top" Register */ + +#define ZR36057_VDBR 0x010 /* Video Display "Bottom" Register */ + +#define ZR36057_VSSFGR 0x014 /* Video Stride, Status, and Frame Grab Register */ +#define ZR36057_VSSFGR_DispStride 16 +#define ZR36057_VSSFGR_VidOvf (1<<8) +#define ZR36057_VSSFGR_SnapShot (1<<1) +#define ZR36057_VSSFGR_FrameGrab (1<<0) + +#define ZR36057_VDCR 0x018 /* Video Display Configuration Register */ +#define ZR36057_VDCR_VidEn (1<<31) +#define ZR36057_VDCR_MinPix 24 +#define ZR36057_VDCR_Triton (1<<24) +#define ZR36057_VDCR_VidWinHt 12 +#define ZR36057_VDCR_VidWinWid 0 + +#define ZR36057_MMTR 0x01c /* Masking Map "Top" Register */ + +#define ZR36057_MMBR 0x020 /* Masking Map "Bottom" Register */ + +#define ZR36057_OCR 0x024 /* Overlay Control Register */ +#define ZR36057_OCR_OvlEnable (1 << 15) +#define ZR36057_OCR_MaskStride 0 + +#define ZR36057_SPGPPCR 0x028 /* System, PCI, and General Purpose Pins Control Register */ +#define ZR36057_SPGPPCR_SoftReset (1<<24) + +#define ZR36057_GPPGCR1 0x02c /* General Purpose Pins and GuestBus Control Register (1) */ + +#define ZR36057_MCSAR 0x030 /* MPEG Code Source Address Register */ + +#define ZR36057_MCTCR 0x034 /* MPEG Code Transfer Control Register */ +#define ZR36057_MCTCR_CodTime (1 << 30) +#define ZR36057_MCTCR_CEmpty (1 << 29) +#define ZR36057_MCTCR_CFlush (1 << 28) +#define ZR36057_MCTCR_CodGuestID 20 +#define ZR36057_MCTCR_CodGuestReg 16 + +#define ZR36057_MCMPR 0x038 /* MPEG Code Memory Pointer Register */ + +#define ZR36057_ISR 0x03c /* Interrupt Status Register */ +#define ZR36057_ISR_GIRQ1 (1<<30) +#define ZR36057_ISR_GIRQ0 (1<<29) +#define ZR36057_ISR_CodRepIRQ (1<<28) +#define ZR36057_ISR_JPEGRepIRQ (1<<27) + +#define ZR36057_ICR 0x040 /* Interrupt Control Register */ +#define ZR36057_ICR_GIRQ1 (1<<30) +#define ZR36057_ICR_GIRQ0 (1<<29) +#define ZR36057_ICR_CodRepIRQ (1<<28) +#define ZR36057_ICR_JPEGRepIRQ (1<<27) +#define ZR36057_ICR_IntPinEn (1<<24) + +#define ZR36057_I2CBR 0x044 /* I2C Bus Register */ +#define ZR36057_I2CBR_SDA (1<<1) +#define ZR36057_I2CBR_SCL (1<<0) + +#define ZR36057_JMC 0x100 /* JPEG Mode and Control */ +#define ZR36057_JMC_JPG (1 << 31) +#define ZR36057_JMC_JPGExpMode (0 << 29) +#define ZR36057_JMC_JPGCmpMode (1 << 29) +#define ZR36057_JMC_MJPGExpMode (2 << 29) +#define ZR36057_JMC_MJPGCmpMode (3 << 29) +#define ZR36057_JMC_RTBUSY_FB (1 << 6) +#define ZR36057_JMC_Go_en (1 << 5) +#define ZR36057_JMC_SyncMstr (1 << 4) +#define ZR36057_JMC_Fld_per_buff (1 << 3) +#define ZR36057_JMC_VFIFO_FB (1 << 2) +#define ZR36057_JMC_CFIFO_FB (1 << 1) +#define ZR36057_JMC_Stll_LitEndian (1 << 0) + +#define ZR36057_JPC 0x104 /* JPEG Process Control */ +#define ZR36057_JPC_P_Reset (1 << 7) +#define ZR36057_JPC_CodTrnsEn (1 << 5) +#define ZR36057_JPC_Active (1 << 0) + +#define ZR36057_VSP 0x108 /* Vertical Sync Parameters */ +#define ZR36057_VSP_VsyncSize 16 +#define ZR36057_VSP_FrmTot 0 + +#define ZR36057_HSP 0x10c /* Horizontal Sync Parameters */ +#define ZR36057_HSP_HsyncStart 16 +#define ZR36057_HSP_LineTot 0 + +#define ZR36057_FHAP 0x110 /* Field Horizontal Active Portion */ +#define ZR36057_FHAP_NAX 16 +#define ZR36057_FHAP_PAX 0 + +#define ZR36057_FVAP 0x114 /* Field Vertical Active Portion */ +#define ZR36057_FVAP_NAY 16 +#define ZR36057_FVAP_PAY 0 + +#define ZR36057_FPP 0x118 /* Field Process Parameters */ +#define ZR36057_FPP_Odd_Even (1 << 0) + +#define ZR36057_JCBA 0x11c /* JPEG Code Base Address */ + +#define ZR36057_JCFT 0x120 /* JPEG Code FIFO Threshold */ + +#define ZR36057_JCGI 0x124 /* JPEG Codec Guest ID */ +#define ZR36057_JCGI_JPEGuestID 4 +#define ZR36057_JCGI_JPEGuestReg 0 + +#define ZR36057_GCR2 0x12c /* GuestBus Control Register (2) */ + +#define ZR36057_POR 0x200 /* Post Office Register */ +#define ZR36057_POR_POPen (1<<25) +#define ZR36057_POR_POTime (1<<24) +#define ZR36057_POR_PODir (1<<23) + +#define ZR36057_STR 0x300 /* "Still" Transfer Register */ + +#endif diff --git a/drivers/media/video/zr36060.c b/drivers/media/video/zr36060.c new file mode 100644 index 000000000000..b50dc403e6db --- /dev/null +++ b/drivers/media/video/zr36060.c @@ -0,0 +1,1016 @@ +/* + * Zoran ZR36060 basic configuration functions + * + * Copyright (C) 2002 Laurent Pinchart <laurent.pinchart@skynet.be> + * + * $Id: zr36060.c,v 1.1.2.22 2003/05/06 09:35:36 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ------------------------------------------------------------------------ + */ + +#define ZR060_VERSION "v0.7" + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/types.h> +#include <linux/wait.h> + +/* includes for structures and defines regarding video + #include<linux/videodev.h> */ + +/* I/O commands, error codes */ +#include<asm/io.h> +//#include<errno.h> + +/* headerfile of this module */ +#include"zr36060.h" + +/* codec io API */ +#include"videocodec.h" + +/* it doesn't make sense to have more than 20 or so, + just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36060_codecs = 0; + +static int low_bitrate = 0; +module_param(low_bitrate, bool, 0); +MODULE_PARM_DESC(low_bitrate, "Buz compatibility option, halves bitrate"); + +/* debugging is available via module parameter */ +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ========================================================================= + Local hardware I/O functions: + + read/write via codec layer (registers are located in the master device) + ========================================================================= */ + +/* read and write functions */ +static u8 +zr36060_read (struct zr36060 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, + reg)) & 0xff; + else + dprintk(1, + KERN_ERR "%s: invalid I/O setup, nothing read!\n", + ptr->name); + + //dprintk(4, "%s: reading from 0x%04x: %02x\n",ptr->name,reg,value); + + return value; +} + +static void +zr36060_write(struct zr36060 *ptr, + u16 reg, + u8 value) +{ + //dprintk(4, "%s: writing 0x%02x to 0x%04x\n",ptr->name,value,reg); + dprintk(4, "0x%02x @0x%04x\n", value, reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* ========================================================================= + Local helper function: + + status read + ========================================================================= */ + +/* status is kept in datastructure */ +static u8 +zr36060_read_status (struct zr36060 *ptr) +{ + ptr->status = zr36060_read(ptr, ZR060_CFSR); + + zr36060_read(ptr, 0); + return ptr->status; +} + +/* ========================================================================= + Local helper function: + + scale factor read + ========================================================================= */ + +/* scale factor is kept in datastructure */ +static u16 +zr36060_read_scalefactor (struct zr36060 *ptr) +{ + ptr->scalefact = (zr36060_read(ptr, ZR060_SF_HI) << 8) | + (zr36060_read(ptr, ZR060_SF_LO) & 0xFF); + + /* leave 0 selected for an eventually GO from master */ + zr36060_read(ptr, 0); + return ptr->scalefact; +} + +/* ========================================================================= + Local helper function: + + wait if codec is ready to proceed (end of processing) or time is over + ========================================================================= */ + +static void +zr36060_wait_end (struct zr36060 *ptr) +{ + int i = 0; + + while (zr36060_read_status(ptr) & ZR060_CFSR_Busy) { + udelay(1); + if (i++ > 200000) { // 200ms, there is for shure something wrong!!! + dprintk(1, + "%s: timout at wait_end (last status: 0x%02x)\n", + ptr->name, ptr->status); + break; + } + } +} + +/* ========================================================================= + Local helper function: + + basic test of "connectivity", writes/reads to/from memory the SOF marker + ========================================================================= */ + +static int +zr36060_basic_test (struct zr36060 *ptr) +{ + if ((zr36060_read(ptr, ZR060_IDR_DEV) != 0x33) && + (zr36060_read(ptr, ZR060_IDR_REV) != 0x01)) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + + zr36060_wait_end(ptr); + if (ptr->status & ZR060_CFSR_Busy) { + dprintk(1, + KERN_ERR + "%s: attach failed, jpeg processor failed (end flag)!\n", + ptr->name); + return -EBUSY; + } + + return 0; /* looks good! */ +} + +/* ========================================================================= + Local helper function: + + simple loop for pushing the init datasets + ========================================================================= */ + +static int +zr36060_pushit (struct zr36060 *ptr, + u16 startreg, + u16 len, + const char *data) +{ + int i = 0; + + dprintk(4, "%s: write data block to 0x%04x (len=%d)\n", ptr->name, + startreg, len); + while (i < len) { + zr36060_write(ptr, startreg++, data[i++]); + } + + return i; +} + +/* ========================================================================= + Basic datasets: + + jpeg baseline setup data (you find it on lots places in internet, or just + extract it from any regular .jpg image...) + + Could be variable, but until it's not needed it they are just fixed to save + memory. Otherwise expand zr36060 structure with arrays, push the values to + it and initalize from there, as e.g. the linux zr36057/60 driver does it. + ========================================================================= */ + +static const char zr36060_dqt[0x86] = { + 0xff, 0xdb, //Marker: DQT + 0x00, 0x84, //Length: 2*65+2 + 0x00, //Pq,Tq first table + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0x01, //Pq,Tq second table + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const char zr36060_dht[0x1a4] = { + 0xff, 0xc4, //Marker: DHT + 0x01, 0xa2, //Length: 2*AC, 2*DC + 0x00, //DC first table + 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, + 0x01, //DC second table + 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, + 0x10, //AC first table + 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, + 0x11, //AC second table + 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 +}; + +/* jpeg baseline setup, this is just fixed in this driver (YUV pictures) */ +#define NO_OF_COMPONENTS 0x3 //Y,U,V +#define BASELINE_PRECISION 0x8 //MCU size (?) +static const char zr36060_tq[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's QT +static const char zr36060_td[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's DC +static const char zr36060_ta[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's AC + +/* horizontal 422 decimation setup (maybe we support 411 or so later, too) */ +static const char zr36060_decimation_h[8] = { 2, 1, 1, 0, 0, 0, 0, 0 }; +static const char zr36060_decimation_v[8] = { 1, 1, 1, 0, 0, 0, 0, 0 }; + +/* ========================================================================= + Local helper functions: + + calculation and setup of parameter-dependent JPEG baseline segments + (needed for compression only) + ========================================================================= */ + +/* ------------------------------------------------------------------------- */ + +/* SOF (start of frame) segment depends on width, height and sampling ratio + of each color component */ + +static int +zr36060_set_sof (struct zr36060 *ptr) +{ + char sof_data[34]; // max. size of register set + int i; + + dprintk(3, "%s: write SOF (%dx%d, %d components)\n", ptr->name, + ptr->width, ptr->height, NO_OF_COMPONENTS); + sof_data[0] = 0xff; + sof_data[1] = 0xc0; + sof_data[2] = 0x00; + sof_data[3] = (3 * NO_OF_COMPONENTS) + 8; + sof_data[4] = BASELINE_PRECISION; // only '8' possible with zr36060 + sof_data[5] = (ptr->height) >> 8; + sof_data[6] = (ptr->height) & 0xff; + sof_data[7] = (ptr->width) >> 8; + sof_data[8] = (ptr->width) & 0xff; + sof_data[9] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sof_data[10 + (i * 3)] = i; // index identifier + sof_data[11 + (i * 3)] = (ptr->h_samp_ratio[i] << 4) | + (ptr->v_samp_ratio[i]); // sampling ratios + sof_data[12 + (i * 3)] = zr36060_tq[i]; // Q table selection + } + return zr36060_pushit(ptr, ZR060_SOF_IDX, + (3 * NO_OF_COMPONENTS) + 10, sof_data); +} + +/* ------------------------------------------------------------------------- */ + +/* SOS (start of scan) segment depends on the used scan components + of each color component */ + +static int +zr36060_set_sos (struct zr36060 *ptr) +{ + char sos_data[16]; // max. size of register set + int i; + + dprintk(3, "%s: write SOS\n", ptr->name); + sos_data[0] = 0xff; + sos_data[1] = 0xda; + sos_data[2] = 0x00; + sos_data[3] = 2 + 1 + (2 * NO_OF_COMPONENTS) + 3; + sos_data[4] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sos_data[5 + (i * 2)] = i; // index + sos_data[6 + (i * 2)] = (zr36060_td[i] << 4) | + zr36060_ta[i]; // AC/DC tbl.sel. + } + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 2] = 00; // scan start + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 3] = 0x3f; + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 4] = 00; + return zr36060_pushit(ptr, ZR060_SOS_IDX, + 4 + 1 + (2 * NO_OF_COMPONENTS) + 3, + sos_data); +} + +/* ------------------------------------------------------------------------- */ + +/* DRI (define restart interval) */ + +static int +zr36060_set_dri (struct zr36060 *ptr) +{ + char dri_data[6]; // max. size of register set + + dprintk(3, "%s: write DRI\n", ptr->name); + dri_data[0] = 0xff; + dri_data[1] = 0xdd; + dri_data[2] = 0x00; + dri_data[3] = 0x04; + dri_data[4] = (ptr->dri) >> 8; + dri_data[5] = (ptr->dri) & 0xff; + return zr36060_pushit(ptr, ZR060_DRI_IDX, 6, dri_data); +} + +/* ========================================================================= + Setup function: + + Setup compression/decompression of Zoran's JPEG processor + ( see also zoran 36060 manual ) + + ... sorry for the spaghetti code ... + ========================================================================= */ +static void +zr36060_init (struct zr36060 *ptr) +{ + int sum = 0; + long bitcnt, tmp; + + if (ptr->mode == CODEC_DO_COMPRESSION) { + dprintk(2, "%s: COMPRESSION SETUP\n", ptr->name); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SyncRst); + + /* 060 communicates with 067 in master mode */ + zr36060_write(ptr, ZR060_CIR, ZR060_CIR_CodeMstr); + + /* Compression with or without variable scale factor */ + /*FIXME: What about ptr->bitrate_ctrl? */ + zr36060_write(ptr, ZR060_CMR, + ZR060_CMR_Comp | ZR060_CMR_Pass2 | + ZR060_CMR_BRB); + + /* Must be zero */ + zr36060_write(ptr, ZR060_MBZ, 0x00); + zr36060_write(ptr, ZR060_TCR_HI, 0x00); + zr36060_write(ptr, ZR060_TCR_LO, 0x00); + + /* Disable all IRQs - no DataErr means autoreset */ + zr36060_write(ptr, ZR060_IMR, 0); + + /* volume control settings */ + zr36060_write(ptr, ZR060_SF_HI, ptr->scalefact >> 8); + zr36060_write(ptr, ZR060_SF_LO, ptr->scalefact & 0xff); + + zr36060_write(ptr, ZR060_AF_HI, 0xff); + zr36060_write(ptr, ZR060_AF_M, 0xff); + zr36060_write(ptr, ZR060_AF_LO, 0xff); + + /* setup the variable jpeg tables */ + sum += zr36060_set_sof(ptr); + sum += zr36060_set_sos(ptr); + sum += zr36060_set_dri(ptr); + + /* setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) */ + sum += + zr36060_pushit(ptr, ZR060_DQT_IDX, sizeof(zr36060_dqt), + zr36060_dqt); + sum += + zr36060_pushit(ptr, ZR060_DHT_IDX, sizeof(zr36060_dht), + zr36060_dht); + zr36060_write(ptr, ZR060_APP_IDX, 0xff); + zr36060_write(ptr, ZR060_APP_IDX + 1, 0xe0 + ptr->app.appn); + zr36060_write(ptr, ZR060_APP_IDX + 2, 0x00); + zr36060_write(ptr, ZR060_APP_IDX + 3, ptr->app.len + 2); + sum += zr36060_pushit(ptr, ZR060_APP_IDX + 4, 60, + ptr->app.data) + 4; + zr36060_write(ptr, ZR060_COM_IDX, 0xff); + zr36060_write(ptr, ZR060_COM_IDX + 1, 0xfe); + zr36060_write(ptr, ZR060_COM_IDX + 2, 0x00); + zr36060_write(ptr, ZR060_COM_IDX + 3, ptr->com.len + 2); + sum += zr36060_pushit(ptr, ZR060_COM_IDX + 4, 60, + ptr->com.data) + 4; + + /* setup misc. data for compression (target code sizes) */ + + /* size of compressed code to reach without header data */ + sum = ptr->real_code_vol - sum; + bitcnt = sum << 3; /* need the size in bits */ + + tmp = bitcnt >> 16; + dprintk(3, + "%s: code: csize=%d, tot=%d, bit=%ld, highbits=%ld\n", + ptr->name, sum, ptr->real_code_vol, bitcnt, tmp); + zr36060_write(ptr, ZR060_TCV_NET_HI, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_NET_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36060_write(ptr, ZR060_TCV_NET_ML, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_NET_LO, tmp & 0xff); + + bitcnt -= bitcnt >> 7; // bits without stuffing + bitcnt -= ((bitcnt * 5) >> 6); // bits without eob + + tmp = bitcnt >> 16; + dprintk(3, "%s: code: nettobit=%ld, highnettobits=%ld\n", + ptr->name, bitcnt, tmp); + zr36060_write(ptr, ZR060_TCV_DATA_HI, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_DATA_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36060_write(ptr, ZR060_TCV_DATA_ML, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_DATA_LO, tmp & 0xff); + + /* JPEG markers to be included in the compressed stream */ + zr36060_write(ptr, ZR060_MER, + ZR060_MER_DQT | ZR060_MER_DHT | + ((ptr->com.len > 0) ? ZR060_MER_Com : 0) | + ((ptr->app.len > 0) ? ZR060_MER_App : 0)); + + /* Setup the Video Frontend */ + /* Limit pixel range to 16..235 as per CCIR-601 */ + zr36060_write(ptr, ZR060_VCR, ZR060_VCR_Range); + + } else { + dprintk(2, "%s: EXPANSION SETUP\n", ptr->name); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SyncRst); + + /* 060 communicates with 067 in master mode */ + zr36060_write(ptr, ZR060_CIR, ZR060_CIR_CodeMstr); + + /* Decompression */ + zr36060_write(ptr, ZR060_CMR, 0); + + /* Must be zero */ + zr36060_write(ptr, ZR060_MBZ, 0x00); + zr36060_write(ptr, ZR060_TCR_HI, 0x00); + zr36060_write(ptr, ZR060_TCR_LO, 0x00); + + /* Disable all IRQs - no DataErr means autoreset */ + zr36060_write(ptr, ZR060_IMR, 0); + + /* setup misc. data for expansion */ + zr36060_write(ptr, ZR060_MER, 0); + + /* setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) */ + zr36060_pushit(ptr, ZR060_DHT_IDX, sizeof(zr36060_dht), + zr36060_dht); + + /* Setup the Video Frontend */ + //zr36060_write(ptr, ZR060_VCR, ZR060_VCR_FIExt); + //this doesn't seem right and doesn't work... + zr36060_write(ptr, ZR060_VCR, ZR060_VCR_Range); + } + + /* Load the tables */ + zr36060_write(ptr, ZR060_LOAD, + ZR060_LOAD_SyncRst | ZR060_LOAD_Load); + zr36060_wait_end(ptr); + dprintk(2, "%s: Status after table preload: 0x%02x\n", ptr->name, + ptr->status); + + if (ptr->status & ZR060_CFSR_Busy) { + dprintk(1, KERN_ERR "%s: init aborted!\n", ptr->name); + return; // something is wrong, its timed out!!!! + } +} + +/* ========================================================================= + CODEC API FUNCTIONS + + this functions are accessed by the master via the API structure + ========================================================================= */ + +/* set compression/expansion mode and launches codec - + this should be the last call from the master before starting processing */ +static int +zr36060_set_mode (struct videocodec *codec, + int mode) +{ + struct zr36060 *ptr = (struct zr36060 *) codec->data; + + dprintk(2, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36060_init(ptr); + + return 0; +} + +/* set picture size (norm is ignored as the codec doesn't know about it) */ +static int +zr36060_set_video (struct videocodec *codec, + struct tvnorm *norm, + struct vfe_settings *cap, + struct vfe_polarity *pol) +{ + struct zr36060 *ptr = (struct zr36060 *) codec->data; + u32 reg; + int size; + + dprintk(2, "%s: set_video %d/%d-%dx%d (%%%d) call\n", ptr->name, + cap->x, cap->y, cap->width, cap->height, cap->decimation); + + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y and norm for now ... */ + ptr->width = cap->width / (cap->decimation & 0xff); + ptr->height = cap->height / (cap->decimation >> 8); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SyncRst); + + /* Note that VSPol/HSPol bits in zr36060 have the opposite + * meaning of their zr360x7 counterparts with the same names + * N.b. for VSPol this is only true if FIVEdge = 0 (default, + * left unchanged here - in accordance with datasheet). + */ + reg = (!pol->vsync_pol ? ZR060_VPR_VSPol : 0) + | (!pol->hsync_pol ? ZR060_VPR_HSPol : 0) + | (pol->field_pol ? ZR060_VPR_FIPol : 0) + | (pol->blank_pol ? ZR060_VPR_BLPol : 0) + | (pol->subimg_pol ? ZR060_VPR_SImgPol : 0) + | (pol->poe_pol ? ZR060_VPR_PoePol : 0) + | (pol->pvalid_pol ? ZR060_VPR_PValPol : 0) + | (pol->vclk_pol ? ZR060_VPR_VCLKPol : 0); + zr36060_write(ptr, ZR060_VPR, reg); + + reg = 0; + switch (cap->decimation & 0xff) { + default: + case 1: + break; + + case 2: + reg |= ZR060_SR_HScale2; + break; + + case 4: + reg |= ZR060_SR_HScale4; + break; + } + + switch (cap->decimation >> 8) { + default: + case 1: + break; + + case 2: + reg |= ZR060_SR_VScale; + break; + } + zr36060_write(ptr, ZR060_SR, reg); + + zr36060_write(ptr, ZR060_BCR_Y, 0x00); + zr36060_write(ptr, ZR060_BCR_U, 0x80); + zr36060_write(ptr, ZR060_BCR_V, 0x80); + + /* sync generator */ + + reg = norm->Ht - 1; /* Vtotal */ + zr36060_write(ptr, ZR060_SGR_VTOTAL_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_VTOTAL_LO, (reg >> 0) & 0xff); + + reg = norm->Wt - 1; /* Htotal */ + zr36060_write(ptr, ZR060_SGR_HTOTAL_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_HTOTAL_LO, (reg >> 0) & 0xff); + + reg = 6 - 1; /* VsyncSize */ + zr36060_write(ptr, ZR060_SGR_VSYNC, reg); + + //reg = 30 - 1; /* HsyncSize */ +///*CP*/ reg = (zr->params.norm == 1 ? 57 : 68); + reg = 68; + zr36060_write(ptr, ZR060_SGR_HSYNC, reg); + + reg = norm->VStart - 1; /* BVstart */ + zr36060_write(ptr, ZR060_SGR_BVSTART, reg); + + reg += norm->Ha / 2; /* BVend */ + zr36060_write(ptr, ZR060_SGR_BVEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_BVEND_LO, (reg >> 0) & 0xff); + + reg = norm->HStart - 1; /* BHstart */ + zr36060_write(ptr, ZR060_SGR_BHSTART, reg); + + reg += norm->Wa; /* BHend */ + zr36060_write(ptr, ZR060_SGR_BHEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_BHEND_LO, (reg >> 0) & 0xff); + + /* active area */ + reg = cap->y + norm->VStart; /* Vstart */ + zr36060_write(ptr, ZR060_AAR_VSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_VSTART_LO, (reg >> 0) & 0xff); + + reg += cap->height; /* Vend */ + zr36060_write(ptr, ZR060_AAR_VEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_VEND_LO, (reg >> 0) & 0xff); + + reg = cap->x + norm->HStart; /* Hstart */ + zr36060_write(ptr, ZR060_AAR_HSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_HSTART_LO, (reg >> 0) & 0xff); + + reg += cap->width; /* Hend */ + zr36060_write(ptr, ZR060_AAR_HEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_HEND_LO, (reg >> 0) & 0xff); + + /* subimage area */ + reg = norm->VStart - 4; /* SVstart */ + zr36060_write(ptr, ZR060_SWR_VSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_VSTART_LO, (reg >> 0) & 0xff); + + reg += norm->Ha / 2 + 8; /* SVend */ + zr36060_write(ptr, ZR060_SWR_VEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_VEND_LO, (reg >> 0) & 0xff); + + reg = norm->HStart /*+ 64 */ - 4; /* SHstart */ + zr36060_write(ptr, ZR060_SWR_HSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_HSTART_LO, (reg >> 0) & 0xff); + + reg += norm->Wa + 8; /* SHend */ + zr36060_write(ptr, ZR060_SWR_HEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_HEND_LO, (reg >> 0) & 0xff); + + size = ptr->width * ptr->height; + /* Target compressed field size in bits: */ + size = size * 16; /* uncompressed size in bits */ + /* (Ronald) by default, quality = 100 is a compression + * ratio 1:2. Setting low_bitrate (insmod option) sets + * it to 1:4 (instead of 1:2, zr36060 max) as limit because the + * buz can't handle more at decimation=1... Use low_bitrate if + * you have a Buz, unless you know what you're doing */ + size = size * cap->quality / (low_bitrate ? 400 : 200); + /* Lower limit (arbitrary, 1 KB) */ + if (size < 8192) + size = 8192; + /* Upper limit: 7/8 of the code buffers */ + if (size > ptr->total_code_vol * 7) + size = ptr->total_code_vol * 7; + + ptr->real_code_vol = size >> 3; /* in bytes */ + + /* the MBCVR is the *maximum* block volume, according to the + * JPEG ISO specs, this shouldn't be used, since that allows + * for the best encoding quality. So set it to it's max value */ + reg = ptr->max_block_vol; + zr36060_write(ptr, ZR060_MBCVR, reg); + + return 0; +} + +/* additional control functions */ +static int +zr36060_control (struct videocodec *codec, + int type, + int size, + void *data) +{ + struct zr36060 *ptr = (struct zr36060 *) codec->data; + int *ival = (int *) data; + + dprintk(2, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status */ + if (size != sizeof(int)) + return -EFAULT; + zr36060_read_status(ptr); + *ival = ptr->status; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = CODEC_MODE_BJPG; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != CODEC_MODE_BJPG) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + /* not needed, do nothing */ + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + case CODEC_G_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + *ival = ptr->total_code_vol; + break; + + case CODEC_S_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + ptr->total_code_vol = *ival; + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + break; + + case CODEC_G_JPEG_SCALE: /* get scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + *ival = zr36060_read_scalefactor(ptr); + break; + + case CODEC_S_JPEG_SCALE: /* set scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + ptr->scalefact = *ival; + break; + + case CODEC_G_JPEG_APP_DATA: { /* get appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + *app = ptr->app; + break; + } + + case CODEC_S_JPEG_APP_DATA: { /* set appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + ptr->app = *app; + break; + } + + case CODEC_G_JPEG_COM_DATA: { /* get comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + *com = ptr->com; + break; + } + + case CODEC_S_JPEG_COM_DATA: { /* set comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + ptr->com = *com; + break; + } + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + Exit and unregister function: + + Deinitializes Zoran's JPEG processor + ========================================================================= */ + +static int +zr36060_unset (struct videocodec *codec) +{ + struct zr36060 *ptr = codec->data; + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + dprintk(1, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36060_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + Setup and registry function: + + Initializes Zoran's JPEG processor + + Also sets pixel size, average code size, mode (compr./decompr.) + (the given size is determined by the processor with the video interface) + ========================================================================= */ + +static int +zr36060_setup (struct videocodec *codec) +{ + struct zr36060 *ptr; + int res; + + dprintk(2, "zr36060: initializing MJPEG subsystem #%d.\n", + zr36060_codecs); + + if (zr36060_codecs == MAX_CODECS) { + dprintk(1, + KERN_ERR "zr36060: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + codec->data = ptr = kmalloc(sizeof(struct zr36060), GFP_KERNEL); + if (NULL == ptr) { + dprintk(1, KERN_ERR "zr36060: Can't get enough memory!\n"); + return -ENOMEM; + } + memset(ptr, 0, sizeof(struct zr36060)); + + snprintf(ptr->name, sizeof(ptr->name), "zr36060[%d]", + zr36060_codecs); + ptr->num = zr36060_codecs++; + ptr->codec = codec; + + //testing + res = zr36060_basic_test(ptr); + if (res < 0) { + zr36060_unset(codec); + return res; + } + //final setup + memcpy(ptr->h_samp_ratio, zr36060_decimation_h, 8); + memcpy(ptr->v_samp_ratio, zr36060_decimation_v, 8); + + ptr->bitrate_ctrl = 0; /* 0 or 1 - fixed file size flag + * (what is the difference?) */ + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 384; + ptr->height = 288; + ptr->total_code_vol = 16000; /* CHECKME */ + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + ptr->max_block_vol = 240; /* CHECKME, was 120 is 240 */ + ptr->scalefact = 0x100; + ptr->dri = 1; /* CHECKME, was 8 is 1 */ + + /* by default, no COM or APP markers - app should set those */ + ptr->com.len = 0; + ptr->app.appn = 0; + ptr->app.len = 0; + + zr36060_init(ptr); + + dprintk(1, KERN_INFO "%s: codec attached and running\n", + ptr->name); + + return 0; +} + +static const struct videocodec zr36060_codec = { + .owner = THIS_MODULE, + .name = "zr36060", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_JPEG | CODEC_FLAG_HARDWARE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER | CODEC_FLAG_VFE, + .type = CODEC_TYPE_ZR36060, + .setup = zr36060_setup, // functionality + .unset = zr36060_unset, + .set_mode = zr36060_set_mode, + .set_video = zr36060_set_video, + .control = zr36060_control, + // others are not used +}; + +/* ========================================================================= + HOOK IN DRIVER AS KERNEL MODULE + ========================================================================= */ + +static int __init +zr36060_init_module (void) +{ + //dprintk(1, "zr36060 driver %s\n",ZR060_VERSION); + zr36060_codecs = 0; + return videocodec_register(&zr36060_codec); +} + +static void __exit +zr36060_cleanup_module (void) +{ + if (zr36060_codecs) { + dprintk(1, + "zr36060: something's wrong - %d codecs left somehow.\n", + zr36060_codecs); + } + + /* however, we can't just stay alive */ + videocodec_unregister(&zr36060_codec); +} + +module_init(zr36060_init_module); +module_exit(zr36060_cleanup_module); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@skynet.be>"); +MODULE_DESCRIPTION("Driver module for ZR36060 jpeg processors " + ZR060_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/zr36060.h b/drivers/media/video/zr36060.h new file mode 100644 index 000000000000..914ffa4ad8d3 --- /dev/null +++ b/drivers/media/video/zr36060.h @@ -0,0 +1,220 @@ +/* + * Zoran ZR36060 basic configuration functions - header file + * + * Copyright (C) 2002 Laurent Pinchart <laurent.pinchart@skynet.be> + * + * $Id: zr36060.h,v 1.1.1.1.2.3 2003/01/14 21:18:47 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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. + * + * ------------------------------------------------------------------------ + */ + +#ifndef ZR36060_H +#define ZR36060_H + +#include "videocodec.h" + +/* data stored for each zoran jpeg codec chip */ +struct zr36060 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // last coder status + __u8 status; + // actual coder setup + int mode; + + __u16 width; + __u16 height; + + __u16 bitrate_ctrl; + + __u32 total_code_vol; + __u32 real_code_vol; + __u16 max_block_vol; + + __u8 h_samp_ratio[8]; + __u8 v_samp_ratio[8]; + __u16 scalefact; + __u16 dri; + + /* app/com marker data */ + struct jpeg_app_marker app; + struct jpeg_com_marker com; +}; + +/* ZR36060 register addresses */ +#define ZR060_LOAD 0x000 +#define ZR060_CFSR 0x001 +#define ZR060_CIR 0x002 +#define ZR060_CMR 0x003 +#define ZR060_MBZ 0x004 +#define ZR060_MBCVR 0x005 +#define ZR060_MER 0x006 +#define ZR060_IMR 0x007 +#define ZR060_ISR 0x008 +#define ZR060_TCV_NET_HI 0x009 +#define ZR060_TCV_NET_MH 0x00a +#define ZR060_TCV_NET_ML 0x00b +#define ZR060_TCV_NET_LO 0x00c +#define ZR060_TCV_DATA_HI 0x00d +#define ZR060_TCV_DATA_MH 0x00e +#define ZR060_TCV_DATA_ML 0x00f +#define ZR060_TCV_DATA_LO 0x010 +#define ZR060_SF_HI 0x011 +#define ZR060_SF_LO 0x012 +#define ZR060_AF_HI 0x013 +#define ZR060_AF_M 0x014 +#define ZR060_AF_LO 0x015 +#define ZR060_ACV_HI 0x016 +#define ZR060_ACV_MH 0x017 +#define ZR060_ACV_ML 0x018 +#define ZR060_ACV_LO 0x019 +#define ZR060_ACT_HI 0x01a +#define ZR060_ACT_MH 0x01b +#define ZR060_ACT_ML 0x01c +#define ZR060_ACT_LO 0x01d +#define ZR060_ACV_TRUN_HI 0x01e +#define ZR060_ACV_TRUN_MH 0x01f +#define ZR060_ACV_TRUN_ML 0x020 +#define ZR060_ACV_TRUN_LO 0x021 +#define ZR060_IDR_DEV 0x022 +#define ZR060_IDR_REV 0x023 +#define ZR060_TCR_HI 0x024 +#define ZR060_TCR_LO 0x025 +#define ZR060_VCR 0x030 +#define ZR060_VPR 0x031 +#define ZR060_SR 0x032 +#define ZR060_BCR_Y 0x033 +#define ZR060_BCR_U 0x034 +#define ZR060_BCR_V 0x035 +#define ZR060_SGR_VTOTAL_HI 0x036 +#define ZR060_SGR_VTOTAL_LO 0x037 +#define ZR060_SGR_HTOTAL_HI 0x038 +#define ZR060_SGR_HTOTAL_LO 0x039 +#define ZR060_SGR_VSYNC 0x03a +#define ZR060_SGR_HSYNC 0x03b +#define ZR060_SGR_BVSTART 0x03c +#define ZR060_SGR_BHSTART 0x03d +#define ZR060_SGR_BVEND_HI 0x03e +#define ZR060_SGR_BVEND_LO 0x03f +#define ZR060_SGR_BHEND_HI 0x040 +#define ZR060_SGR_BHEND_LO 0x041 +#define ZR060_AAR_VSTART_HI 0x042 +#define ZR060_AAR_VSTART_LO 0x043 +#define ZR060_AAR_VEND_HI 0x044 +#define ZR060_AAR_VEND_LO 0x045 +#define ZR060_AAR_HSTART_HI 0x046 +#define ZR060_AAR_HSTART_LO 0x047 +#define ZR060_AAR_HEND_HI 0x048 +#define ZR060_AAR_HEND_LO 0x049 +#define ZR060_SWR_VSTART_HI 0x04a +#define ZR060_SWR_VSTART_LO 0x04b +#define ZR060_SWR_VEND_HI 0x04c +#define ZR060_SWR_VEND_LO 0x04d +#define ZR060_SWR_HSTART_HI 0x04e +#define ZR060_SWR_HSTART_LO 0x04f +#define ZR060_SWR_HEND_HI 0x050 +#define ZR060_SWR_HEND_LO 0x051 + +#define ZR060_SOF_IDX 0x060 +#define ZR060_SOS_IDX 0x07a +#define ZR060_DRI_IDX 0x0c0 +#define ZR060_DQT_IDX 0x0cc +#define ZR060_DHT_IDX 0x1d4 +#define ZR060_APP_IDX 0x380 +#define ZR060_COM_IDX 0x3c0 + +/* ZR36060 LOAD register bits */ + +#define ZR060_LOAD_Load (1 << 7) +#define ZR060_LOAD_SyncRst (1 << 0) + +/* ZR36060 Code FIFO Status register bits */ + +#define ZR060_CFSR_Busy (1 << 7) +#define ZR060_CFSR_CBusy (1 << 2) +#define ZR060_CFSR_CFIFO (3 << 0) + +/* ZR36060 Code Interface register */ + +#define ZR060_CIR_Code16 (1 << 7) +#define ZR060_CIR_Endian (1 << 6) +#define ZR060_CIR_CFIS (1 << 2) +#define ZR060_CIR_CodeMstr (1 << 0) + +/* ZR36060 Codec Mode register */ + +#define ZR060_CMR_Comp (1 << 7) +#define ZR060_CMR_ATP (1 << 6) +#define ZR060_CMR_Pass2 (1 << 5) +#define ZR060_CMR_TLM (1 << 4) +#define ZR060_CMR_BRB (1 << 2) +#define ZR060_CMR_FSF (1 << 1) + +/* ZR36060 Markers Enable register */ + +#define ZR060_MER_App (1 << 7) +#define ZR060_MER_Com (1 << 6) +#define ZR060_MER_DRI (1 << 5) +#define ZR060_MER_DQT (1 << 4) +#define ZR060_MER_DHT (1 << 3) + +/* ZR36060 Interrupt Mask register */ + +#define ZR060_IMR_EOAV (1 << 3) +#define ZR060_IMR_EOI (1 << 2) +#define ZR060_IMR_End (1 << 1) +#define ZR060_IMR_DataErr (1 << 0) + +/* ZR36060 Interrupt Status register */ + +#define ZR060_ISR_ProCnt (3 << 6) +#define ZR060_ISR_EOAV (1 << 3) +#define ZR060_ISR_EOI (1 << 2) +#define ZR060_ISR_End (1 << 1) +#define ZR060_ISR_DataErr (1 << 0) + +/* ZR36060 Video Control register */ + +#define ZR060_VCR_Video8 (1 << 7) +#define ZR060_VCR_Range (1 << 6) +#define ZR060_VCR_FIDet (1 << 3) +#define ZR060_VCR_FIVedge (1 << 2) +#define ZR060_VCR_FIExt (1 << 1) +#define ZR060_VCR_SyncMstr (1 << 0) + +/* ZR36060 Video Polarity register */ + +#define ZR060_VPR_VCLKPol (1 << 7) +#define ZR060_VPR_PValPol (1 << 6) +#define ZR060_VPR_PoePol (1 << 5) +#define ZR060_VPR_SImgPol (1 << 4) +#define ZR060_VPR_BLPol (1 << 3) +#define ZR060_VPR_FIPol (1 << 2) +#define ZR060_VPR_HSPol (1 << 1) +#define ZR060_VPR_VSPol (1 << 0) + +/* ZR36060 Scaling register */ + +#define ZR060_SR_VScale (1 << 2) +#define ZR060_SR_HScale2 (1 << 0) +#define ZR060_SR_HScale4 (2 << 0) + +#endif /*fndef ZR36060_H */ diff --git a/drivers/media/video/zr36120.c b/drivers/media/video/zr36120.c new file mode 100644 index 000000000000..c33533155cc7 --- /dev/null +++ b/drivers/media/video/zr36120.c @@ -0,0 +1,2073 @@ +/* + zr36120.c - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <linux/wait.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <linux/video_decoder.h> + +#include <asm/uaccess.h> + +#include "tuner.h" +#include "zr36120.h" +#include "zr36120_mem.h" + +/* mark an required function argument unused - lintism */ +#define UNUSED(x) (void)(x) + +/* sensible default */ +#ifndef CARDTYPE +#define CARDTYPE 0 +#endif + +/* Anybody who uses more than four? */ +#define ZORAN_MAX 4 + +static unsigned int triton1=0; /* triton1 chipset? */ +static unsigned int cardtype[ZORAN_MAX]={ [ 0 ... ZORAN_MAX-1 ] = CARDTYPE }; +static int video_nr = -1; +static int vbi_nr = -1; + +static struct pci_device_id zr36120_pci_tbl[] = { + { PCI_VENDOR_ID_ZORAN,PCI_DEVICE_ID_ZORAN_36120, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, zr36120_pci_tbl); + +MODULE_AUTHOR("Pauline Middelink <middelin@polyware.nl>"); +MODULE_DESCRIPTION("Zoran ZR36120 based framegrabber"); +MODULE_LICENSE("GPL"); + +MODULE_PARM(triton1,"i"); +MODULE_PARM(cardtype,"1-" __MODULE_STRING(ZORAN_MAX) "i"); +MODULE_PARM(video_nr,"i"); +MODULE_PARM(vbi_nr,"i"); + +static int zoran_cards; +static struct zoran zorans[ZORAN_MAX]; + +/* + * the meaning of each element can be found in zr36120.h + * Determining the value of gpdir/gpval can be tricky. The + * best way is to run the card under the original software + * and read the values from the general purpose registers + * 0x28 and 0x2C. How you do that is left as an exercise + * to the impatient reader :) + */ +#define T 1 /* to separate the bools from the ints */ +#define F 0 +static struct tvcard tvcards[] = { + /* reported working by <middelin@polyware.nl> */ +/*0*/ { "Trust Victor II", + 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } }, + /* reported working by <Michael.Paxton@aihw.gov.au> */ +/*1*/ { "Aitech WaveWatcher TV-PCI", + 3, 0, T, F, T, T, 0x7F, 0x80, { 1, TUNER(3), SVHS(6) }, { 0 } }, + /* reported working by ? */ +/*2*/ { "Genius Video Wonder PCI Video Capture Card", + 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } }, + /* reported working by <Pascal.Gabriel@wanadoo.fr> */ +/*3*/ { "Guillemot Maxi-TV PCI", + 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } }, + /* reported working by "Craig Whitmore <lennon@igrin.co.nz> */ +/*4*/ { "Quadrant Buster", + 3, 3, T, F, T, T, 0x7F, 0x80, { SVHS(1), TUNER(2), 3 }, { 1, 2, 3 } }, + /* a debug entry which has all inputs mapped */ +/*5*/ { "ZR36120 based framegrabber (all inputs enabled)", + 6, 0, T, T, T, T, 0x7F, 0x80, { 1, 2, 3, 4, 5, 6 }, { 0 } } +}; +#undef T +#undef F +#define NRTVCARDS (sizeof(tvcards)/sizeof(tvcards[0])) + +#ifdef __sparc__ +#define ENDIANESS 0 +#else +#define ENDIANESS ZORAN_VFEC_LE +#endif + +static struct { const char name[8]; uint mode; uint bpp; } palette2fmt[] = { +/* n/a */ { "n/a", 0, 0 }, +/* GREY */ { "GRAY", 0, 0 }, +/* HI240 */ { "HI240", 0, 0 }, +/* RGB565 */ { "RGB565", ZORAN_VFEC_RGB_RGB565|ENDIANESS, 2 }, +/* RGB24 */ { "RGB24", ZORAN_VFEC_RGB_RGB888|ENDIANESS|ZORAN_VFEC_PACK24, 3 }, +/* RGB32 */ { "RGB32", ZORAN_VFEC_RGB_RGB888|ENDIANESS, 4 }, +/* RGB555 */ { "RGB555", ZORAN_VFEC_RGB_RGB555|ENDIANESS, 2 }, +/* YUV422 */ { "YUV422", ZORAN_VFEC_RGB_YUV422|ENDIANESS, 2 }, +/* YUYV */ { "YUYV", 0, 0 }, +/* UYVY */ { "UYVY", 0, 0 }, +/* YUV420 */ { "YUV420", 0, 0 }, +/* YUV411 */ { "YUV411", 0, 0 }, +/* RAW */ { "RAW", 0, 0 }, +/* YUV422P */ { "YUV422P", 0, 0 }, +/* YUV411P */ { "YUV411P", 0, 0 }}; +#define NRPALETTES (sizeof(palette2fmt)/sizeof(palette2fmt[0])) +#undef ENDIANESS + +/* ----------------------------------------------------------------------- */ +/* ZORAN chipset detector */ +/* shamelessly stolen from bttv.c */ +/* Reason for beeing here: we need to detect if we are running on a */ +/* Triton based chipset, and if so, enable a certain bit */ +/* ----------------------------------------------------------------------- */ +static +void __init handle_chipset(void) +{ + /* Just in case some nut set this to something dangerous */ + if (triton1) + triton1 = ZORAN_VDC_TRICOM; + + if (pci_pci_problems & PCIPCI_TRITON) { + printk(KERN_INFO "zoran: Host bridge 82437FX Triton PIIX\n"); + triton1 = ZORAN_VDC_TRICOM; + } +} + +/* ----------------------------------------------------------------------- */ +/* ZORAN functions */ +/* ----------------------------------------------------------------------- */ + +static void zoran_set_geo(struct zoran* ztv, struct vidinfo* i); + +#if 0 /* unused */ +static +void zoran_dump(struct zoran *ztv) +{ + char str[256]; + char *p=str; /* shut up, gcc! */ + int i; + + for (i=0; i<0x60; i+=4) { + if ((i % 16) == 0) { + if (i) printk("%s\n",str); + p = str; + p+= sprintf(str, KERN_DEBUG " %04x: ",i); + } + p += sprintf(p, "%08x ",zrread(i)); + } +} +#endif /* unused */ + +static +void reap_states(struct zoran* ztv) +{ + /* count frames */ + ztv->fieldnr++; + + /* + * Are we busy at all? + * This depends on if there is a workqueue AND the + * videotransfer is enabled on the chip... + */ + if (ztv->workqueue && (zrread(ZORAN_VDC) & ZORAN_VDC_VIDEN)) + { + struct vidinfo* newitem; + + /* did we get a complete frame? */ + if (zrread(ZORAN_VSTR) & ZORAN_VSTR_GRAB) + return; + +DEBUG(printk(CARD_DEBUG "completed %s at %p\n",CARD,ztv->workqueue->kindof==FBUFFER_GRAB?"grab":"read",ztv->workqueue)); + + /* we are done with this buffer, tell everyone */ + ztv->workqueue->status = FBUFFER_DONE; + ztv->workqueue->fieldnr = ztv->fieldnr; + /* not good, here for BTTV_FIELDNR reasons */ + ztv->lastfieldnr = ztv->fieldnr; + + switch (ztv->workqueue->kindof) { + case FBUFFER_GRAB: + wake_up_interruptible(&ztv->grabq); + break; + case FBUFFER_VBI: + wake_up_interruptible(&ztv->vbiq); + break; + default: + printk(CARD_INFO "somebody killed the workqueue (kindof=%d)!\n",CARD,ztv->workqueue->kindof); + } + + /* item completed, skip to next item in queue */ + write_lock(&ztv->lock); + newitem = ztv->workqueue->next; + ztv->workqueue->next = 0; /* mark completed */ + ztv->workqueue = newitem; + write_unlock(&ztv->lock); + } + + /* + * ok, so it seems we have nothing in progress right now. + * Lets see if we can find some work. + */ + if (ztv->workqueue) + { + struct vidinfo* newitem; +again: + +DEBUG(printk(CARD_DEBUG "starting %s at %p\n",CARD,ztv->workqueue->kindof==FBUFFER_GRAB?"grab":"read",ztv->workqueue)); + + /* loadup the frame settings */ + read_lock(&ztv->lock); + zoran_set_geo(ztv,ztv->workqueue); + read_unlock(&ztv->lock); + + switch (ztv->workqueue->kindof) { + case FBUFFER_GRAB: + case FBUFFER_VBI: + zrand(~ZORAN_OCR_OVLEN, ZORAN_OCR); + zror(ZORAN_VSTR_SNAPSHOT,ZORAN_VSTR); + zror(ZORAN_VDC_VIDEN,ZORAN_VDC); + + /* start single-shot grab */ + zror(ZORAN_VSTR_GRAB, ZORAN_VSTR); + break; + default: + printk(CARD_INFO "what is this doing on the queue? (kindof=%d)\n",CARD,ztv->workqueue->kindof); + write_lock(&ztv->lock); + newitem = ztv->workqueue->next; + ztv->workqueue->next = 0; + ztv->workqueue = newitem; + write_unlock(&ztv->lock); + if (newitem) + goto again; /* yeah, sure.. */ + } + /* bye for now */ + return; + } +DEBUG(printk(CARD_DEBUG "nothing in queue\n",CARD)); + + /* + * What? Even the workqueue is empty? Am i really here + * for nothing? Did i come all that way to... do nothing? + */ + + /* do we need to overlay? */ + if (test_bit(STATE_OVERLAY, &ztv->state)) + { + /* are we already overlaying? */ + if (!(zrread(ZORAN_OCR) & ZORAN_OCR_OVLEN) || + !(zrread(ZORAN_VDC) & ZORAN_VDC_VIDEN)) + { +DEBUG(printk(CARD_DEBUG "starting overlay\n",CARD)); + + read_lock(&ztv->lock); + zoran_set_geo(ztv,&ztv->overinfo); + read_unlock(&ztv->lock); + + zror(ZORAN_OCR_OVLEN, ZORAN_OCR); + zrand(~ZORAN_VSTR_SNAPSHOT,ZORAN_VSTR); + zror(ZORAN_VDC_VIDEN,ZORAN_VDC); + } + + /* + * leave overlaying on, but turn interrupts off. + */ + zrand(~ZORAN_ICR_EN,ZORAN_ICR); + return; + } + + /* do we have any VBI idle time processing? */ + if (test_bit(STATE_VBI, &ztv->state)) + { + struct vidinfo* item; + struct vidinfo* lastitem; + + /* protect the workqueue */ + write_lock(&ztv->lock); + lastitem = ztv->workqueue; + if (lastitem) + while (lastitem->next) lastitem = lastitem->next; + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + if (item->next == 0 && item->status == FBUFFER_FREE) + { +DEBUG(printk(CARD_DEBUG "%p added to queue\n",CARD,item)); + item->status = FBUFFER_BUSY; + if (!lastitem) + ztv->workqueue = item; + else + lastitem->next = item; + lastitem = item; + } + write_unlock(&ztv->lock); + if (ztv->workqueue) + goto again; /* hey, _i_ graduated :) */ + } + + /* + * Then we must be realy IDLE + */ +DEBUG(printk(CARD_DEBUG "turning off\n",CARD)); + /* nothing further to do, disable DMA and further IRQs */ + zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC); + zrand(~ZORAN_ICR_EN,ZORAN_ICR); +} + +static +void zoran_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + u32 stat,estat; + int count = 0; + struct zoran *ztv = dev_id; + + UNUSED(irq); UNUSED(regs); + for (;;) { + /* get/clear interrupt status bits */ + stat=zrread(ZORAN_ISR); + estat=stat & zrread(ZORAN_ICR); + if (!estat) + return; + zrwrite(estat,ZORAN_ISR); + IDEBUG(printk(CARD_DEBUG "estat %08x\n",CARD,estat)); + IDEBUG(printk(CARD_DEBUG " stat %08x\n",CARD,stat)); + + if (estat & ZORAN_ISR_CODE) + { + IDEBUG(printk(CARD_DEBUG "CodReplIRQ\n",CARD)); + } + if (estat & ZORAN_ISR_GIRQ0) + { + IDEBUG(printk(CARD_DEBUG "GIRQ0\n",CARD)); + if (!ztv->card->usegirq1) + reap_states(ztv); + } + if (estat & ZORAN_ISR_GIRQ1) + { + IDEBUG(printk(CARD_DEBUG "GIRQ1\n",CARD)); + if (ztv->card->usegirq1) + reap_states(ztv); + } + + count++; + if (count > 10) + printk(CARD_ERR "irq loop %d (%x)\n",CARD,count,estat); + if (count > 20) + { + zrwrite(0, ZORAN_ICR); + printk(CARD_ERR "IRQ lockup, cleared int mask\n",CARD); + } + } +} + +static +int zoran_muxsel(struct zoran* ztv, int channel, int norm) +{ + int rv; + + /* set the new video norm */ + rv = i2c_control_device(&(ztv->i2c), I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &norm); + if (rv) + return rv; + ztv->norm = norm; + + /* map the given channel to the cards decoder's channel */ + channel = ztv->card->video_mux[channel] & CHANNEL_MASK; + + /* set the new channel */ + rv = i2c_control_device(&(ztv->i2c), I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &channel); + return rv; +} + +/* Tell the interrupt handler what to to. */ +static +void zoran_cap(struct zoran* ztv, int on) +{ +DEBUG(printk(CARD_DEBUG "zoran_cap(%d) state=%x\n",CARD,on,ztv->state)); + + if (on) { + ztv->running = 1; + + /* + * turn interrupts (back) on. The DMA will be enabled + * inside the irq handler when it detects a restart. + */ + zror(ZORAN_ICR_EN,ZORAN_ICR); + } + else { + /* + * turn both interrupts and DMA off + */ + zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC); + zrand(~ZORAN_ICR_EN,ZORAN_ICR); + + ztv->running = 0; + } +} + +static ulong dmask[] = { + 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFC, 0xFFFFFFF8, + 0xFFFFFFF0, 0xFFFFFFE0, 0xFFFFFFC0, 0xFFFFFF80, + 0xFFFFFF00, 0xFFFFFE00, 0xFFFFFC00, 0xFFFFF800, + 0xFFFFF000, 0xFFFFE000, 0xFFFFC000, 0xFFFF8000, + 0xFFFF0000, 0xFFFE0000, 0xFFFC0000, 0xFFF80000, + 0xFFF00000, 0xFFE00000, 0xFFC00000, 0xFF800000, + 0xFF000000, 0xFE000000, 0xFC000000, 0xF8000000, + 0xF0000000, 0xE0000000, 0xC0000000, 0x80000000 +}; + +static +void zoran_built_overlay(struct zoran* ztv, int count, struct video_clip *vcp) +{ + ulong* mtop; + int ystep = (ztv->vidXshift + ztv->vidWidth+31)/32; /* next DWORD */ + int i; + +DEBUG(printk(KERN_DEBUG " overlay at %p, ystep=%d, clips=%d\n",ztv->overinfo.overlay,ystep,count)); + + for (i=0; i<count; i++) { + struct video_clip *vp = vcp+i; + UNUSED(vp); +DEBUG(printk(KERN_DEBUG " %d: clip(%d,%d,%d,%d)\n", i,vp->x,vp->y,vp->width,vp->height)); + } + + /* + * activate the visible portion of the screen + * Note we take some shortcuts here, because we + * know the width can never be < 32. (I.e. a DWORD) + * We also assume the overlay starts somewhere in + * the FIRST dword. + */ + { + int start = ztv->vidXshift; + ulong firstd = dmask[start]; + ulong lastd = ~dmask[(start + ztv->overinfo.w) & 31]; + mtop = ztv->overinfo.overlay; + for (i=0; i<ztv->overinfo.h; i++) { + int w = ztv->vidWidth; + ulong* line = mtop; + if (start & 31) { + *line++ = firstd; + w -= 32-(start&31); + } + memset(line, ~0, w/8); + if (w & 31) + line[w/32] = lastd; + mtop += ystep; + } + } + + /* process clipping regions */ + for (i=0; i<count; i++) { + int h; + if (vcp->x < 0 || (uint)vcp->x > ztv->overinfo.w || + vcp->y < 0 || vcp->y > ztv->overinfo.h || + vcp->width < 0 || (uint)(vcp->x+vcp->width) > ztv->overinfo.w || + vcp->height < 0 || (vcp->y+vcp->height) > ztv->overinfo.h) + { + DEBUG(printk(CARD_DEBUG "invalid clipzone (%d,%d,%d,%d) not in (0,0,%d,%d), adapting\n",CARD,vcp->x,vcp->y,vcp->width,vcp->height,ztv->overinfo.w,ztv->overinfo.h)); + if (vcp->x < 0) vcp->x = 0; + if ((uint)vcp->x > ztv->overinfo.w) vcp->x = ztv->overinfo.w; + if (vcp->y < 0) vcp->y = 0; + if (vcp->y > ztv->overinfo.h) vcp->y = ztv->overinfo.h; + if (vcp->width < 0) vcp->width = 0; + if ((uint)(vcp->x+vcp->width) > ztv->overinfo.w) vcp->width = ztv->overinfo.w - vcp->x; + if (vcp->height < 0) vcp->height = 0; + if (vcp->y+vcp->height > ztv->overinfo.h) vcp->height = ztv->overinfo.h - vcp->y; +// continue; + } + + mtop = &ztv->overinfo.overlay[vcp->y*ystep]; + for (h=0; h<=vcp->height; h++) { + int w; + int x = ztv->vidXshift + vcp->x; + for (w=0; w<=vcp->width; w++) { + clear_bit(x&31, &mtop[x/32]); + x++; + } + mtop += ystep; + } + ++vcp; + } + + mtop = ztv->overinfo.overlay; + zrwrite(virt_to_bus(mtop), ZORAN_MTOP); + zrwrite(virt_to_bus(mtop+ystep), ZORAN_MBOT); + zraor((ztv->vidInterlace*ystep)<<0,~ZORAN_OCR_MASKSTRIDE,ZORAN_OCR); +} + +struct tvnorm +{ + u16 Wt, Wa, Ht, Ha, HStart, VStart; +}; + +static struct tvnorm tvnorms[] = { + /* PAL-BDGHI */ +/* { 864, 720, 625, 576, 131, 21 },*/ +/*00*/ { 864, 768, 625, 576, 81, 17 }, + /* NTSC */ +/*01*/ { 858, 720, 525, 480, 121, 10 }, + /* SECAM */ +/*02*/ { 864, 720, 625, 576, 131, 21 }, + /* BW50 */ +/*03*/ { 864, 720, 625, 576, 131, 21 }, + /* BW60 */ +/*04*/ { 858, 720, 525, 480, 121, 10 } +}; +#define TVNORMS (sizeof(tvnorms)/sizeof(tvnorm)) + +/* + * Program the chip for a setup as described in the vidinfo struct. + * + * Side-effects: calculates vidXshift, vidInterlace, + * vidHeight, vidWidth which are used in a later stage + * to calculate the overlay mask + * + * This is an internal function, as such it does not check the + * validity of the struct members... Spectaculair crashes will + * follow /very/ quick when you're wrong and the chip right :) + */ +static +void zoran_set_geo(struct zoran* ztv, struct vidinfo* i) +{ + ulong top, bot; + int stride; + int winWidth, winHeight; + int maxWidth, maxHeight, maxXOffset, maxYOffset; + long vfec; + +DEBUG(printk(CARD_DEBUG "set_geo(rect=(%d,%d,%d,%d), norm=%d, format=%d, bpp=%d, bpl=%d, busadr=%lx, overlay=%p)\n",CARD,i->x,i->y,i->w,i->h,ztv->norm,i->format,i->bpp,i->bpl,i->busadr,i->overlay)); + + /* + * make sure the DMA transfers are inhibited during our + * reprogramming of the chip + */ + zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC); + + maxWidth = tvnorms[ztv->norm].Wa; + maxHeight = tvnorms[ztv->norm].Ha/2; + maxXOffset = tvnorms[ztv->norm].HStart; + maxYOffset = tvnorms[ztv->norm].VStart; + + /* setup vfec register (keep ExtFl,TopField and VCLKPol settings) */ + vfec = (zrread(ZORAN_VFEC) & (ZORAN_VFEC_EXTFL|ZORAN_VFEC_TOPFIELD|ZORAN_VFEC_VCLKPOL)) | + (palette2fmt[i->format].mode & (ZORAN_VFEC_RGB|ZORAN_VFEC_ERRDIF|ZORAN_VFEC_LE|ZORAN_VFEC_PACK24)); + + /* + * Set top, bottom ptrs. Since these must be DWORD aligned, + * possible adjust the x and the width of the window. + * so the endposition stay the same. The vidXshift will make + * sure we are not writing pixels before the requested x. + */ + ztv->vidXshift = 0; + winWidth = i->w; + if (winWidth < 0) + winWidth = -winWidth; + top = i->busadr + i->x*i->bpp + i->y*i->bpl; + if (top & 3) { + ztv->vidXshift = (top & 3) / i->bpp; + winWidth += ztv->vidXshift; + DEBUG(printk(KERN_DEBUG " window-x shifted %d pixels left\n",ztv->vidXshift)); + top &= ~3; + } + + /* + * bottom points to next frame but in interleaved mode we want + * to 'mix' the 2 frames to one capture, so 'bot' points to one + * (physical) line below the top line. + */ + bot = top + i->bpl; + zrwrite(top,ZORAN_VTOP); + zrwrite(bot,ZORAN_VBOT); + + /* + * Make sure the winWidth is DWORD aligned too, + * thereby automaticly making sure the stride to the + * next line is DWORD aligned too (as required by spec). + */ + if ((winWidth*i->bpp) & 3) { +DEBUG(printk(KERN_DEBUG " window-width enlarged by %d pixels\n",(winWidth*i->bpp) & 3)); + winWidth += (winWidth*i->bpp) & 3; + } + + /* determine the DispMode and stride */ + if (i->h >= 0 && i->h <= maxHeight) { + /* single frame grab suffices for this height. */ + vfec |= ZORAN_VFEC_DISPMOD; + ztv->vidInterlace = 0; + stride = i->bpl - (winWidth*i->bpp); + winHeight = i->h; + } + else { + /* interleaving needed for this height */ + ztv->vidInterlace = 1; + stride = i->bpl*2 - (winWidth*i->bpp); + winHeight = i->h/2; + } + if (winHeight < 0) /* can happen for VBI! */ + winHeight = -winHeight; + + /* safety net, sometimes bpl is too short??? */ + if (stride<0) { +DEBUG(printk(CARD_DEBUG "WARNING stride = %d\n",CARD,stride)); + stride = 0; + } + + zraor((winHeight<<12)|(winWidth<<0),~(ZORAN_VDC_VIDWINHT|ZORAN_VDC_VIDWINWID), ZORAN_VDC); + zraor(stride<<16,~ZORAN_VSTR_DISPSTRIDE,ZORAN_VSTR); + + /* remember vidWidth, vidHeight for overlay calculations */ + ztv->vidWidth = winWidth; + ztv->vidHeight = winHeight; +DEBUG(printk(KERN_DEBUG " top=%08lx, bottom=%08lx\n",top,bot)); +DEBUG(printk(KERN_DEBUG " winWidth=%d, winHeight=%d\n",winWidth,winHeight)); +DEBUG(printk(KERN_DEBUG " maxWidth=%d, maxHeight=%d\n",maxWidth,maxHeight)); +DEBUG(printk(KERN_DEBUG " stride=%d\n",stride)); + + /* + * determine horizontal scales and crops + */ + if (i->w < 0) { + int Hstart = 1; + int Hend = Hstart + winWidth; +DEBUG(printk(KERN_DEBUG " Y: scale=0, start=%d, end=%d\n", Hstart, Hend)); + zraor((Hstart<<10)|(Hend<<0),~(ZORAN_VFEH_HSTART|ZORAN_VFEH_HEND),ZORAN_VFEH); + } + else { + int Wa = maxWidth; + int X = (winWidth*64+Wa-1)/Wa; + int We = winWidth*64/X; + int HorDcm = 64-X; + int hcrop1 = 2*(Wa-We)/4; + /* + * BUGFIX: Juha Nurmela <junki@qn-lpr2-165.quicknet.inet.fi> + * found the solution to the color phase shift. + * See ChangeLog for the full explanation) + */ + int Hstart = (maxXOffset + hcrop1) | 1; + int Hend = Hstart + We - 1; + +DEBUG(printk(KERN_DEBUG " X: scale=%d, start=%d, end=%d\n", HorDcm, Hstart, Hend)); + + zraor((Hstart<<10)|(Hend<<0),~(ZORAN_VFEH_HSTART|ZORAN_VFEH_HEND),ZORAN_VFEH); + vfec |= HorDcm<<14; + + if (HorDcm<16) + vfec |= ZORAN_VFEC_HFILTER_1; /* no filter */ + else if (HorDcm<32) + vfec |= ZORAN_VFEC_HFILTER_3; /* 3 tap filter */ + else if (HorDcm<48) + vfec |= ZORAN_VFEC_HFILTER_4; /* 4 tap filter */ + else vfec |= ZORAN_VFEC_HFILTER_5; /* 5 tap filter */ + } + + /* + * Determine vertical scales and crops + * + * when height is negative, we want to read starting at line 0 + * One day someone might need access to these lines... + */ + if (i->h < 0) { + int Vstart = 0; + int Vend = Vstart + winHeight; +DEBUG(printk(KERN_DEBUG " Y: scale=0, start=%d, end=%d\n", Vstart, Vend)); + zraor((Vstart<<10)|(Vend<<0),~(ZORAN_VFEV_VSTART|ZORAN_VFEV_VEND),ZORAN_VFEV); + } + else { + int Ha = maxHeight; + int Y = (winHeight*64+Ha-1)/Ha; + int He = winHeight*64/Y; + int VerDcm = 64-Y; + int vcrop1 = 2*(Ha-He)/4; + int Vstart = maxYOffset + vcrop1; + int Vend = Vstart + He - 1; + +DEBUG(printk(KERN_DEBUG " Y: scale=%d, start=%d, end=%d\n", VerDcm, Vstart, Vend)); + zraor((Vstart<<10)|(Vend<<0),~(ZORAN_VFEV_VSTART|ZORAN_VFEV_VEND),ZORAN_VFEV); + vfec |= VerDcm<<8; + } + +DEBUG(printk(KERN_DEBUG " F: format=%d(=%s)\n",i->format,palette2fmt[i->format].name)); + + /* setup the requested format */ + zrwrite(vfec, ZORAN_VFEC); +} + +static +void zoran_common_open(struct zoran* ztv, int flags) +{ + UNUSED(flags); + + /* already opened? */ + if (ztv->users++ != 0) + return; + + /* unmute audio */ + /* /what/ audio? */ + + ztv->state = 0; + + /* setup the encoder to the initial values */ + ztv->picture.colour=254<<7; + ztv->picture.brightness=128<<8; + ztv->picture.hue=128<<8; + ztv->picture.contrast=216<<7; + i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &ztv->picture); + + /* default to the composite input since my camera is there */ + zoran_muxsel(ztv, 0, VIDEO_MODE_PAL); +} + +static +void zoran_common_close(struct zoran* ztv) +{ + if (--ztv->users != 0) + return; + + /* mute audio */ + /* /what/ audio? */ + + /* stop the chip */ + zoran_cap(ztv, 0); +} + +/* + * Open a zoran card. Right now the flags are just a hack + */ +static int zoran_open(struct video_device *dev, int flags) +{ + struct zoran *ztv = (struct zoran*)dev; + struct vidinfo* item; + char* pos; + + DEBUG(printk(CARD_DEBUG "open(dev,%d)\n",CARD,flags)); + + /********************************************* + * We really should be doing lazy allocing... + *********************************************/ + /* allocate a frame buffer */ + if (!ztv->fbuffer) + ztv->fbuffer = bmalloc(ZORAN_MAX_FBUFSIZE); + if (!ztv->fbuffer) { + /* could not get a buffer, bail out */ + return -ENOBUFS; + } + /* at this time we _always_ have a framebuffer */ + memset(ztv->fbuffer,0,ZORAN_MAX_FBUFSIZE); + + if (!ztv->overinfo.overlay) + ztv->overinfo.overlay = kmalloc(1024*1024/8, GFP_KERNEL); + if (!ztv->overinfo.overlay) { + /* could not get an overlay buffer, bail out */ + bfree(ztv->fbuffer, ZORAN_MAX_FBUFSIZE); + return -ENOBUFS; + } + /* at this time we _always_ have a overlay */ + + /* clear buffer status, and give them a DMAable address */ + pos = ztv->fbuffer; + for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++) + { + item->status = FBUFFER_FREE; + item->memadr = pos; + item->busadr = virt_to_bus(pos); + pos += ZORAN_MAX_FBUFFER; + } + + /* do the common part of all open's */ + zoran_common_open(ztv, flags); + + return 0; +} + +static +void zoran_close(struct video_device* dev) +{ + struct zoran *ztv = (struct zoran*)dev; + + DEBUG(printk(CARD_DEBUG "close(dev)\n",CARD)); + + /* driver specific closure */ + clear_bit(STATE_OVERLAY, &ztv->state); + + zoran_common_close(ztv); + + /* + * This is sucky but right now I can't find a good way to + * be sure its safe to free the buffer. We wait 5-6 fields + * which is more than sufficient to be sure. + */ + msleep(100); /* Wait 1/10th of a second */ + + /* free the allocated framebuffer */ + if (ztv->fbuffer) + bfree( ztv->fbuffer, ZORAN_MAX_FBUFSIZE ); + ztv->fbuffer = 0; + if (ztv->overinfo.overlay) + kfree( ztv->overinfo.overlay ); + ztv->overinfo.overlay = 0; + +} + +/* + * This read function could be used reentrant in a SMP situation. + * + * This is made possible by the spinlock which is kept till we + * found and marked a buffer for our own use. The lock must + * be released as soon as possible to prevent lock contention. + */ +static +long zoran_read(struct video_device* dev, char* buf, unsigned long count, int nonblock) +{ + struct zoran *ztv = (struct zoran*)dev; + unsigned long max; + struct vidinfo* unused = 0; + struct vidinfo* done = 0; + + DEBUG(printk(CARD_DEBUG "zoran_read(%p,%ld,%d)\n",CARD,buf,count,nonblock)); + + /* find ourself a free or completed buffer */ + for (;;) { + struct vidinfo* item; + + write_lock_irq(&ztv->lock); + for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++) + { + if (!unused && item->status == FBUFFER_FREE) + unused = item; + if (!done && item->status == FBUFFER_DONE) + done = item; + } + if (done || unused) + break; + + /* no more free buffers, wait for them. */ + write_unlock_irq(&ztv->lock); + if (nonblock) + return -EWOULDBLOCK; + interruptible_sleep_on(&ztv->grabq); + if (signal_pending(current)) + return -EINTR; + } + + /* Do we have 'ready' data? */ + if (!done) { + /* no? than this will take a while... */ + if (nonblock) { + write_unlock_irq(&ztv->lock); + return -EWOULDBLOCK; + } + + /* mark the unused buffer as wanted */ + unused->status = FBUFFER_BUSY; + unused->w = 320; + unused->h = 240; + unused->format = VIDEO_PALETTE_RGB24; + unused->bpp = palette2fmt[unused->format].bpp; + unused->bpl = unused->w * unused->bpp; + unused->next = 0; + { /* add to tail of queue */ + struct vidinfo* oldframe = ztv->workqueue; + if (!oldframe) ztv->workqueue = unused; + else { + while (oldframe->next) oldframe = oldframe->next; + oldframe->next = unused; + } + } + write_unlock_irq(&ztv->lock); + + /* tell the state machine we want it filled /NOW/ */ + zoran_cap(ztv, 1); + + /* wait till this buffer gets grabbed */ + wait_event_interruptible(ztv->grabq, + (unused->status != FBUFFER_BUSY)); + /* see if a signal did it */ + if (signal_pending(current)) + return -EINTR; + done = unused; + } + else + write_unlock_irq(&ztv->lock); + + /* Yes! we got data! */ + max = done->bpl * done->h; + if (count > max) + count = max; + if (copy_to_user((void*)buf, done->memadr, count)) + count = -EFAULT; + + /* keep the engine running */ + done->status = FBUFFER_FREE; +// zoran_cap(ztv,1); + + /* tell listeners this buffer became free */ + wake_up_interruptible(&ztv->grabq); + + /* goodbye */ + DEBUG(printk(CARD_DEBUG "zoran_read() returns %lu\n",CARD,count)); + return count; +} + +static +long zoran_write(struct video_device* dev, const char* buf, unsigned long count, int nonblock) +{ + struct zoran *ztv = (struct zoran *)dev; + UNUSED(ztv); UNUSED(dev); UNUSED(buf); UNUSED(count); UNUSED(nonblock); + DEBUG(printk(CARD_DEBUG "zoran_write\n",CARD)); + return -EINVAL; +} + +static +unsigned int zoran_poll(struct video_device *dev, struct file *file, poll_table *wait) +{ + struct zoran *ztv = (struct zoran *)dev; + struct vidinfo* item; + unsigned int mask = 0; + + poll_wait(file, &ztv->grabq, wait); + + for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++) + if (item->status == FBUFFER_DONE) + { + mask |= (POLLIN | POLLRDNORM); + break; + } + + DEBUG(printk(CARD_DEBUG "zoran_poll()=%x\n",CARD,mask)); + + return mask; +} + +/* append a new clipregion to the vector of video_clips */ +static +void new_clip(struct video_window* vw, struct video_clip* vcp, int x, int y, int w, int h) +{ + vcp[vw->clipcount].x = x; + vcp[vw->clipcount].y = y; + vcp[vw->clipcount].width = w; + vcp[vw->clipcount].height = h; + vw->clipcount++; +} + +static +int zoran_ioctl(struct video_device* dev, unsigned int cmd, void *arg) +{ + struct zoran* ztv = (struct zoran*)dev; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability c; + DEBUG(printk(CARD_DEBUG "VIDIOCGCAP\n",CARD)); + + strcpy(c.name,ztv->video_dev.name); + c.type = VID_TYPE_CAPTURE| + VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING| + VID_TYPE_FRAMERAM| + VID_TYPE_SCALES; + if (ztv->have_tuner) + c.type |= VID_TYPE_TUNER; + if (ztv->have_decoder) { + c.channels = ztv->card->video_inputs; + c.audios = ztv->card->audio_inputs; + } else + /* no decoder -> no channels */ + c.channels = c.audios = 0; + c.maxwidth = 768; + c.maxheight = 576; + c.minwidth = 32; + c.minheight = 32; + if (copy_to_user(arg,&c,sizeof(c))) + return -EFAULT; + break; + } + + case VIDIOCGCHAN: + { + struct video_channel v; + int mux; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCGCHAN(%d)\n",CARD,v.channel)); + v.flags=VIDEO_VC_AUDIO +#ifdef VIDEO_VC_NORM + |VIDEO_VC_NORM +#endif + ; + v.tuners=0; + v.type=VIDEO_TYPE_CAMERA; +#ifdef I_EXPECT_POSSIBLE_NORMS_IN_THE_API + v.norm=VIDEO_MODE_PAL| + VIDEO_MODE_NTSC| + VIDEO_MODE_SECAM; +#else + v.norm=VIDEO_MODE_PAL; +#endif + /* too many inputs? no decoder -> no channels */ + if (!ztv->have_decoder || v.channel < 0 || v.channel >= ztv->card->video_inputs) + return -EINVAL; + + /* now determine the name of the channel */ + mux = ztv->card->video_mux[v.channel]; + if (mux & IS_TUNER) { + /* lets assume only one tuner, yes? */ + strcpy(v.name,"Television"); + v.type = VIDEO_TYPE_TV; + if (ztv->have_tuner) { + v.flags |= VIDEO_VC_TUNER; + v.tuners = 1; + } + } + else if (mux & IS_SVHS) + sprintf(v.name,"S-Video-%d",v.channel); + else + sprintf(v.name,"CVBS-%d",v.channel); + + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + break; + } + case VIDIOCSCHAN: + { /* set video channel */ + struct video_channel v; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSCHAN(%d,%d)\n",CARD,v.channel,v.norm)); + + /* too many inputs? no decoder -> no channels */ + if (!ztv->have_decoder || v.channel >= ztv->card->video_inputs || v.channel < 0) + return -EINVAL; + + if (v.norm != VIDEO_MODE_PAL && + v.norm != VIDEO_MODE_NTSC && + v.norm != VIDEO_MODE_SECAM && + v.norm != VIDEO_MODE_AUTO) + return -EOPNOTSUPP; + + /* make it happen, nr1! */ + return zoran_muxsel(ztv,v.channel,v.norm); + } + + case VIDIOCGTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCGTUNER(%d)\n",CARD,v.tuner)); + + /* Only no or one tuner for now */ + if (!ztv->have_tuner || v.tuner) + return -EINVAL; + + strcpy(v.name,"Television"); + v.rangelow = 0; + v.rangehigh = ~0; + v.flags = VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + v.mode = ztv->norm; + v.signal = 0xFFFF; /* unknown */ + + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + break; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSTUNER(%d,%d)\n",CARD,v.tuner,v.mode)); + + /* Only no or one tuner for now */ + if (!ztv->have_tuner || v.tuner) + return -EINVAL; + + /* and it only has certain valid modes */ + if( v.mode != VIDEO_MODE_PAL && + v.mode != VIDEO_MODE_NTSC && + v.mode != VIDEO_MODE_SECAM) + return -EOPNOTSUPP; + + /* engage! */ + return zoran_muxsel(ztv,v.tuner,v.mode); + } + + case VIDIOCGPICT: + { + struct video_picture p = ztv->picture; + DEBUG(printk(CARD_DEBUG "VIDIOCGPICT\n",CARD)); + p.depth = ztv->depth; + switch (p.depth) { + case 8: p.palette=VIDEO_PALETTE_YUV422; + break; + case 15: p.palette=VIDEO_PALETTE_RGB555; + break; + case 16: p.palette=VIDEO_PALETTE_RGB565; + break; + case 24: p.palette=VIDEO_PALETTE_RGB24; + break; + case 32: p.palette=VIDEO_PALETTE_RGB32; + break; + } + if (copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + break; + } + case VIDIOCSPICT: + { + struct video_picture p; + if (copy_from_user(&p, arg,sizeof(p))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSPICT(%d,%d,%d,%d,%d,%d,%d)\n",CARD,p.brightness,p.hue,p.colour,p.contrast,p.whiteness,p.depth,p.palette)); + + /* depth must match with framebuffer */ + if (p.depth != ztv->depth) + return -EINVAL; + + /* check if palette matches this bpp */ + if (p.palette>NRPALETTES || + palette2fmt[p.palette].bpp != ztv->overinfo.bpp) + return -EINVAL; + + write_lock_irq(&ztv->lock); + ztv->overinfo.format = p.palette; + ztv->picture = p; + write_unlock_irq(&ztv->lock); + + /* tell the decoder */ + i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &p); + break; + } + + case VIDIOCGWIN: + { + struct video_window vw; + DEBUG(printk(CARD_DEBUG "VIDIOCGWIN\n",CARD)); + read_lock(&ztv->lock); + vw.x = ztv->overinfo.x; + vw.y = ztv->overinfo.y; + vw.width = ztv->overinfo.w; + vw.height = ztv->overinfo.h; + vw.chromakey= 0; + vw.flags = 0; + if (ztv->vidInterlace) + vw.flags|=VIDEO_WINDOW_INTERLACE; + read_unlock(&ztv->lock); + if (copy_to_user(arg,&vw,sizeof(vw))) + return -EFAULT; + break; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip *vcp; + int on; + if (copy_from_user(&vw,arg,sizeof(vw))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSWIN(%d,%d,%d,%d,%x,%d)\n",CARD,vw.x,vw.y,vw.width,vw.height,vw.flags,vw.clipcount)); + + if (vw.flags) + return -EINVAL; + + if (vw.clipcount <0 || vw.clipcount>256) + return -EDOM; /* Too many! */ + + /* + * Do any clips. + */ + vcp = vmalloc(sizeof(struct video_clip)*(vw.clipcount+4)); + if (vcp==NULL) + return -ENOMEM; + if (vw.clipcount && copy_from_user(vcp,vw.clips,sizeof(struct video_clip)*vw.clipcount)) { + vfree(vcp); + return -EFAULT; + } + + on = ztv->running; + if (on) + zoran_cap(ztv, 0); + + /* + * strange, it seems xawtv sometimes calls us with 0 + * width and/or height. Ignore these values + */ + if (vw.x == 0) + vw.x = ztv->overinfo.x; + if (vw.y == 0) + vw.y = ztv->overinfo.y; + + /* by now we are committed to the new data... */ + write_lock_irq(&ztv->lock); + ztv->overinfo.x = vw.x; + ztv->overinfo.y = vw.y; + ztv->overinfo.w = vw.width; + ztv->overinfo.h = vw.height; + write_unlock_irq(&ztv->lock); + + /* + * Impose display clips + */ + if (vw.x+vw.width > ztv->swidth) + new_clip(&vw, vcp, ztv->swidth-vw.x, 0, vw.width-1, vw.height-1); + if (vw.y+vw.height > ztv->sheight) + new_clip(&vw, vcp, 0, ztv->sheight-vw.y, vw.width-1, vw.height-1); + + /* built the requested clipping zones */ + zoran_set_geo(ztv, &ztv->overinfo); + zoran_built_overlay(ztv, vw.clipcount, vcp); + vfree(vcp); + + /* if we were on, restart the video engine */ + if (on) + zoran_cap(ztv, 1); + break; + } + + case VIDIOCCAPTURE: + { + int v; + if (get_user(v, (int *)arg)) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCCAPTURE(%d)\n",CARD,v)); + + if (v==0) { + clear_bit(STATE_OVERLAY, &ztv->state); + zoran_cap(ztv, 1); + } + else { + /* is VIDIOCSFBUF, VIDIOCSWIN done? */ + if (ztv->overinfo.busadr==0 || ztv->overinfo.w==0 || ztv->overinfo.h==0) + return -EINVAL; + + set_bit(STATE_OVERLAY, &ztv->state); + zoran_cap(ztv, 1); + } + break; + } + + case VIDIOCGFBUF: + { + struct video_buffer v; + DEBUG(printk(CARD_DEBUG "VIDIOCGFBUF\n",CARD)); + read_lock(&ztv->lock); + v.base = (void *)ztv->overinfo.busadr; + v.height = ztv->sheight; + v.width = ztv->swidth; + v.depth = ztv->depth; + v.bytesperline = ztv->overinfo.bpl; + read_unlock(&ztv->lock); + if(copy_to_user(arg, &v,sizeof(v))) + return -EFAULT; + break; + } + case VIDIOCSFBUF: + { + struct video_buffer v; + if(!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSFBUF(%p,%d,%d,%d,%d)\n",CARD,v.base, v.width,v.height,v.depth,v.bytesperline)); + + if (v.depth!=15 && v.depth!=16 && v.depth!=24 && v.depth!=32) + return -EINVAL; + if (v.bytesperline<1) + return -EINVAL; + if (ztv->running) + return -EBUSY; + write_lock_irq(&ztv->lock); + ztv->overinfo.busadr = (ulong)v.base; + ztv->sheight = v.height; + ztv->swidth = v.width; + ztv->depth = v.depth; /* bits per pixel */ + ztv->overinfo.bpp = ((v.depth+1)&0x38)/8;/* bytes per pixel */ + ztv->overinfo.bpl = v.bytesperline; /* bytes per line */ + write_unlock_irq(&ztv->lock); + break; + } + + case VIDIOCKEY: + { + /* Will be handled higher up .. */ + break; + } + + case VIDIOCSYNC: + { + int i; + if (get_user(i, (int *) arg)) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDEOCSYNC(%d)\n",CARD,i)); + if (i<0 || i>ZORAN_MAX_FBUFFERS) + return -EINVAL; + switch (ztv->grabinfo[i].status) { + case FBUFFER_FREE: + return -EINVAL; + case FBUFFER_BUSY: + /* wait till this buffer gets grabbed */ + wait_event_interruptible(ztv->grabq, + (ztv->grabinfo[i].status != FBUFFER_BUSY)); + /* see if a signal did it */ + if (signal_pending(current)) + return -EINTR; + /* don't fall through; a DONE buffer is not UNUSED */ + break; + case FBUFFER_DONE: + ztv->grabinfo[i].status = FBUFFER_FREE; + /* tell ppl we have a spare buffer */ + wake_up_interruptible(&ztv->grabq); + break; + } + DEBUG(printk(CARD_DEBUG "VIDEOCSYNC(%d) returns\n",CARD,i)); + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + struct vidinfo* frame; + if (copy_from_user(&vm,arg,sizeof(vm))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCMCAPTURE(%d,(%d,%d),%d)\n",CARD,vm.frame,vm.width,vm.height,vm.format)); + if (vm.frame<0 || vm.frame>ZORAN_MAX_FBUFFERS || + vm.width<32 || vm.width>768 || + vm.height<32 || vm.height>576 || + vm.format>NRPALETTES || + palette2fmt[vm.format].mode == 0) + return -EINVAL; + + /* we are allowed to take over UNUSED and DONE buffers */ + frame = &ztv->grabinfo[vm.frame]; + if (frame->status == FBUFFER_BUSY) + return -EBUSY; + + /* setup the other parameters if they are given */ + write_lock_irq(&ztv->lock); + frame->w = vm.width; + frame->h = vm.height; + frame->format = vm.format; + frame->bpp = palette2fmt[frame->format].bpp; + frame->bpl = frame->w*frame->bpp; + frame->status = FBUFFER_BUSY; + frame->next = 0; + { /* add to tail of queue */ + struct vidinfo* oldframe = ztv->workqueue; + if (!oldframe) ztv->workqueue = frame; + else { + while (oldframe->next) oldframe = oldframe->next; + oldframe->next = frame; + } + } + write_unlock_irq(&ztv->lock); + zoran_cap(ztv, 1); + break; + } + + case VIDIOCGMBUF: + { + struct video_mbuf mb; + int i; + DEBUG(printk(CARD_DEBUG "VIDIOCGMBUF\n",CARD)); + mb.size = ZORAN_MAX_FBUFSIZE; + mb.frames = ZORAN_MAX_FBUFFERS; + for (i=0; i<ZORAN_MAX_FBUFFERS; i++) + mb.offsets[i] = i*ZORAN_MAX_FBUFFER; + if(copy_to_user(arg, &mb,sizeof(mb))) + return -EFAULT; + break; + } + + case VIDIOCGUNIT: + { + struct video_unit vu; + DEBUG(printk(CARD_DEBUG "VIDIOCGUNIT\n",CARD)); + vu.video = ztv->video_dev.minor; + vu.vbi = ztv->vbi_dev.minor; + vu.radio = VIDEO_NO_UNIT; + vu.audio = VIDEO_NO_UNIT; + vu.teletext = VIDEO_NO_UNIT; + if(copy_to_user(arg, &vu,sizeof(vu))) + return -EFAULT; + break; + } + + case VIDIOCGFREQ: + { + unsigned long v = ztv->tuner_freq; + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCGFREQ\n",CARD)); + break; + } + case VIDIOCSFREQ: + { + unsigned long v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSFREQ\n",CARD)); + + if (ztv->have_tuner) { + int fixme = v; + if (i2c_control_device(&(ztv->i2c), I2C_DRIVERID_TUNER, TUNER_SET_TVFREQ, &fixme) < 0) + return -EAGAIN; + } + ztv->tuner_freq = v; + break; + } + + /* Why isn't this in the API? + * And why doesn't it take a buffer number? + case BTTV_FIELDNR: + { + unsigned long v = ztv->lastfieldnr; + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "BTTV_FIELDNR\n",CARD)); + break; + } + */ + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static +int zoran_mmap(struct vm_area_struct *vma, struct video_device* dev, const char* adr, unsigned long size) +{ + struct zoran* ztv = (struct zoran*)dev; + unsigned long start = (unsigned long)adr; + unsigned long pos; + + DEBUG(printk(CARD_DEBUG "zoran_mmap(0x%p,%ld)\n",CARD,adr,size)); + + /* sanity checks */ + if (size > ZORAN_MAX_FBUFSIZE || !ztv->fbuffer) + return -EINVAL; + + /* start mapping the whole shabang to user memory */ + pos = (unsigned long)ztv->fbuffer; + while (size>0) { + unsigned long pfn = virt_to_phys((void*)pos) >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + return 0; +} + +static struct video_device zr36120_template= +{ + .owner = THIS_MODULE, + .name = "UNSET", + .type = VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY, + .hardware = VID_HARDWARE_ZR36120, + .open = zoran_open, + .close = zoran_close, + .read = zoran_read, + .write = zoran_write, + .poll = zoran_poll, + .ioctl = zoran_ioctl, + .mmap = zoran_mmap, + .minor = -1, +}; + +static +int vbi_open(struct video_device *dev, int flags) +{ + struct zoran *ztv = dev->priv; + struct vidinfo* item; + + DEBUG(printk(CARD_DEBUG "vbi_open(dev,%d)\n",CARD,flags)); + + /* + * During VBI device open, we continiously grab VBI-like + * data in the vbi buffer when we have nothing to do. + * Only when there is an explicit request for VBI data + * (read call) we /force/ a read. + */ + + /* allocate buffers */ + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + { + item->status = FBUFFER_FREE; + + /* alloc */ + if (!item->memadr) { + item->memadr = bmalloc(ZORAN_VBI_BUFSIZE); + if (!item->memadr) { + /* could not get a buffer, bail out */ + while (item != ztv->readinfo) { + item--; + bfree(item->memadr, ZORAN_VBI_BUFSIZE); + item->memadr = 0; + item->busadr = 0; + } + return -ENOBUFS; + } + } + + /* determine the DMAable address */ + item->busadr = virt_to_bus(item->memadr); + } + + /* do the common part of all open's */ + zoran_common_open(ztv, flags); + + set_bit(STATE_VBI, &ztv->state); + /* start read-ahead */ + zoran_cap(ztv, 1); + + return 0; +} + +static +void vbi_close(struct video_device *dev) +{ + struct zoran *ztv = dev->priv; + struct vidinfo* item; + + DEBUG(printk(CARD_DEBUG "vbi_close(dev)\n",CARD)); + + /* driver specific closure */ + clear_bit(STATE_VBI, &ztv->state); + + zoran_common_close(ztv); + + /* + * This is sucky but right now I can't find a good way to + * be sure its safe to free the buffer. We wait 5-6 fields + * which is more than sufficient to be sure. + */ + msleep(100); /* Wait 1/10th of a second */ + + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + { + if (item->memadr) + bfree(item->memadr, ZORAN_VBI_BUFSIZE); + item->memadr = 0; + } + +} + +/* + * This read function could be used reentrant in a SMP situation. + * + * This is made possible by the spinlock which is kept till we + * found and marked a buffer for our own use. The lock must + * be released as soon as possible to prevent lock contention. + */ +static +long vbi_read(struct video_device* dev, char* buf, unsigned long count, int nonblock) +{ + struct zoran *ztv = dev->priv; + unsigned long max; + struct vidinfo* unused = 0; + struct vidinfo* done = 0; + + DEBUG(printk(CARD_DEBUG "vbi_read(0x%p,%ld,%d)\n",CARD,buf,count,nonblock)); + + /* find ourself a free or completed buffer */ + for (;;) { + struct vidinfo* item; + + write_lock_irq(&ztv->lock); + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) { + if (!unused && item->status == FBUFFER_FREE) + unused = item; + if (!done && item->status == FBUFFER_DONE) + done = item; + } + if (done || unused) + break; + + /* no more free buffers, wait for them. */ + write_unlock_irq(&ztv->lock); + if (nonblock) + return -EWOULDBLOCK; + interruptible_sleep_on(&ztv->vbiq); + if (signal_pending(current)) + return -EINTR; + } + + /* Do we have 'ready' data? */ + if (!done) { + /* no? than this will take a while... */ + if (nonblock) { + write_unlock_irq(&ztv->lock); + return -EWOULDBLOCK; + } + + /* mark the unused buffer as wanted */ + unused->status = FBUFFER_BUSY; + unused->next = 0; + { /* add to tail of queue */ + struct vidinfo* oldframe = ztv->workqueue; + if (!oldframe) ztv->workqueue = unused; + else { + while (oldframe->next) oldframe = oldframe->next; + oldframe->next = unused; + } + } + write_unlock_irq(&ztv->lock); + + /* tell the state machine we want it filled /NOW/ */ + zoran_cap(ztv, 1); + + /* wait till this buffer gets grabbed */ + wait_event_interruptible(ztv->vbiq, + (unused->status != FBUFFER_BUSY)); + /* see if a signal did it */ + if (signal_pending(current)) + return -EINTR; + done = unused; + } + else + write_unlock_irq(&ztv->lock); + + /* Yes! we got data! */ + max = done->bpl * -done->h; + if (count > max) + count = max; + + /* check if the user gave us enough room to write the data */ + if (!access_ok(VERIFY_WRITE, buf, count)) { + count = -EFAULT; + goto out; + } + + /* + * Now transform/strip the data from YUV to Y-only + * NB. Assume the Y is in the LSB of the YUV data. + */ + { + unsigned char* optr = buf; + unsigned char* eptr = buf+count; + + /* are we beeing accessed from an old driver? */ + if (count == 2*19*2048) { + /* + * Extreme HACK, old VBI programs expect 2048 points + * of data, and we only got 864 orso. Double each + * datapoint and clear the rest of the line. + * This way we have appear to have a + * sample_frequency of 29.5 Mc. + */ + int x,y; + unsigned char* iptr = done->memadr+1; + for (y=done->h; optr<eptr && y<0; y++) + { + /* copy to doubled data to userland */ + for (x=0; optr+1<eptr && x<-done->w; x++) + { + unsigned char a = iptr[x*2]; + __put_user(a, optr++); + __put_user(a, optr++); + } + /* and clear the rest of the line */ + for (x*=2; optr<eptr && x<done->bpl; x++) + __put_user(0, optr++); + /* next line */ + iptr += done->bpl; + } + } + else { + /* + * Other (probably newer) programs asked + * us what geometry we are using, and are + * reading the correct size. + */ + int x,y; + unsigned char* iptr = done->memadr+1; + for (y=done->h; optr<eptr && y<0; y++) + { + /* copy to doubled data to userland */ + for (x=0; optr<eptr && x<-done->w; x++) + __put_user(iptr[x*2], optr++); + /* and clear the rest of the line */ + for (;optr<eptr && x<done->bpl; x++) + __put_user(0, optr++); + /* next line */ + iptr += done->bpl; + } + } + + /* API compliance: + * place the framenumber (half fieldnr) in the last long + */ + __put_user(done->fieldnr/2, ((ulong*)eptr)[-1]); + } + + /* keep the engine running */ + done->status = FBUFFER_FREE; + zoran_cap(ztv, 1); + + /* tell listeners this buffer just became free */ + wake_up_interruptible(&ztv->vbiq); + + /* goodbye */ +out: + DEBUG(printk(CARD_DEBUG "vbi_read() returns %lu\n",CARD,count)); + return count; +} + +static +unsigned int vbi_poll(struct video_device *dev, struct file *file, poll_table *wait) +{ + struct zoran *ztv = dev->priv; + struct vidinfo* item; + unsigned int mask = 0; + + poll_wait(file, &ztv->vbiq, wait); + + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + if (item->status == FBUFFER_DONE) + { + mask |= (POLLIN | POLLRDNORM); + break; + } + + DEBUG(printk(CARD_DEBUG "vbi_poll()=%x\n",CARD,mask)); + + return mask; +} + +static +int vbi_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct zoran* ztv = dev->priv; + + switch (cmd) { + case VIDIOCGVBIFMT: + { + struct vbi_format f; + DEBUG(printk(CARD_DEBUG "VIDIOCGVBIINFO\n",CARD)); + f.sampling_rate = 14750000UL; + f.samples_per_line = -ztv->readinfo[0].w; + f.sample_format = VIDEO_PALETTE_RAW; + f.start[0] = f.start[1] = ztv->readinfo[0].y; + f.start[1] += 312; + f.count[0] = f.count[1] = -ztv->readinfo[0].h; + f.flags = VBI_INTERLACED; + if (copy_to_user(arg,&f,sizeof(f))) + return -EFAULT; + break; + } + case VIDIOCSVBIFMT: + { + struct vbi_format f; + int i; + if (copy_from_user(&f, arg,sizeof(f))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSVBIINFO(%d,%d,%d,%d,%d,%d,%d,%x)\n",CARD,f.sampling_rate,f.samples_per_line,f.sample_format,f.start[0],f.start[1],f.count[0],f.count[1],f.flags)); + + /* lots of parameters are fixed... (PAL) */ + if (f.sampling_rate != 14750000UL || + f.samples_per_line > 864 || + f.sample_format != VIDEO_PALETTE_RAW || + f.start[0] < 0 || + f.start[0] != f.start[1]-312 || + f.count[0] != f.count[1] || + f.start[0]+f.count[0] >= 288 || + f.flags != VBI_INTERLACED) + return -EINVAL; + + write_lock_irq(&ztv->lock); + ztv->readinfo[0].y = f.start[0]; + ztv->readinfo[0].w = -f.samples_per_line; + ztv->readinfo[0].h = -f.count[0]; + ztv->readinfo[0].bpl = f.samples_per_line*ztv->readinfo[0].bpp; + for (i=1; i<ZORAN_VBI_BUFFERS; i++) + ztv->readinfo[i] = ztv->readinfo[i]; + write_unlock_irq(&ztv->lock); + break; + } + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct video_device vbi_template= +{ + .owner = THIS_MODULE, + .name = "UNSET", + .type = VID_TYPE_CAPTURE|VID_TYPE_TELETEXT, + .hardware = VID_HARDWARE_ZR36120, + .open = vbi_open, + .close = vbi_close, + .read = vbi_read, + .write = zoran_write, + .poll = vbi_poll, + .ioctl = vbi_ioctl, + .minor = -1, +}; + +/* + * Scan for a Zoran chip, request the irq and map the io memory + */ +static +int __init find_zoran(void) +{ + int result; + struct zoran *ztv; + struct pci_dev *dev = NULL; + unsigned char revision; + int zoran_num=0; + + while ((dev = pci_find_device(PCI_VENDOR_ID_ZORAN,PCI_DEVICE_ID_ZORAN_36120, dev))) + { + /* Ok, a ZR36120/ZR36125 found! */ + ztv = &zorans[zoran_num]; + ztv->dev = dev; + + if (pci_enable_device(dev)) + return -EIO; + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &revision); + printk(KERN_INFO "zoran: Zoran %x (rev %d) ", + dev->device, revision); + printk("bus: %d, devfn: %d, irq: %d, ", + dev->bus->number, dev->devfn, dev->irq); + printk("memory: 0x%08lx.\n", ztv->zoran_adr); + + ztv->zoran_mem = ioremap(ztv->zoran_adr, 0x1000); + DEBUG(printk(KERN_DEBUG "zoran: mapped-memory at 0x%p\n",ztv->zoran_mem)); + + result = request_irq(dev->irq, zoran_irq, + SA_SHIRQ|SA_INTERRUPT,"zoran", ztv); + if (result==-EINVAL) + { + iounmap(ztv->zoran_mem); + printk(KERN_ERR "zoran: Bad irq number or handler\n"); + return -EINVAL; + } + if (result==-EBUSY) + printk(KERN_ERR "zoran: IRQ %d busy, change your PnP config in BIOS\n",dev->irq); + if (result < 0) { + iounmap(ztv->zoran_mem); + return result; + } + /* Enable bus-mastering */ + pci_set_master(dev); + + zoran_num++; + } + if(zoran_num) + printk(KERN_INFO "zoran: %d Zoran card(s) found.\n",zoran_num); + return zoran_num; +} + +static +int __init init_zoran(int card) +{ + struct zoran *ztv = &zorans[card]; + int i; + + /* if the given cardtype valid? */ + if (cardtype[card]>=NRTVCARDS) { + printk(KERN_INFO "invalid cardtype(%d) detected\n",cardtype[card]); + return -1; + } + + /* reset the zoran */ + zrand(~ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + zror(ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + + /* zoran chip specific details */ + ztv->card = tvcards+cardtype[card]; /* point to the selected card */ + ztv->norm = 0; /* PAL */ + ztv->tuner_freq = 0; + + /* videocard details */ + ztv->swidth = 800; + ztv->sheight = 600; + ztv->depth = 16; + + /* State details */ + ztv->fbuffer = 0; + ztv->overinfo.kindof = FBUFFER_OVERLAY; + ztv->overinfo.status = FBUFFER_FREE; + ztv->overinfo.x = 0; + ztv->overinfo.y = 0; + ztv->overinfo.w = 768; /* 640 */ + ztv->overinfo.h = 576; /* 480 */ + ztv->overinfo.format = VIDEO_PALETTE_RGB565; + ztv->overinfo.bpp = palette2fmt[ztv->overinfo.format].bpp; + ztv->overinfo.bpl = ztv->overinfo.bpp*ztv->swidth; + ztv->overinfo.busadr = 0; + ztv->overinfo.memadr = 0; + ztv->overinfo.overlay = 0; + for (i=0; i<ZORAN_MAX_FBUFFERS; i++) { + ztv->grabinfo[i] = ztv->overinfo; + ztv->grabinfo[i].kindof = FBUFFER_GRAB; + } + init_waitqueue_head(&ztv->grabq); + + /* VBI details */ + ztv->readinfo[0] = ztv->overinfo; + ztv->readinfo[0].kindof = FBUFFER_VBI; + ztv->readinfo[0].w = -864; + ztv->readinfo[0].h = -38; + ztv->readinfo[0].format = VIDEO_PALETTE_YUV422; + ztv->readinfo[0].bpp = palette2fmt[ztv->readinfo[0].format].bpp; + ztv->readinfo[0].bpl = 1024*ztv->readinfo[0].bpp; + for (i=1; i<ZORAN_VBI_BUFFERS; i++) + ztv->readinfo[i] = ztv->readinfo[0]; + init_waitqueue_head(&ztv->vbiq); + + /* maintenance data */ + ztv->have_decoder = 0; + ztv->have_tuner = 0; + ztv->tuner_type = 0; + ztv->running = 0; + ztv->users = 0; + rwlock_init(&ztv->lock); + ztv->workqueue = 0; + ztv->fieldnr = 0; + ztv->lastfieldnr = 0; + + if (triton1) + zrand(~ZORAN_VDC_TRICOM, ZORAN_VDC); + + /* external FL determines TOP frame */ + zror(ZORAN_VFEC_EXTFL, ZORAN_VFEC); + + /* set HSpol */ + if (ztv->card->hsync_pos) + zrwrite(ZORAN_VFEH_HSPOL, ZORAN_VFEH); + /* set VSpol */ + if (ztv->card->vsync_pos) + zrwrite(ZORAN_VFEV_VSPOL, ZORAN_VFEV); + + /* Set the proper General Purpuse register bits */ + /* implicit: no softreset, 0 waitstates */ + zrwrite(ZORAN_PCI_SOFTRESET|(ztv->card->gpdir<<0),ZORAN_PCI); + /* implicit: 3 duration and recovery PCI clocks on guest 0-3 */ + zrwrite(ztv->card->gpval<<24,ZORAN_GUEST); + + /* clear interrupt status */ + zrwrite(~0, ZORAN_ISR); + + /* + * i2c template + */ + ztv->i2c = zoran_i2c_bus_template; + sprintf(ztv->i2c.name,"zoran-%d",card); + ztv->i2c.data = ztv; + + /* + * Now add the template and register the device unit + */ + ztv->video_dev = zr36120_template; + strcpy(ztv->video_dev.name, ztv->i2c.name); + ztv->video_dev.priv = ztv; + if (video_register_device(&ztv->video_dev, VFL_TYPE_GRABBER, video_nr) < 0) + return -1; + + ztv->vbi_dev = vbi_template; + strcpy(ztv->vbi_dev.name, ztv->i2c.name); + ztv->vbi_dev.priv = ztv; + if (video_register_device(&ztv->vbi_dev, VFL_TYPE_VBI, vbi_nr) < 0) { + video_unregister_device(&ztv->video_dev); + return -1; + } + i2c_register_bus(&ztv->i2c); + + /* set interrupt mask - the PIN enable will be set later */ + zrwrite(ZORAN_ICR_GIRQ0|ZORAN_ICR_GIRQ1|ZORAN_ICR_CODE, ZORAN_ICR); + + printk(KERN_INFO "%s: installed %s\n",ztv->i2c.name,ztv->card->name); + return 0; +} + +static +void release_zoran(int max) +{ + struct zoran *ztv; + int i; + + for (i=0;i<max; i++) + { + ztv = &zorans[i]; + + /* turn off all capturing, DMA and IRQs */ + /* reset the zoran */ + zrand(~ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + zror(ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + + /* first disable interrupts before unmapping the memory! */ + zrwrite(0, ZORAN_ICR); + zrwrite(0xffffffffUL,ZORAN_ISR); + + /* free it */ + free_irq(ztv->dev->irq,ztv); + + /* unregister i2c_bus */ + i2c_unregister_bus((&ztv->i2c)); + + /* unmap and free memory */ + if (ztv->zoran_mem) + iounmap(ztv->zoran_mem); + + video_unregister_device(&ztv->video_dev); + video_unregister_device(&ztv->vbi_dev); + } +} + +void __exit zr36120_exit(void) +{ + release_zoran(zoran_cards); +} + +int __init zr36120_init(void) +{ + int card; + + handle_chipset(); + zoran_cards = find_zoran(); + if (zoran_cards<0) + /* no cards found, no need for a driver */ + return -EIO; + + /* initialize Zorans */ + for (card=0; card<zoran_cards; card++) { + if (init_zoran(card)<0) { + /* only release the zorans we have registered */ + release_zoran(card); + return -EIO; + } + } + return 0; +} + +module_init(zr36120_init); +module_exit(zr36120_exit); diff --git a/drivers/media/video/zr36120.h b/drivers/media/video/zr36120.h new file mode 100644 index 000000000000..571f8e84b58a --- /dev/null +++ b/drivers/media/video/zr36120.h @@ -0,0 +1,279 @@ +/* + zr36120.h - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink (middelin@polyware.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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _ZR36120_H +#define _ZR36120_H + +#ifdef __KERNEL__ + +#include <linux/types.h> +#include <linux/wait.h> + +#include <linux/i2c-old.h> +#include <linux/videodev.h> + +#include <asm/io.h> + +/* + * Debug macro's, place an x behind the ) for actual debug-compilation + * E.g. #define DEBUG(x...) x + */ +#define DEBUG(x...) /* Debug driver */ +#define IDEBUG(x...) /* Debug interrupt handler */ +#define PDEBUG 0 /* Debug PCI writes */ + +/* defined in zr36120_i2c */ +extern struct i2c_bus zoran_i2c_bus_template; + +#define ZORAN_MAX_FBUFFERS 2 +#define ZORAN_MAX_FBUFFER (768*576*2) +#define ZORAN_MAX_FBUFSIZE (ZORAN_MAX_FBUFFERS*ZORAN_MAX_FBUFFER) + +#define ZORAN_VBI_BUFFERS 2 +#define ZORAN_VBI_BUFSIZE (22*1024*2) + +struct tvcard { + char* name; /* name of the cardtype */ + int video_inputs; /* number of channels defined in video_mux */ + int audio_inputs; /* number of channels defined in audio_mux */ + __u32 swapi2c:1, /* need to swap i2c wires SDA/SCL? */ + usegirq1:1, /* VSYNC at GIRQ1 instead of GIRQ0? */ + vsync_pos:1, /* positive VSYNC signal? */ + hsync_pos:1, /* positive HSYNC signal? */ + gpdir:8, /* General Purpose Direction register */ + gpval:8; /* General Purpose Value register */ + int video_mux[6]; /* mapping channel number to physical input */ +#define IS_TUNER 0x80 +#define IS_SVHS 0x40 +#define CHANNEL_MASK 0x3F + int audio_mux[6]; /* mapping channel number to physical input */ +}; +#define TUNER(x) ((x)|IS_TUNER) +#define SVHS(x) ((x)|IS_SVHS) + +struct vidinfo { + struct vidinfo* next; /* next active buffer */ + uint kindof; +#define FBUFFER_OVERLAY 0 +#define FBUFFER_GRAB 1 +#define FBUFFER_VBI 2 + uint status; +#define FBUFFER_FREE 0 +#define FBUFFER_BUSY 1 +#define FBUFFER_DONE 2 + ulong fieldnr; /* # of field, not framer! */ + uint x,y; + int w,h; /* w,h can be negative! */ + uint format; /* index in palette2fmt[] */ + uint bpp; /* lookup from palette2fmt[] */ + uint bpl; /* calc: width * bpp */ + ulong busadr; /* bus addr for DMA engine */ + char* memadr; /* kernel addr for making copies */ + ulong* overlay; /* kernel addr of overlay mask */ +}; + +struct zoran +{ + struct video_device video_dev; +#define CARD_DEBUG KERN_DEBUG "%s(%lu): " +#define CARD_INFO KERN_INFO "%s(%lu): " +#define CARD_ERR KERN_ERR "%s(%lu): " +#define CARD ztv->video_dev.name,ztv->fieldnr + + /* zoran chip specific details */ + struct i2c_bus i2c; /* i2c registration data */ + struct pci_dev* dev; /* ptr to PCI device */ + ulong zoran_adr; /* bus address of IO memory */ + char* zoran_mem; /* kernel address of IO memory */ + struct tvcard* card; /* the cardtype */ + uint norm; /* 0=PAL, 1=NTSC, 2=SECAM */ + uint tuner_freq; /* Current freq in kHz */ + struct video_picture picture; /* Current picture params */ + + /* videocard details */ + uint swidth; /* screen width */ + uint sheight; /* screen height */ + uint depth; /* depth in bits */ + + /* State details */ + char* fbuffer; /* framebuffers for mmap */ + struct vidinfo overinfo; /* overlay data */ + struct vidinfo grabinfo[ZORAN_MAX_FBUFFERS]; /* grabbing data*/ + wait_queue_head_t grabq; /* grabbers queue */ + + /* VBI details */ + struct video_device vbi_dev; + struct vidinfo readinfo[2]; /* VBI data - flip buffers */ + wait_queue_head_t vbiq; /* vbi queue */ + + /* maintenance data */ + int have_decoder; /* did we detect a mux? */ + int have_tuner; /* did we detect a tuner? */ + int users; /* howmany video/vbi open? */ + int tuner_type; /* tuner type, when found */ + int running; /* are we rolling? */ + rwlock_t lock; + long state; /* what is requested of us? */ +#define STATE_OVERLAY 0 +#define STATE_VBI 1 + struct vidinfo* workqueue; /* buffers to grab, head is active */ + ulong fieldnr; /* #field, ticked every VSYNC */ + ulong lastfieldnr; /* #field, ticked every GRAB */ + + int vidInterlace; /* calculated */ + int vidXshift; /* calculated */ + uint vidWidth; /* calculated */ + uint vidHeight; /* calculated */ +}; + +#define zrwrite(dat,adr) writel((dat),(char *) (ztv->zoran_mem+(adr))) +#define zrread(adr) readl(ztv->zoran_mem+(adr)) + +#if PDEBUG == 0 +#define zrand(dat,adr) zrwrite((dat) & zrread(adr), adr) +#define zror(dat,adr) zrwrite((dat) | zrread(adr), adr) +#define zraor(dat,mask,adr) zrwrite( ((dat)&~(mask)) | ((mask)&zrread(adr)), adr) +#else +#define zrand(dat, adr) \ +do { \ + ulong data = (dat) & zrread((adr)); \ + zrwrite(data, (adr)); \ + if (0 != (~(dat) & zrread((adr)))) \ + printk(KERN_DEBUG "zoran: zrand at %d(%d) detected set bits(%x)\n", __LINE__, (adr), (dat)); \ +} while(0) + +#define zror(dat, adr) \ +do { \ + ulong data = (dat) | zrread((adr)); \ + zrwrite(data, (adr)); \ + if ((dat) != ((dat) & zrread(adr))) \ + printk(KERN_DEBUG "zoran: zror at %d(%d) detected unset bits(%x)\n", __LINE__, (adr), (dat)); \ +} while(0) + +#define zraor(dat, mask, adr) \ +do { \ + ulong data; \ + if ((dat) & (mask)) \ + printk(KERN_DEBUG "zoran: zraor at %d(%d) detected bits(%x:%x)\n", __LINE__, (adr), (dat), (mask)); \ + data = ((dat)&~(mask)) | ((mask) & zrread((adr))); \ + zrwrite(data,(adr)); \ + if ( (dat) != (~(mask) & zrread((adr))) ) \ + printk(KERN_DEBUG "zoran: zraor at %d(%d) could not set all bits(%x:%x)\n", __LINE__, (adr), (dat), (mask)); \ +} while(0) +#endif + +#endif + +/* zoran PCI address space */ +#define ZORAN_VFEH 0x000 /* Video Front End Horizontal Conf. */ +#define ZORAN_VFEH_HSPOL (1<<30) +#define ZORAN_VFEH_HSTART (0x3FF<<10) +#define ZORAN_VFEH_HEND (0x3FF<<0) + +#define ZORAN_VFEV 0x004 /* Video Front End Vertical Conf. */ +#define ZORAN_VFEV_VSPOL (1<<30) +#define ZORAN_VFEV_VSTART (0x3FF<<10) +#define ZORAN_VFEV_VEND (0x3FF<<0) + +#define ZORAN_VFEC 0x008 /* Video Front End Scaler and Pixel */ +#define ZORAN_VFEC_EXTFL (1<<26) +#define ZORAN_VFEC_TOPFIELD (1<<25) +#define ZORAN_VFEC_VCLKPOL (1<<24) +#define ZORAN_VFEC_HFILTER (7<<21) +#define ZORAN_VFEC_HFILTER_1 (0<<21) /* no lumi, 3-tap chromo */ +#define ZORAN_VFEC_HFILTER_2 (1<<21) /* 3-tap lumi, 3-tap chromo */ +#define ZORAN_VFEC_HFILTER_3 (2<<21) /* 4-tap lumi, 4-tap chromo */ +#define ZORAN_VFEC_HFILTER_4 (3<<21) /* 5-tap lumi, 4-tap chromo */ +#define ZORAN_VFEC_HFILTER_5 (4<<21) /* 4-tap lumi, 4-tap chromo */ +#define ZORAN_VFEC_DUPFLD (1<<20) +#define ZORAN_VFEC_HORDCM (63<<14) +#define ZORAN_VFEC_VERDCM (63<<8) +#define ZORAN_VFEC_DISPMOD (1<<6) +#define ZORAN_VFEC_RGB (3<<3) +#define ZORAN_VFEC_RGB_YUV422 (0<<3) +#define ZORAN_VFEC_RGB_RGB888 (1<<3) +#define ZORAN_VFEC_RGB_RGB565 (2<<3) +#define ZORAN_VFEC_RGB_RGB555 (3<<3) +#define ZORAN_VFEC_ERRDIF (1<<2) +#define ZORAN_VFEC_PACK24 (1<<1) +#define ZORAN_VFEC_LE (1<<0) + +#define ZORAN_VTOP 0x00C /* Video Display "Top" */ + +#define ZORAN_VBOT 0x010 /* Video Display "Bottom" */ + +#define ZORAN_VSTR 0x014 /* Video Display Stride */ +#define ZORAN_VSTR_DISPSTRIDE (0xFFFF<<16) +#define ZORAN_VSTR_VIDOVF (1<<8) +#define ZORAN_VSTR_SNAPSHOT (1<<1) +#define ZORAN_VSTR_GRAB (1<<0) + +#define ZORAN_VDC 0x018 /* Video Display Conf. */ +#define ZORAN_VDC_VIDEN (1<<31) +#define ZORAN_VDC_MINPIX (0x1F<<25) +#define ZORAN_VDC_TRICOM (1<<24) +#define ZORAN_VDC_VIDWINHT (0x3FF<<12) +#define ZORAN_VDC_VIDWINWID (0x3FF<<0) + +#define ZORAN_MTOP 0x01C /* Masking Map "Top" */ + +#define ZORAN_MBOT 0x020 /* Masking Map "Bottom" */ + +#define ZORAN_OCR 0x024 /* Overlay Control */ +#define ZORAN_OCR_OVLEN (1<<15) +#define ZORAN_OCR_MASKSTRIDE (0xFF<<0) + +#define ZORAN_PCI 0x028 /* System, PCI and GPP Control */ +#define ZORAN_PCI_SOFTRESET (1<<24) +#define ZORAN_PCI_WAITSTATE (3<<16) +#define ZORAN_PCI_GENPURDIR (0xFF<<0) + +#define ZORAN_GUEST 0x02C /* GuestBus Control */ + +#define ZORAN_CSOURCE 0x030 /* Code Source Address */ + +#define ZORAN_CTRANS 0x034 /* Code Transfer Control */ + +#define ZORAN_CMEM 0x038 /* Code Memory Pointer */ + +#define ZORAN_ISR 0x03C /* Interrupt Status Register */ +#define ZORAN_ISR_CODE (1<<28) +#define ZORAN_ISR_GIRQ0 (1<<29) +#define ZORAN_ISR_GIRQ1 (1<<30) + +#define ZORAN_ICR 0x040 /* Interrupt Control Register */ +#define ZORAN_ICR_EN (1<<24) +#define ZORAN_ICR_CODE (1<<28) +#define ZORAN_ICR_GIRQ0 (1<<29) +#define ZORAN_ICR_GIRQ1 (1<<30) + +#define ZORAN_I2C 0x044 /* I2C-Bus */ +#define ZORAN_I2C_SCL (1<<1) +#define ZORAN_I2C_SDA (1<<0) + +#define ZORAN_POST 0x48 /* PostOffice */ +#define ZORAN_POST_PEN (1<<25) +#define ZORAN_POST_TIME (1<<24) +#define ZORAN_POST_DIR (1<<23) +#define ZORAN_POST_GUESTID (3<<20) +#define ZORAN_POST_GUEST (7<<16) +#define ZORAN_POST_DATA (0xFF<<0) + +#endif diff --git a/drivers/media/video/zr36120_i2c.c b/drivers/media/video/zr36120_i2c.c new file mode 100644 index 000000000000..6bfe84d657f1 --- /dev/null +++ b/drivers/media/video/zr36120_i2c.c @@ -0,0 +1,132 @@ +/* + zr36120_i2c.c - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/types.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include <linux/video_decoder.h> +#include <asm/uaccess.h> + +#include "tuner.h" +#include "zr36120.h" + +/* ----------------------------------------------------------------------- */ +/* I2C functions */ +/* ----------------------------------------------------------------------- */ + +/* software I2C functions */ + +#define I2C_DELAY 10 + +static void i2c_setlines(struct i2c_bus *bus,int ctrl,int data) +{ + struct zoran *ztv = (struct zoran*)bus->data; + unsigned int b = 0; + if (data) b |= ztv->card->swapi2c ? ZORAN_I2C_SCL : ZORAN_I2C_SDA; + if (ctrl) b |= ztv->card->swapi2c ? ZORAN_I2C_SDA : ZORAN_I2C_SCL; + zrwrite(b, ZORAN_I2C); + udelay(I2C_DELAY); +} + +static int i2c_getdataline(struct i2c_bus *bus) +{ + struct zoran *ztv = (struct zoran*)bus->data; + if (ztv->card->swapi2c) + return zrread(ZORAN_I2C) & ZORAN_I2C_SCL; + return zrread(ZORAN_I2C) & ZORAN_I2C_SDA; +} + +static +void attach_inform(struct i2c_bus *bus, int id) +{ + struct zoran *ztv = (struct zoran*)bus->data; + struct video_decoder_capability dc; + int rv; + + switch (id) { + case I2C_DRIVERID_VIDEODECODER: + DEBUG(printk(CARD_INFO "decoder attached\n",CARD)); + + /* fetch the capabilites of the decoder */ + rv = i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_GET_CAPABILITIES, &dc); + if (rv) { + DEBUG(printk(CARD_DEBUG "decoder is not V4L aware!\n",CARD)); + break; + } + DEBUG(printk(CARD_DEBUG "capabilities %d %d %d\n",CARD,dc.flags,dc.inputs,dc.outputs)); + + /* Test if the decoder can de VBI transfers */ + if (dc.flags & 16 /*VIDEO_DECODER_VBI*/) + ztv->have_decoder = 2; + else + ztv->have_decoder = 1; + break; + case I2C_DRIVERID_TUNER: + ztv->have_tuner = 1; + DEBUG(printk(CARD_INFO "tuner attached\n",CARD)); + if (ztv->tuner_type >= 0) + { + if (i2c_control_device(&ztv->i2c,I2C_DRIVERID_TUNER,TUNER_SET_TYPE,&ztv->tuner_type)<0) + DEBUG(printk(CARD_INFO "attach_inform; tuner won't be set to type %d\n",CARD,ztv->tuner_type)); + } + break; + default: + DEBUG(printk(CARD_INFO "attach_inform; unknown device id=%d\n",CARD,id)); + break; + } +} + +static +void detach_inform(struct i2c_bus *bus, int id) +{ + struct zoran *ztv = (struct zoran*)bus->data; + + switch (id) { + case I2C_DRIVERID_VIDEODECODER: + ztv->have_decoder = 0; + DEBUG(printk(CARD_INFO "decoder detached\n",CARD)); + break; + case I2C_DRIVERID_TUNER: + ztv->have_tuner = 0; + DEBUG(printk(CARD_INFO "tuner detached\n",CARD)); + break; + default: + DEBUG(printk(CARD_INFO "detach_inform; unknown device id=%d\n",CARD,id)); + break; + } +} + +struct i2c_bus zoran_i2c_bus_template = +{ + "ZR36120", + I2C_BUSID_ZORAN, + NULL, + + SPIN_LOCK_UNLOCKED, + + attach_inform, + detach_inform, + + i2c_setlines, + i2c_getdataline, + NULL, + NULL +}; diff --git a/drivers/media/video/zr36120_mem.c b/drivers/media/video/zr36120_mem.c new file mode 100644 index 000000000000..c87113d6cc60 --- /dev/null +++ b/drivers/media/video/zr36120_mem.c @@ -0,0 +1,79 @@ +/* + zr36120_mem.c - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/config.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <asm/io.h> +#ifdef CONFIG_BIGPHYS_AREA +#include <linux/bigphysarea.h> +#endif + +#include "zr36120.h" +#include "zr36120_mem.h" + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +void* bmalloc(unsigned long size) +{ + void* mem; +#ifdef CONFIG_BIGPHYS_AREA + mem = bigphysarea_alloc_pages(size/PAGE_SIZE, 1, GFP_KERNEL); +#else + /* + * The following function got a lot of memory at boottime, + * so we know its always there... + */ + mem = (void*)__get_free_pages(GFP_USER|GFP_DMA,get_order(size)); +#endif + if (mem) { + unsigned long adr = (unsigned long)mem; + while (size > 0) { + SetPageReserved(virt_to_page(phys_to_virt(adr))); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + } + return mem; +} + +void bfree(void* mem, unsigned long size) +{ + if (mem) { + unsigned long adr = (unsigned long)mem; + unsigned long siz = size; + while (siz > 0) { + ClearPageReserved(virt_to_page(phys_to_virt(adr))); + adr += PAGE_SIZE; + siz -= PAGE_SIZE; + } +#ifdef CONFIG_BIGPHYS_AREA + bigphysarea_free_pages(mem); +#else + free_pages((unsigned long)mem,get_order(size)); +#endif + } +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/zr36120_mem.h b/drivers/media/video/zr36120_mem.h new file mode 100644 index 000000000000..aad117acc91d --- /dev/null +++ b/drivers/media/video/zr36120_mem.h @@ -0,0 +1,3 @@ +/* either kmalloc() or bigphysarea() alloced memory - continuous */ +void* bmalloc(unsigned long size); +void bfree(void* mem, unsigned long size); |