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/saa7134 | |
download | blackbird-op-linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz blackbird-op-linux-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/saa7134')
-rw-r--r-- | drivers/media/video/saa7134/Makefile | 11 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa6752hs.c | 543 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-cards.c | 2018 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-core.c | 1237 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-dvb.c | 266 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-empress.c | 436 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-i2c.c | 453 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-input.c | 491 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-oss.c | 857 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-reg.h | 366 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-ts.c | 243 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-tvaudio.c | 1031 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-vbi.c | 270 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134-video.c | 2406 | ||||
-rw-r--r-- | drivers/media/video/saa7134/saa7134.h | 618 |
15 files changed, 11246 insertions, 0 deletions
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: + */ |