Commit e12a2bd0 authored by Gerd Knorr's avatar Gerd Knorr Committed by Linus Torvalds

[PATCH] new v4l2 driver: saa7134

This patch adds a new device driver to the linux kernel.  It is for TV
cards based on the Philips SAA7134 chip.  It supports the v4l2 API and
thus depends on the v4l2 patches of the previous mails.
parent 8354eb8a
......@@ -212,5 +212,17 @@ config VIDEO_MEYE
whenever you want). If you want to compile it as a module, say M
here and read <file:Documentation/modules.txt>.
config VIDEO_SAA7134
tristate "Philips SAA7134 support"
depends on VIDEO_DEV && PCI && I2C
---help---
This is a video4linux driver for Philips SAA7130/7134 based
TV cards.
This driver is available as a module called saa7134.o ( = code
which can be inserted in and removed from the running kernel
whenever you want). If you want to compile it as a module, say M
here and read <file:Documentation/modules.txt>.
endmenu
......@@ -35,6 +35,7 @@ obj-$(CONFIG_VIDEO_CPIA) += cpia.o
obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o
obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o
obj-$(CONFIG_VIDEO_MEYE) += meye.o
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ tuner.o tda9887.o
obj-$(CONFIG_TUNER_3036) += tuner-3036.o
include $(TOPDIR)/Rules.make
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
obj-$(CONFIG_VIDEO_SAA7134) += saa7134.o
EXTRA_CFLAGS = -I$(src)/..
include $(TOPDIR)/Rules.make
/*
* device driver for philips saa7134 based TV cards
* card-specific stuff.
*
* (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/module.h>
#include "saa7134-reg.h"
#include "saa7134.h"
#include "tuner.h"
/* commly used strings */
static char name_mute[] = "mute";
static char name_radio[] = "Radio";
static char name_tv[] = "Television";
static char name_comp1[] = "Composite1";
static char name_comp2[] = "Composite2";
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,
}},
},
[SAA7134_BOARD_FLYVIDEO3000] = {
/* "Marco d'Itri" <md@Linux.IT> */
name: "LifeView FlyVIDEO3000",
audio_clock: 0x00200000,
tuner_type: TUNER_PHILIPS_PAL,
inputs: {{
name: name_tv,
vmux: 1,
amux: TV,
tv: 1,
},{
name: name_comp1,
vmux: 0,
amux: LINE1,
},{
name: name_comp2,
vmux: 3,
amux: LINE1,
},{
name: name_svideo,
vmux: 8,
amux: LINE1,
}},
radio: {
name: name_radio,
amux: LINE2,
},
},
[SAA7134_BOARD_FLYVIDEO2000] = {
/* "TC Wan" <tcwan@cs.usm.my> */
name: "LifeView FlyVIDEO2000",
audio_clock: 0x00200000,
tuner_type: TUNER_LG_PAL_NEW_TAPC,
gpiomask: 0x6000,
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,
},
mute: {
name: name_mute,
amux: LINE1,
},
},
[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,
},
i2s_rate: 48000,
has_ts: 1,
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: "TV (mono only)",
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] = {
name: "KNC One TV-Station RDS",
audio_clock: 0x00200000,
tuner_type: TUNER_PHILIPS_FM1216ME_MK3,
need_tda9887: 1,
inputs: {{
name: name_tv,
vmux: 1,
amux: TV,
tv: 1,
},{
name: name_comp1,
vmux: 2,
amux: LINE1,
},{
name: name_comp2,
vmux: 3,
amux: LINE1,
}},
radio: {
name: name_radio,
amux: LINE2,
},
},
[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: 0x00200000,
tuner_type: TUNER_PHILIPS_FM1216ME_MK3,
need_tda9887: 1,
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,
},
},
};
const int saa7134_bcount = (sizeof(saa7134_boards)/sizeof(struct saa7134_board));
/* ------------------------------------------------------------------ */
/* PCI ids + subsystem IDs */
struct pci_device_id __devinitdata 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_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,
},{
/* --- 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_SAA7134,
subvendor: PCI_ANY_ID,
subdevice: PCI_ANY_ID,
driver_data: SAA7134_BOARD_UNKNOWN,
},{
/* --- end of list --- */
}
};
MODULE_DEVICE_TABLE(pci, saa7134_pci_tbl);
/* ----------------------------------------------------------- */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* device driver for philips saa7134 based TV cards
* driver core
*
* (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/kernel.h>
#include <linux/slab.h>
#include <linux/kmod.h>
#include <linux/sound.h>
#include <linux/interrupt.h>
#include "saa7134-reg.h"
#include "saa7134.h"
#include "tuner.h"
MODULE_DESCRIPTION("v4l2 driver module for saa7130/34 based TV cards");
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
MODULE_LICENSE("GPL");
#define SAA7134_MAXBOARDS 4
/* ------------------------------------------------------------------ */
static unsigned int irq_debug = 0;
MODULE_PARM(irq_debug,"i");
MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]");
static unsigned int core_debug = 0;
MODULE_PARM(core_debug,"i");
MODULE_PARM_DESC(core_debug,"enable debug messages [core]");
static unsigned int gpio_tracking = 0;
MODULE_PARM(gpio_tracking,"i");
MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]");
static unsigned int video_nr = -1;
MODULE_PARM(video_nr,"i");
MODULE_PARM_DESC(video_nr,"video device number");
static unsigned int ts_nr = -1;
MODULE_PARM(ts_nr,"i");
MODULE_PARM_DESC(ts_nr,"ts device number");
static unsigned int vbi_nr = -1;
MODULE_PARM(vbi_nr,"i");
MODULE_PARM_DESC(vbi_nr,"vbi device number");
static unsigned int radio_nr = -1;
MODULE_PARM(radio_nr,"i");
MODULE_PARM_DESC(radio_nr,"radio device number");
static unsigned int dsp_nr = -1;
MODULE_PARM(dsp_nr,"i");
MODULE_PARM_DESC(dsp_nr,"oss dsp device number");
static unsigned int mixer_nr = -1;
MODULE_PARM(mixer_nr,"i");
MODULE_PARM_DESC(mixer_nr,"oss mixer device number");
static int tuner[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = -1};
MODULE_PARM(tuner,"1-" __stringify(SAA7134_MAXBOARDS) "i");
MODULE_PARM_DESC(tuner,"tuner type");
static int card[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = -1};
MODULE_PARM(card,"1-" __stringify(SAA7134_MAXBOARDS) "i");
MODULE_PARM_DESC(card,"card type");
static int latency = -1;
MODULE_PARM(latency,"i");
MODULE_PARM_DESC(latency,"pci latency timer");
struct list_head saa7134_devlist;
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", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT",
"CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ",
"SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT",
"GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO",
"SMICROCODE", "GVBIFMT", "SVBIFMT" };
#define V4L1_IOCTLS (sizeof(v4l1_ioctls)/sizeof(char*))
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 (sizeof(v4l2_ioctls)/sizeof(char*))
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 (sizeof(v4l2_ioctls)/sizeof(char*))
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
/* ------------------------------------------------------------------ */
/* nr of (saa7134-)pages for the given buffer size */
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(int size, int count)
{
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, int length,
int startpage)
{
u32 *ptr;
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)
{
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
dprintk("buffer_queue %p\n",buf);
if (NULL == q->curr) {
q->curr = buf;
buf->activate(dev,buf,NULL);
} 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,
int state)
{
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
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;
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
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);
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)
{
unsigned long task=0, ctrl=0, irq=0, split = 0;
enum v4l2_field cap = V4L2_FIELD_ANY;
enum v4l2_field ov = V4L2_FIELD_ANY;
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
/* 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.recording) {
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:
odd A, even A, odd B, even B, repeat */
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);
} 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%02lx ctrl=0x%02lx irq=0x%lx 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"
};
#define IRQBITS (sizeof(irqbits)/sizeof(char*))
static void print_irqstatus(struct saa7134_dev *dev, int loop,
unsigned long report, unsigned long status)
{
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 void 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;
for (loop = 0; loop < 10; loop++) {
report = saa_readl(SAA7134_IRQ_REPORT);
status = saa_readl(SAA7134_IRQ_STATUS);
saa_writel(SAA7134_IRQ_REPORT,report);
if (0 == report) {
if (irq_debug > 1)
printk(KERN_DEBUG "%s/irq: no (more) work\n",
dev->name);
return;
}
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_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_ts(dev))
saa7134_irq_ts_done(dev,status);
if ((report & SAA7134_IRQ_REPORT_DONE_RA3))
saa7134_irq_oss_done(dev,status);
};
if (10 == loop) {
print_irqstatus(dev,loop,report,status);
printk(KERN_WARNING "%s/irq: looping -- clearing enable bits\n",dev->name);
/* disable all irqs */
saa_writel(SAA7134_IRQ1,0);
saa_writel(SAA7134_IRQ2,0);
}
}
/* ------------------------------------------------------------------ */
static int saa7134_hwinit(struct saa7134_dev *dev)
{
init_MUTEX(&dev->lock);
dev->slock = SPIN_LOCK_UNLOCKED;
saa7134_track_gpio(dev,"pre-init");
saa7134_tvaudio_init(dev);
saa7134_video_init(dev);
saa7134_vbi_init(dev);
if (card_has_ts(dev))
saa7134_ts_init(dev);
if (card_has_audio(dev))
saa7134_oss_init(dev);
/* 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);
/* IRQ's */
saa_writel(SAA7134_IRQ1, 0);
saa_writel(SAA7134_IRQ2,
SAA7134_IRQ2_INTE_SC2 |
SAA7134_IRQ2_INTE_SC1 |
SAA7134_IRQ2_INTE_SC0 |
/* SAA7134_IRQ2_INTE_DEC5 | FIXME: TRIG_ERR ??? */
SAA7134_IRQ2_INTE_DEC3 |
SAA7134_IRQ2_INTE_DEC2 |
/* SAA7134_IRQ2_INTE_DEC1 | */
SAA7134_IRQ2_INTE_DEC0 |
SAA7134_IRQ2_INTE_PE |
SAA7134_IRQ2_INTE_AR);
/* enable peripheral devices */
saa_writeb(SAA7134_SPECIAL_MODE, 0x01);
return 0;
}
static void __devinit must_configure_manually(void)
{
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 int __devinit saa7134_initdev(struct pci_dev *pci_dev,
const struct pci_device_id *pci_id)
{
struct saa7134_dev *dev;
int err;
dev = kmalloc(sizeof(*dev),GFP_KERNEL);
if (NULL == dev)
return -ENOMEM;
memset(dev,0,sizeof(*dev));
/* pci stuff */
dev->pci = pci_dev;
if (pci_enable_device(pci_dev)) {
err = -EIO;
goto fail1;
}
sprintf(dev->name,"saa%x[%d]",pci_dev->device,saa7134_devcount);
if (-1 != 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);
}
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_dev->slot_name, 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[saa7134_devcount] >= 0 &&
card[saa7134_devcount] < saa7134_bcount)
dev->board = card[saa7134_devcount];
if (SAA7134_BOARD_NOAUTO == dev->board) {
must_configure_manually();
dev->board = SAA7134_BOARD_UNKNOWN;
}
dev->tuner_type = saa7134_boards[dev->board].tuner_type;
if (-1 != tuner[saa7134_devcount])
dev->tuner_type = tuner[saa7134_devcount];
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[saa7134_devcount] == 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*)dev->lmmio;
/* 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 fail2;
}
/* initialize hardware */
saa7134_hwinit(dev);
/* register i2c bus + load i2c helpers */
saa7134_i2c_register(dev);
if (TUNER_ABSENT != card(dev).tuner_type)
request_module("tuner");
if (saa7134_boards[dev->board].need_tda9887)
request_module("tda9887");
/* register v4l devices */
dev->video_dev = saa7134_video_template;
dev->video_dev.priv = dev;
err = video_register_device(&dev->video_dev,VFL_TYPE_GRABBER,video_nr);
if (err < 0) {
printk(KERN_INFO "%s: can't register video device\n",
dev->name);
goto fail3;
}
printk(KERN_INFO "%s: registered device video%d [v4l2]\n",
dev->name,dev->video_dev.minor & 0x1f);
dev->ts_dev = saa7134_ts_template;
dev->ts_dev.priv = dev;
if (card_has_ts(dev)) {
err = video_register_device(&dev->ts_dev,VFL_TYPE_GRABBER,ts_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 [ts]\n",
dev->name,dev->ts_dev.minor & 0x1f);
}
dev->vbi_dev = saa7134_vbi_template;
dev->vbi_dev.priv = dev;
err = video_register_device(&dev->vbi_dev,VFL_TYPE_VBI,vbi_nr);
if (err < 0)
goto fail5;
printk(KERN_INFO "%s: registered device vbi%d\n",
dev->name,dev->vbi_dev.minor & 0x1f);
dev->radio_dev = saa7134_radio_template;
dev->radio_dev.priv = dev;
if (card_has_radio(dev)) {
err = video_register_device(&dev->radio_dev,VFL_TYPE_RADIO,radio_nr);
if (err < 0)
goto fail6;
printk(KERN_INFO "%s: registered device radio%d\n",
dev->name,dev->radio_dev.minor & 0x1f);
}
/* register oss devices */
if (card_has_audio(dev)) {
dev->oss.minor_dsp = register_sound_dsp(&saa7134_dsp_fops,dsp_nr);
if (dev->oss.minor_dsp < 0)
goto fail7;
printk(KERN_INFO "%s: registered device dsp%d\n",
dev->name,dev->oss.minor_dsp >> 4);
dev->oss.minor_mixer =
register_sound_mixer(&saa7134_mixer_fops,mixer_nr);
if (dev->oss.minor_mixer < 0)
goto fail8;
printk(KERN_INFO "%s: registered device mixer%d\n",
dev->name,dev->oss.minor_mixer >> 4);
}
/* everything worked */
list_add_tail(&dev->devlist,&saa7134_devlist);
pci_set_drvdata(pci_dev,dev);
saa7134_devcount++;
return 0;
fail8:
if (card_has_audio(dev))
unregister_sound_dsp(dev->oss.minor_dsp);
fail7:
if (card_has_radio(dev))
video_unregister_device(&dev->radio_dev);
fail6:
video_unregister_device(&dev->vbi_dev);
fail5:
if (card_has_ts(dev))
video_unregister_device(&dev->ts_dev);
fail4:
video_unregister_device(&dev->video_dev);
fail3:
if (card_has_audio(dev))
saa7134_oss_fini(dev);
if (card_has_ts(dev))
saa7134_ts_fini(dev);
saa7134_vbi_fini(dev);
saa7134_video_fini(dev);
saa7134_tvaudio_fini(dev);
saa7134_i2c_unregister(dev);
free_irq(pci_dev->irq, dev);
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);
/* 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 */
if (card_has_audio(dev))
saa7134_oss_fini(dev);
if (card_has_ts(dev))
saa7134_ts_fini(dev);
saa7134_vbi_fini(dev);
saa7134_video_fini(dev);
saa7134_tvaudio_fini(dev);
saa7134_i2c_unregister(dev);
/* release ressources */
free_irq(pci_dev->irq, dev);
release_mem_region(pci_resource_start(pci_dev,0),
pci_resource_len(pci_dev,0));
/* unregister */
saa7134_i2c_unregister(dev);
if (card_has_audio(dev)) {
unregister_sound_mixer(dev->oss.minor_mixer);
unregister_sound_dsp(dev->oss.minor_dsp);
}
if (card_has_radio(dev))
video_unregister_device(&dev->radio_dev);
video_unregister_device(&dev->vbi_dev);
if (card_has_ts(dev))
video_unregister_device(&dev->ts_dev);
video_unregister_device(&dev->video_dev);
pci_set_drvdata(pci_dev, NULL);
#if 0
/* causes some trouble when reinserting the driver ... */
pci_disable_device(pci_dev);
#endif
/* free memory */
list_del(&dev->devlist);
saa7134_devcount--;
kfree(dev);
}
static struct pci_driver saa7134_pci_driver = {
name: "saa7134",
id_table: saa7134_pci_tbl,
probe: saa7134_initdev,
remove: 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);
return pci_module_init(&saa7134_pci_driver);
}
static void saa7134_fini(void)
{
pci_unregister_driver(&saa7134_pci_driver);
}
module_init(saa7134_init);
module_exit(saa7134_fini);
/* ----------------------------------------------------------- */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* 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.
*/
#define __NO_VERSION__ 1
#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 "saa7134-reg.h"
#include "saa7134.h"
#include "tuner.h"
#include "id.h"
/* ----------------------------------------------------------- */
static unsigned int i2c_debug = 0;
MODULE_PARM(i2c_debug,"i");
MODULE_PARM_DESC(i2c_debug,"enable debug messages [i2c]");
static unsigned int i2c_scan = 0;
MODULE_PARM(i2c_scan,"i");
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;
if (need_resched())
schedule();
else
udelay(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;
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;
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 */
addr = msgs[i].addr << 1;
if (msgs[i].flags & I2C_M_RD)
addr |= 1;
d1printk(" < %02x", addr);
rc = i2c_send_byte(dev,START,addr);
if (rc < 0)
goto err;
}
if (msgs[i].flags & I2C_M_RD) {
/* read bytes */
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 */
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;
}
}
}
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 void inc_use(struct i2c_adapter *adap)
{
MOD_INC_USE_COUNT;
}
static void dec_use(struct i2c_adapter *adap)
{
MOD_DEC_USE_COUNT;
}
static int attach_inform(struct i2c_client *client)
{
struct saa7134_dev *dev = client->adapter->algo_data;
int tuner = card(dev).tuner_type;
saa7134_i2c_call_clients(dev,TUNER_SET_TYPE,&tuner);
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 = {
name: "saa7134",
id: I2C_ALGO_SAA7134,
algo: &saa7134_algo,
inc_use: inc_use,
dec_use: dec_use,
client_register: attach_inform,
};
static struct i2c_client saa7134_client_template = {
name: "saa7134 internal",
id: -1,
};
/* ----------------------------------------------------------- */
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 int
saa7134_i2c_scan(struct saa7134_dev *dev)
{
unsigned char buf;
int i,rc;
for (i = 0; i < 256; i+= 2) {
dev->i2c_client.addr = i >> 1;
rc = i2c_master_recv(&dev->i2c_client,&buf,0);
if (rc < 0)
continue;
printk("%s: i2c scan: found device @ %x%s\n",
dev->name, i, (i == 0xa0) ? " [eeprom]" : "");
}
return 0;
}
void saa7134_i2c_call_clients(struct saa7134_dev *dev,
unsigned int cmd, void *arg)
{
int i;
for (i = 0; i < I2C_CLIENT_MAX; i++) {
if (NULL == dev->i2c_adap.clients[i])
continue;
if (NULL == dev->i2c_adap.clients[i]->driver->command)
continue;
dev->i2c_adap.clients[i]->driver->command
(dev->i2c_adap.clients[i],cmd,arg);
}
}
int saa7134_i2c_register(struct saa7134_dev *dev)
{
dev->i2c_adap = saa7134_adap_template;
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)
saa7134_i2c_scan(dev);
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:
*/
/*
* 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.
*/
#define __NO_VERSION__ 1
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.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_PARM(oss_debug,"i");
MODULE_PARM_DESC(oss_debug,"enable debug messages [oss]");
static unsigned int oss_rate = 0;
MODULE_PARM(oss_rate,"i");
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 = 0x100;
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();
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 int dsp_rec_start(struct saa7134_dev *dev)
{
int err, fmt, bswap, wswap;
unsigned long control,flags;
/* prepare buffer */
if (0 != (err = videobuf_dma_pci_map(dev->pci,&dev->oss.dma)))
return err;
if (0 != (err = saa7134_pgtable_build(dev->pci,&dev->oss.pt,
dev->oss.dma.sglist,
dev->oss.dma.sglen,
0)))
goto fail1;
/* sample format */
switch (dev->oss.afmt) {
case AFMT_U8: fmt = 0x00; break;
case AFMT_S8: fmt = 0x00 | 0x04; break;
case AFMT_U16_LE:
case AFMT_U16_BE: fmt = 0x01; break;
case AFMT_S16_LE:
case AFMT_S16_BE: fmt = 0x01 | 0x04; break;
/* 4front API specs mention these ones,
the (2.4.15) kernel header hasn't them ... */
#ifdef AFMT_S32_LE
case AFMT_S32_LE:
case AFMT_S32_BE: fmt = 0x02 | 0x04; break;
#endif
default:
err = -EINVAL;
goto fail2;
}
switch (dev->oss.afmt) {
case AFMT_U16_BE:
case AFMT_S16_BE: bswap = 1; wswap = 0; break;
#ifdef AFMT_S32_LE
case AFMT_S32_BE: bswap = 1; wswap = 1; break;
#endif
default: bswap = 0; wswap = 0; break;
}
if (1 == dev->oss.channels)
fmt |= (1 << 3);
if (2 == dev->oss.channels)
fmt |= (3 << 3);
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);
dprintk("rec_start: afmt=%d ch=%d => fmt=0x%x swap=%c%c\n",
dev->oss.afmt, dev->oss.channels, fmt,
bswap ? 'b' : '-', wswap ? 'w' : '-');
/* 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;
if (wswap)
control |= SAA7134_RS_CONTROL_WSWAP;
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 */
spin_lock_irqsave(&dev->slock,flags);
dev->oss.recording = 1;
dev->oss.dma_blk = 0;
saa7134_set_dmabits(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 */
spin_lock_irqsave(&dev->slock,flags);
dev->oss.dma_blk = -1;
dev->oss.recording = 0;
saa7134_set_dmabits(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)
{
unsigned int minor = minor(inode->i_rdev);
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)
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 *buffer,
size_t count, loff_t *ppos)
{
struct saa7134_dev *dev = file->private_data;
DECLARE_WAITQUEUE(wait, current);
int bytes,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) {
err = dsp_rec_start(dev);
if (err < 0) {
if (0 == ret)
ret = err;
break;
}
}
if (file->f_flags & O_NONBLOCK) {
if (0 == ret)
ret = -EAGAIN;
break;
}
up(&dev->oss.lock);
current->state = TASK_INTERRUPTIBLE;
schedule();
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);
current->state = TASK_RUNNING;
return ret;
}
static ssize_t dsp_write(struct file *file, const char *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;
int val = 0;
if (oss_debug > 1)
saa7134_print_ioctl(dev->name,cmd);
switch (cmd) {
case OSS_GETVERSION:
return put_user(SOUND_VERSION, (int *)arg);
case SNDCTL_DSP_GETCAPS:
return 0;
case SNDCTL_DSP_SPEED:
if (get_user(val, (int*)arg))
return -EFAULT;
/* fall through */
case SOUND_PCM_READ_RATE:
return put_user(dev->oss.rate, (int*)arg);
case SNDCTL_DSP_STEREO:
if (get_user(val, (int*)arg))
return -EFAULT;
down(&dev->oss.lock);
dev->oss.channels = val ? 2 : 1;
if (dev->oss.recording) {
dsp_rec_stop(dev);
dsp_rec_start(dev);
}
up(&dev->oss.lock);
return put_user(dev->oss.channels-1, (int *)arg);
case SNDCTL_DSP_CHANNELS:
if (get_user(val, (int*)arg))
return -EFAULT;
if (val != 1 && val != 2)
return -EINVAL;
down(&dev->oss.lock);
dev->oss.channels = val;
if (dev->oss.recording) {
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, (int *)arg);
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, (int*)arg);
case SNDCTL_DSP_SETFMT: /* Selects ONE fmt */
if (get_user(val, (int*)arg))
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:
#ifdef AFMT_S32_LE
case AFMT_S32_LE:
case AFMT_S32_BE:
#endif
down(&dev->oss.lock);
dev->oss.afmt = val;
if (dev->oss.recording) {
dsp_rec_stop(dev);
dsp_rec_start(dev);
}
up(&dev->oss.lock);
return put_user(dev->oss.afmt,(int*)arg);
default:
return -EINVAL;
}
case SOUND_PCM_READ_BITS:
switch (dev->oss.afmt) {
case AFMT_U8:
case AFMT_S8:
return put_user(8, (int*)arg);
case AFMT_U16_LE:
case AFMT_U16_BE:
case AFMT_S16_LE:
case AFMT_S16_BE:
return put_user(16, (int*)arg);
#ifdef AFMT_S32_LE
case AFMT_S32_LE:
case AFMT_S32_BE:
return put_user(20, (int*)arg);
#endif
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)
dsp_rec_stop(dev);
up(&dev->oss.lock);
return 0;
case SNDCTL_DSP_GETBLKSIZE:
return put_user(dev->oss.blksize,(int*)arg);
case SNDCTL_DSP_SETFRAGMENT:
if (get_user(val, (int*)arg))
return -EFAULT;
if (dev->oss.recording)
return -EBUSY;
dsp_buffer_free(dev);
dsp_buffer_conf(dev,1 << (val & 0xffff), (arg >> 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((void *)arg, &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)
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(struct saa7134_dev *dev, enum saa7134_audio_in src)
{
static const char *iname[] = { "Oops", "TV", "LINE1", "LINE2" };
int analog_io,rate;
dev->oss.count++;
dev->oss.input = src;
dprintk("mixer input = %s\n",iname[dev->oss.input]);
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:
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_level(struct saa7134_dev *dev, enum saa7134_audio_in src, int level)
{
switch (src) {
case TV:
/* nothing */
break;
case LINE1:
saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x10,
(100 == level) ? 0x00 : 0x10);
break;
case LINE2:
saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x20,
(100 == level) ? 0x00 : 0x20);
break;
}
return 0;
}
static int mixer_open(struct inode *inode, struct file *file)
{
unsigned int minor = minor(inode->i_rdev);
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;
if (oss_debug > 1)
saa7134_print_ioctl(dev->name,cmd);
switch (cmd) {
case OSS_GETVERSION:
return put_user(SOUND_VERSION, (int *)arg);
case SOUND_MIXER_INFO:
{
mixer_info info;
memset(&info,0,sizeof(info));
strncpy(info.id, "TV audio", sizeof(info.id)-1);
strncpy(info.name, dev->name, sizeof(info.name)-1);
info.modify_counter = dev->oss.count;
if (copy_to_user((void *)arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
case SOUND_OLD_MIXER_INFO:
{
_old_mixer_info info;
memset(&info,0,sizeof(info));
strncpy(info.id, "TV audio", sizeof(info.id)-1);
strncpy(info.name, dev->name, sizeof(info.name)-1);
if (copy_to_user((void *)arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
case MIXER_READ(SOUND_MIXER_CAPS):
return put_user(SOUND_CAP_EXCL_INPUT,(int*)arg);
case MIXER_READ(SOUND_MIXER_STEREODEVS):
return put_user(0,(int*)arg);
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,(int*)arg);
case MIXER_WRITE(SOUND_MIXER_RECSRC):
if (get_user(val, (int *)arg))
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,(int*)arg);
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,(int*)arg);
case MIXER_WRITE(SOUND_MIXER_LINE1):
if (get_user(val, (int *)arg))
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,
(int*)arg);
case MIXER_WRITE(SOUND_MIXER_LINE2):
if (get_user(val, (int *)arg))
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,
(int*)arg);
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_init(struct saa7134_dev *dev)
{
init_MUTEX(&dev->oss.lock);
init_waitqueue_head(&dev->oss.wq);
dev->oss.line1 = 50;
dev->oss.line2 = 50;
mixer_level(dev,LINE1,dev->oss.line1);
mixer_level(dev,LINE2,dev->oss.line2);
dev->oss.rate = 32000;
if (oss_rate)
dev->oss.rate = oss_rate;
if (saa7134_boards[dev->board].i2s_rate)
dev->oss.rate = saa7134_boards[dev->board].i2s_rate;
dev->oss.rate = (dev->oss.rate > 40000) ? 48000 : 32000;
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 (-1 == dev->oss.dma_blk) {
dprintk("irq: recording stopped%s\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_rec_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:
*/
/*
* 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_SAA7134
# define PCI_DEVICE_ID_PHILIPS_SAA7134 0x7134
#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_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_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
/* ------------------------------------------------------------------ */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* 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.
*/
#define __NO_VERSION__ 1
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "saa7134-reg.h"
#include "saa7134.h"
/* ------------------------------------------------------------------ */
static unsigned int ts_debug = 0;
MODULE_PARM(ts_debug,"i");
MODULE_PARM_DESC(ts_debug,"enable debug messages [ts]");
static unsigned int tsbufs = 4;
MODULE_PARM(tsbufs,"i");
MODULE_PARM_DESC(tsbufs,"number of ts buffers, range 2-32");
#define TS_PACKET_SIZE 188 /* TS packets 188 bytes */
#define TS_NR_PACKETS 312
#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)
{
unsigned long control,status;
dprintk("buffer_activate [%p]\n",buf);
buf->vb.state = STATE_ACTIVE;
/* dma: setup channel 5 (= TS) */
control = SAA7134_RS_CONTROL_BURST_16 |
SAA7134_RS_CONTROL_ME |
(buf->pt->dma >> 12);
status = saa_readl(SAA7134_IRQ_STATUS);
if (0 == (status & 0x100000)) {
saa_writel(SAA7134_RS_BA1(5),saa7134_buffer_base(buf));
saa_writel(SAA7134_RS_BA2(5),saa7134_buffer_base(next));
} else {
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 file *file, struct videobuf_buffer *vb)
{
struct saa7134_dev *dev = file->private_data;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
int lines, llength, size, err;
llength = TS_PACKET_SIZE;
lines = 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);
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->top_seen = 0;
buf->activate = buffer_activate;
buf->vb.field = V4L2_FIELD_SEQ_TB;
return 0;
oops:
saa7134_dma_free(dev,buf);
return err;
}
static int
buffer_setup(struct file *file, int *count, int *size)
{
*size = TS_PACKET_SIZE * TS_NR_PACKETS;
if (0 == *count)
*count = tsbufs;
*count = saa7134_buffer_count(*size,*count);
return 0;
}
static void buffer_queue(struct file *file, struct videobuf_buffer *vb)
{
struct saa7134_dev *dev = file->private_data;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
saa7134_buffer_queue(dev,&dev->ts_q,buf);
}
static void buffer_release(struct file *file, struct videobuf_buffer *vb)
{
struct saa7134_dev *dev = file->private_data;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
saa7134_dma_free(dev,buf);
}
static struct videobuf_queue_ops ts_qops = {
buf_setup: buffer_setup,
buf_prepare: buffer_prepare,
buf_queue: buffer_queue,
buf_release: buffer_release,
};
/* ------------------------------------------------------------------ */
static int ts_open(struct inode *inode, struct file *file)
{
unsigned int minor = minor(inode->i_rdev);
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->ts_dev.minor == minor)
dev = h;
}
if (NULL == dev)
return -ENODEV;
dprintk("open minor=%d\n",minor);
down(&dev->ts.ts.lock);
err = -EBUSY;
if (dev->ts.users)
goto done;
dev->ts.users++;
file->private_data = dev;
err = 0;
done:
up(&dev->ts.ts.lock);
return err;
}
static int ts_release(struct inode *inode, struct file *file)
{
struct saa7134_dev *dev = file->private_data;
if (dev->ts.ts.streaming)
videobuf_streamoff(file,&dev->ts.ts);
down(&dev->ts.ts.lock);
if (dev->ts.ts.reading)
videobuf_read_stop(file,&dev->ts.ts);
dev->ts.users--;
up(&dev->ts.ts.lock);
return 0;
}
static ssize_t
ts_read(struct file *file, char *data, size_t count, loff_t *ppos)
{
struct saa7134_dev *dev = file->private_data;
return videobuf_read_stream(file, &dev->ts.ts, data, count, ppos, 0);
}
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->ts.ts, wait);
}
static int
ts_mmap(struct file *file, struct vm_area_struct * vma)
{
struct saa7134_dev *dev = file->private_data;
return videobuf_mmap_mapper(vma, &dev->ts.ts);
}
/*
* 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 (ts_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");
strncpy(cap->card, saa7134_boards[dev->board].name,
sizeof(cap->card));
sprintf(cap->bus_info,"PCI:%s",dev->pci->slot_name);
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;
strncpy(f->description, "MPEG TS", 31);
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*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*TS_NR_PACKETS;
}
case VIDIOC_REQBUFS:
return videobuf_reqbufs(file,&dev->ts.ts,arg);
case VIDIOC_QUERYBUF:
return videobuf_querybuf(&dev->ts.ts,arg);
case VIDIOC_QBUF:
return videobuf_qbuf(file,&dev->ts.ts,arg);
case VIDIOC_DQBUF:
return videobuf_dqbuf(file,&dev->ts.ts,arg);
case VIDIOC_STREAMON:
return videobuf_streamon(file,&dev->ts.ts);
case VIDIOC_STREAMOFF:
return videobuf_streamoff(file,&dev->ts.ts);
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,
};
/* ----------------------------------------------------------- */
/* exported stuff */
struct video_device saa7134_ts_template =
{
name: "saa7134-ts",
type: 0 /* FIXME */,
type2: 0 /* FIXME */,
hardware: 0,
fops: &ts_fops,
minor: -1,
};
int saa7134_ts_init(struct saa7134_dev *dev)
{
/* sanitycheck insmod options */
if (tsbufs < 2)
tsbufs = 2;
if (tsbufs > VIDEO_MAX_FRAME)
tsbufs = VIDEO_MAX_FRAME;
INIT_LIST_HEAD(&dev->ts_q.queue);
dev->ts_q.timeout.function = saa7134_buffer_timeout;
dev->ts_q.timeout.data = (unsigned long)(&dev->ts_q);
dev->ts_q.dev = dev;
videobuf_queue_init(&dev->ts.ts, &ts_qops, dev->pci, &dev->slock,
V4L2_BUF_TYPE_VIDEO_CAPTURE,
sizeof(struct saa7134_buf));
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, ((TS_NR_PACKETS-1)&0xff));
saa_writeb(SAA7134_TS_DMA1, (((TS_NR_PACKETS-1)>>8)&0xff));
saa_writeb(SAA7134_TS_DMA2, ((((TS_NR_PACKETS-1)>>16)&0x3f) | 0x00)); /* TSNOPIT=0, TSCOLAP=0 */
return 0;
}
int saa7134_ts_fini(struct saa7134_dev *dev)
{
/* nothing */
saa7134_pgtable_free(dev->pci,&dev->ts.pt_ts);
return 0;
}
void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status)
{
spin_lock(&dev->slock);
if (dev->ts_q.curr) {
saa7134_buffer_finish(dev,&dev->ts_q,STATE_DONE);
}
saa7134_buffer_next(dev,&dev->ts_q);
spin_unlock(&dev->slock);
}
/* ----------------------------------------------------------- */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* device driver for philips saa7134 based TV cards
* tv audio decoder (fm stereo, nicam, ...)
*
* (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.
*/
#define __NO_VERSION__ 1
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <asm/div64.h>
#include "saa7134-reg.h"
#include "saa7134.h"
/* ------------------------------------------------------------------ */
static unsigned int audio_debug = 0;
MODULE_PARM(audio_debug,"i");
MODULE_PARM_DESC(audio_debug,"enable debug messages [tv audio]");
#define dprintk(fmt, arg...) if (audio_debug) \
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)))
/* ------------------------------------------------------------------ */
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: "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;
/* init all audio registers */
saa_writeb(SAA7134_AUDIO_PLL_CTRL, 0x00);
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 saa7134_tvaudio_do_mute_input(struct saa7134_dev *dev)
{
int mute;
struct saa7134_input *in;
int reg = 0;
int mask;
/* look what is to do ... */
in = dev->input;
mute = (dev->ctl_mute || dev->automute);
if (!card_has_audio(dev) && 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)
return;
#if 1
dprintk("ctl_mute=%d automute=%d input=%s => mute=%d input=%s\n",
dev->ctl_mute,dev->automute,dev->input->name,mute,in->name);
#endif
dev->hw_mute = mute;
dev->hw_input = in;
if (card_has_audio(dev))
/* 7134 mute */
saa_writeb(SAA7134_AUDIO_MUTE_CTRL, mute ? 0xff : 0xbb);
/* switch internal audio mux */
switch (in->amux) {
case TV: reg = 0x02; break;
case LINE1: reg = 0x00; break;
case LINE2: reg = 0x01; break;
}
saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, reg);
/* 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);
}
void saa7134_tvaudio_setmute(struct saa7134_dev *dev)
{
saa7134_tvaudio_do_mute_input(dev);
}
void saa7134_tvaudio_setinput(struct saa7134_dev *dev,
struct saa7134_input *in)
{
dev->input = in;
saa7134_tvaudio_do_mute_input(dev);
}
void saa7134_tvaudio_setvolume(struct saa7134_dev *dev, int level)
{
saa_writeb(SAA7134_CHANNEL1_LEVEL, level & 0x1f);
saa_writeb(SAA7134_CHANNEL2_LEVEL, level & 0x1f);
saa_writeb(SAA7134_NICAM_LEVEL_ADJUST, level & 0x1f);
}
static void tvaudio_setmode(struct saa7134_dev *dev,
struct saa7134_tvaudio *audio,
char *note)
{
if (note)
dprintk("tvaudio_setmode: %s %s [%d.%03d/%d.%03d MHz]\n",
note,audio->name,
audio->carr1 / 1000, audio->carr1 % 1000,
audio->carr2 / 1000, audio->carr2 % 1000);
if (dev->tvnorm->id == V4L2_STD_NTSC) {
saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD0, 0xde);
saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD1, 0x15);
saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD2, 0x02);
} else {
saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD0, 0x00);
saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD1, 0x80);
saa_writeb(SAA7134_AUDIO_CLOCKS_PER_FIELD2, 0x02);
}
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);
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);
break;
case TVAUDIO_FM_SAT_STEREO:
/* not implemented (yet) */
break;
}
saa_writel(0x174 >> 2, 0x0001e000); /* FIXME */
}
int saa7134_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;
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);
switch (nicam & 0x0b) {
case 0x08:
retval = V4L2_TUNER_SUB_MONO;
break;
case 0x09:
retval = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2;
break;
case 0x0a:
retval = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
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_sleep(struct saa7134_dev *dev, int timeout)
{
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&dev->thread.wq, &wait);
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(timeout);
remove_wait_queue(&dev->thread.wq, &wait);
return dev->thread.scan1 != dev->thread.scan2;
}
static int tvaudio_checkcarrier(struct saa7134_dev *dev, int carrier)
{
__s32 left,right,value;
tvaudio_setcarrier(dev,carrier-100,carrier-100);
if (tvaudio_sleep(dev,HZ/10))
return -1;
left = saa_readl(SAA7134_LEVEL_READOUT1 >> 2);
if (tvaudio_sleep(dev,HZ/10))
return -1;
left = saa_readl(SAA7134_LEVEL_READOUT1 >> 2);
tvaudio_setcarrier(dev,carrier+100,carrier+100);
if (tvaudio_sleep(dev,HZ/10))
return -1;
right = saa_readl(SAA7134_LEVEL_READOUT1 >> 2);
if (tvaudio_sleep(dev,HZ/10))
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 => dc is %5d [%d/%d]\n",
carrier/1000,carrier%1000,value,left,right);
return value;
}
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);
}
static int tvaudio_thread(void *data)
{
#define MAX_SCAN 4
static const int carr_pal[MAX_SCAN] = { 5500, 6000, 6500 };
static const int carr_ntsc[MAX_SCAN] = { 4500 };
static const int carr_secam[MAX_SCAN] = { 6500 };
static const int carr_default[MAX_SCAN] = { 4500, 5500, 6000, 6500 };
struct saa7134_dev *dev = data;
const int *carr_scan;
int carr_vals[4];
int i,max,carrier,audio;
lock_kernel();
daemonize();
sigfillset(&current->blocked);
sprintf(current->comm, "%s", dev->name);
dev->thread.task = current;
unlock_kernel();
if (dev->thread.notify != NULL)
up(dev->thread.notify);
for (;;) {
if (dev->thread.exit || signal_pending(current))
goto done;
interruptible_sleep_on(&dev->thread.wq);
if (dev->thread.exit || 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);
dev->automute = 1;
saa7134_tvaudio_setmute(dev);
/* give the tuner some time */
if (tvaudio_sleep(dev,HZ/2))
goto restart;
/* find the main carrier */
carr_scan = carr_default;
if (dev->tvnorm->id & V4L2_STD_PAL)
carr_scan = carr_pal;
if (dev->tvnorm->id & V4L2_STD_NTSC)
carr_scan = carr_ntsc;
if (dev->tvnorm->id & V4L2_STD_SECAM)
carr_scan = carr_secam;
saa_writeb(SAA7134_MONITOR_SELECT,0x00);
tvaudio_setmode(dev,&tvaudio[0],NULL);
for (i = 0; i < MAX_SCAN; i++) {
if (!carr_scan[i])
continue;
carr_vals[i] = tvaudio_checkcarrier(dev,carr_scan[i]);
if (dev->thread.scan1 != dev->thread.scan2)
goto restart;
}
for (carrier = 0, max = 0, i = 0; i < MAX_SCAN; i++) {
if (!carr_scan[i])
continue;
if (max < carr_vals[i]) {
max = carr_vals[i];
carrier = carr_scan[i];
}
}
if (0 == carrier) {
/* Oops: autoscan didn't work for some reason :-/ */
printk("%s/audio: oops: audio carrier scan failed\n",
dev->name);
sifdebug_dump_regs(dev);
} else {
dprintk("found %s main sound carrier @ %d.%03d MHz\n",
dev->tvnorm->name,
carrier/1000,carrier%1000);
}
tvaudio_setcarrier(dev,carrier,carrier);
dev->automute = 0;
saa7134_tvaudio_setmute(dev);
/* find the exact tv audio norm */
for (audio = -1, i = 0; i < TVAUDIO; i++) {
if (dev->tvnorm->id != -1 &&
dev->tvnorm->id != tvaudio[i].std)
continue;
if (tvaudio[i].carr1 != carrier)
continue;
if (-1 == audio)
audio = i;
tvaudio_setmode(dev,&tvaudio[i],"trying");
if (tvaudio_sleep(dev,HZ))
goto restart;
if (-1 != saa7134_tvaudio_getstereo(dev,&tvaudio[i])) {
audio = i;
break;
}
}
if (-1 == audio)
continue;
tvaudio_setmode(dev,&tvaudio[audio],"using");
dev->tvaudio = &tvaudio[audio];
#if 1
if (tvaudio_sleep(dev,3*HZ))
goto restart;
saa7134_tvaudio_getstereo(dev,&tvaudio[i]);
#endif
}
done:
dev->thread.task = NULL;
if(dev->thread.notify != NULL)
up(dev->thread.notify);
return 0;
}
/* ------------------------------------------------------------------ */
int saa7134_tvaudio_init(struct saa7134_dev *dev)
{
DECLARE_MUTEX_LOCKED(sem);
/* enable I2S audio output */
if (saa7134_boards[dev->board].i2s_rate) {
int rate = (32000 == saa7134_boards[dev->board].i2s_rate) ? 0x01 : 0x03;
/* set rate */
saa_andorb(SAA7134_SIF_SAMPLE_FREQ, 0x03, rate);
/* enable I2S output */
saa_writeb(SAA7134_DSP_OUTPUT_SELECT, 0x80);
saa_writeb(SAA7134_I2S_OUTPUT_SELECT, 0x80);
saa_writeb(SAA7134_I2S_OUTPUT_FORMAT, 0x01);
saa_writeb(SAA7134_I2S_OUTPUT_LEVEL, 0x00);
saa_writeb(SAA7134_I2S_AUDIO_OUTPUT, 0x01);
}
/* start tvaudio thread */
init_waitqueue_head(&dev->thread.wq);
dev->thread.notify = &sem;
kernel_thread(tvaudio_thread,dev,0);
down(&sem);
dev->thread.notify = NULL;
wake_up_interruptible(&dev->thread.wq);
return 0;
}
int saa7134_tvaudio_fini(struct saa7134_dev *dev)
{
DECLARE_MUTEX_LOCKED(sem);
/* shutdown tvaudio thread */
if (dev->thread.task) {
dev->thread.notify = &sem;
dev->thread.exit = 1;
wake_up_interruptible(&dev->thread.wq);
down(&sem);
dev->thread.notify = NULL;
}
saa_andorb(SAA7134_ANALOG_IO_SELECT, 0x07, 0x00); /* LINE1 */
return 0;
}
int saa7134_tvaudio_do_scan(struct saa7134_dev *dev)
{
dev->thread.scan2++;
wake_up_interruptible(&dev->thread.wq);
return 0;
}
/* ----------------------------------------------------------- */
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* 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.
*/
#define __NO_VERSION__ 1
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "saa7134-reg.h"
#include "saa7134.h"
/* ------------------------------------------------------------------ */
static unsigned int vbi_debug = 0;
MODULE_PARM(vbi_debug,"i");
MODULE_PARM_DESC(vbi_debug,"enable debug messages [vbi]");
static unsigned int vbibufs = 4;
MODULE_PARM(vbibufs,"i");
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;
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 file *file, struct videobuf_buffer *vb)
{
struct saa7134_fh *fh = file->private_data;
struct saa7134_dev *dev = fh->dev;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
struct saa7134_tvnorm *norm = dev->tvnorm;
int lines, llength, size, 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);
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->top_seen = 0;
buf->activate = buffer_activate;
buf->vb.field = V4L2_FIELD_SEQ_TB;
return 0;
oops:
saa7134_dma_free(dev,buf);
return err;
}
static int
buffer_setup(struct file *file, int *count, int *size)
{
struct saa7134_fh *fh = file->private_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 file *file, struct videobuf_buffer *vb)
{
struct saa7134_fh *fh = file->private_data;
struct saa7134_dev *dev = fh->dev;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
saa7134_buffer_queue(dev,&dev->vbi_q,buf);
}
static void buffer_release(struct file *file, struct videobuf_buffer *vb)
{
struct saa7134_fh *fh = file->private_data;
struct saa7134_dev *dev = fh->dev;
struct saa7134_buf *buf = (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_init(struct saa7134_dev *dev)
{
INIT_LIST_HEAD(&dev->vbi_q.queue);
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) == 0x10) {
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:
*/
/*
* 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.
*/
#define __NO_VERSION__ 1
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "saa7134-reg.h"
#include "saa7134.h"
#include "tuner.h"
#include "audiochip.h"
/* ------------------------------------------------------------------ */
static unsigned int video_debug = 0;
static unsigned int gbuffers = 8;
static unsigned int gbufsize = 768*576*4;
static unsigned int gbufsize_max = 768*576*4;
MODULE_PARM(video_debug,"i");
MODULE_PARM_DESC(video_debug,"enable debug messages [video]");
MODULE_PARM(gbuffers,"i");
MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32");
#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: "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,
}
};
#define FORMATS (sizeof(formats)/sizeof(struct saa7134_format))
static struct saa7134_tvnorm tvnorms[] = {
{
name: "PAL-BGHI",
id: V4L2_STD_PAL,
width: 720,
height: 576,
sync_control: 0x18,
luma_control: 0x40,
chroma_ctrl1: 0x81,
chroma_gain: 0x2a,
chroma_ctrl2: 0x06,
h_start: 0,
h_stop: 719,
video_v_start: 24,
video_v_stop: 311,
vbi_v_start: 7-3, /* FIXME */
vbi_v_stop: 22-3,
},{
name: "NTSC-M",
id: V4L2_STD_NTSC,
width: 720,
height: 480,
sync_control: 0x59,
luma_control: 0x40,
chroma_ctrl1: 0x89,
chroma_gain: 0x2a,
chroma_ctrl2: 0x0e,
h_start: 0,
h_stop: 719,
video_v_start: 22,
video_v_stop: 22+240,
vbi_v_start: 10, /* FIXME */
vbi_v_stop: 21, /* FIXME */
},{
name: "SECAM",
id: V4L2_STD_SECAM,
width: 720,
height: 576,
sync_control: 0x58,
luma_control: 0x1b,
chroma_ctrl1: 0xd1,
chroma_gain: 0x80,
chroma_ctrl2: 0x00,
h_start: 0,
h_stop: 719,
video_v_start: 24,
video_v_stop: 311,
vbi_v_start: 7,
vbi_v_stop: 22,
},{
name: "AUTO",
id: -1,
width: 768,
height: 576,
sync_control: 0x98,
luma_control: 0x40,
chroma_ctrl1: 0x8b,
chroma_gain: 0x00,
chroma_ctrl2: 0x00,
h_start: 0,
h_stop: 719,
video_v_start: 24,
video_v_stop: 311,
vbi_v_start: 7,
vbi_v_stop: 22,
}
};
#define TVNORMS (sizeof(tvnorms)/sizeof(struct saa7134_tvnorm))
#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_LASTP1 (V4L2_CID_PRIVATE_BASE + 3)
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,
}
};
const int CTRLS = (sizeof(video_ctrls)/sizeof(struct v4l2_queryctrl));
static const struct v4l2_queryctrl* ctrl_by_id(int id)
{
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(int fourcc)
{
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, 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, int bit)
{
return (fh->resources & bit);
}
static
int res_locked(struct saa7134_dev *dev, int bit)
{
return (dev->resources & bit);
}
static
void res_free(struct saa7134_dev *dev, struct saa7134_fh *fh, 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)
{
struct video_channel c;
int luma_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;
if (mux > 5)
luma_control |= 0x80; /* svideo */
/* 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_SYNC_CTRL, norm->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, 0x18); /* FIXME */
saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40);
saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80);
/* pass down info to the i2c chips (v4l1) */
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);
}
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 = sizeof(vals)/sizeof(vals[0]);
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)
{
struct saa7134_tvnorm *norm = dev->tvnorm;
int prescale,xscale,yscale,y_even,y_odd;
int div = interlace ? 2 : 1;
/* setup video scaler */
saa_writeb(SAA7134_VIDEO_H_START1(task), norm->h_start & 0xff);
saa_writeb(SAA7134_VIDEO_H_START2(task), norm->h_start >> 8);
saa_writeb(SAA7134_VIDEO_H_STOP1(task), norm->h_stop & 0xff);
saa_writeb(SAA7134_VIDEO_H_STOP2(task), norm->h_stop >> 8);
saa_writeb(SAA7134_VIDEO_V_START1(task), norm->video_v_start & 0xff);
saa_writeb(SAA7134_VIDEO_V_START2(task), norm->video_v_start >> 8);
saa_writeb(SAA7134_VIDEO_V_STOP1(task), norm->video_v_stop & 0xff);
saa_writeb(SAA7134_VIDEO_V_STOP2(task), norm->video_v_stop >> 8);
prescale = norm->width / width;
if (0 == prescale)
prescale = 1;
xscale = 1024 * norm->width / prescale / width;
yscale = 512 * div * norm->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 */
if (interlace) {
y_odd = dev->ctl_y_odd;
y_even = dev->ctl_y_even + yscale / 32;
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);
} else {
y_odd = dev->ctl_y_odd;
y_even = dev->ctl_y_even + yscale / 64;
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->tvnorm->width;
maxh = dev->tvnorm->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;
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; /* planar */
dprintk("buffer_activate buf=%p\n",buf);
buf->vb.state = STATE_ACTIVE;
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;
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 file *file, struct videobuf_buffer *vb)
{
struct saa7134_fh *fh = file->private_data;
struct saa7134_dev *dev = fh->dev;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
int size,err;
/* sanity checks */
if (NULL == fh->fmt)
return -EINVAL;
if (fh->width < 48 ||
fh->height < 32 ||
fh->width > dev->tvnorm->width ||
fh->height > dev->tvnorm->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 [size=%dx%d,bytes=%d,fields=%s,%s]\n",
fh->width,fh->height,size,v4l2_field_names[fh->field],
fh->fmt->name);
if (buf->vb.width != fh->width ||
buf->vb.height != fh->height ||
buf->vb.size != size ||
buf->vb.field != fh->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->fmt = fh->fmt;
buf->vb.field = fh->field;
buf->pt = &fh->pt_cap;
err = videobuf_iolock(dev->pci,&buf->vb);
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->top_seen = 0;
buf->activate = buffer_activate;
return 0;
oops:
saa7134_dma_free(dev,buf);
return err;
}
static int
buffer_setup(struct file *file, int *count, int *size)
{
struct saa7134_fh *fh = file->private_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 file *file, struct videobuf_buffer *vb)
{
struct saa7134_fh *fh = file->private_data;
struct saa7134_buf *buf = (struct saa7134_buf *)vb;
saa7134_buffer_queue(fh->dev,&fh->dev->video_q,buf);
}
static void buffer_release(struct file *file, struct videobuf_buffer *vb)
{
struct saa7134_fh *fh = file->private_data;
struct saa7134_buf *buf = (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;
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;
default:
return -EINVAL;
}
if (restart_overlay && 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)
{
unsigned int minor = minor(inode->i_rdev);
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.minor == minor)
dev = h;
if (h->radio_dev.minor == minor) {
radio = 1;
dev = h;
}
if (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 = 320;
fh->height = 240;
videobuf_queue_init(&fh->cap, &video_qops,
dev->pci, &dev->slock,
V4L2_BUF_TYPE_VIDEO_CAPTURE,
sizeof(struct saa7134_buf));
init_MUTEX(&fh->cap.lock);
saa7134_pgtable_alloc(dev->pci,&fh->pt_cap);
videobuf_queue_init(&fh->vbi, &saa7134_vbi_qops,
dev->pci, &dev->slock,
V4L2_BUF_TYPE_VBI_CAPTURE,
sizeof(struct saa7134_buf));
init_MUTEX(&fh->vbi.lock);
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 */
set_tvnorm(dev,dev->tvnorm);
video_mux(dev,dev->ctl_input);
}
return 0;
}
static ssize_t
video_read(struct file *file, char *data, size_t count, loff_t *ppos)
{
struct saa7134_fh *fh = file->private_data;
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
return videobuf_read_one(file, saa7134_queue(fh),
data, count, ppos);
case V4L2_BUF_TYPE_VBI_CAPTURE:
return videobuf_read_stream(file, saa7134_queue(fh),
data, count, ppos, 1);
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 (-1 == 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(file,fh->cap.read_buf)) {
up(&fh->cap.lock);
return POLLERR;
}
fh->cap.ops->buf_queue(file,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;
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);
}
if (res_check(fh, RESOURCE_VIDEO)) {
videobuf_queue_cancel(file,&fh->cap);
res_free(dev,fh,RESOURCE_VIDEO);
}
if (fh->cap.read_buf) {
buffer_release(file,fh->cap.read_buf);
kfree(fh->cap.read_buf);
}
if (fh->vbi.streaming)
videobuf_streamoff(file,&fh->vbi);
if (fh->vbi.reading)
videobuf_read_stop(file,&fh->vbi);
saa7134_pgtable_free(dev->pci,&fh->pt_cap);
saa7134_pgtable_free(dev->pci,&fh->pt_vbi);
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(vma,saa7134_queue(fh));
}
/* ------------------------------------------------------------------ */
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.pixelformat = fh->fmt->fourcc;
f->fmt.pix.sizeimage =
(fh->width*fh->height*fh->fmt->depth)/8;
return 0;
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
f->fmt.win = fh->win;
return 0;
case V4L2_BUF_TYPE_VBI_CAPTURE:
{
struct saa7134_tvnorm *norm = fh->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 */;
return 0;
}
default:
return -EINVAL;
}
}
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;
int maxw, maxh;
fmt = format_by_fourcc(f->fmt.pix.pixelformat);
if (NULL == fmt)
return -EINVAL;
field = f->fmt.pix.field;
maxw = dev->tvnorm->width;
maxh = dev->tvnorm->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 > maxw)
f->fmt.pix.width = maxw;
if (f->fmt.pix.height > maxh)
f->fmt.pix.height = maxh;
f->fmt.pix.sizeimage =
(fh->width * fh->height * fmt->depth)/8;
return 0;
}
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
err = verify_preview(dev,&f->fmt.win);
if (0 != err)
return err;
return 0;
default:
return -EINVAL;
}
}
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->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;
break;
default:
return -EINVAL;
}
}
/*
* This function is _not_ called directly, but from
* video_generic_ioctl (and maybe others). userspace
* copying is done already, arg is a kernel pointer.
*/
static int video_do_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, void *arg)
{
struct 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_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap,0,sizeof(*cap));
strcpy(cap->driver, "saa7134");
strncpy(cap->card, saa7134_boards[dev->board].name,
sizeof(cap->card));
sprintf(cap->bus_info,"PCI:%s",dev->pci->slot_name);
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;
if (e->index < 0 || e->index >= TVNORMS)
return -EINVAL;
err = v4l2_video_std_construct(e, tvnorms[e->index].id,
tvnorms[e->index].name);
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;
int i;
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]);
up(&dev->lock);
return 0;
}
/* --- input switching --------------------------------------- */
case VIDIOC_ENUMINPUT:
{
struct v4l2_input *i = arg;
if (i->index >= SAA7134_INPUT_MAX)
return -EINVAL;
if (NULL == card_in(dev,i->index).name)
return -EINVAL;
i->type = V4L2_INPUT_TYPE_CAMERA;
strcpy(i->name,card_in(dev,i->index).name);
if (card_in(dev,i->index).tv)
i->type = V4L2_INPUT_TYPE_TUNER;
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;
}
/* --- tuner ioctls ------------------------------------------ */
case VIDIOC_G_TUNER:
{
struct v4l2_tuner *t = arg;
int n;
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;
if (dev->tvaudio) {
t->rxsubchans = saa7134_tvaudio_getstereo
(dev,dev->tvaudio);
} else {
t->rxsubchans = V4L2_TUNER_SUB_MONO;
}
#if 1
/* fill audmode -- FIXME: allow manual switching */
t->audmode = V4L2_TUNER_MODE_MONO;
if (t->rxsubchans & V4L2_TUNER_SUB_STEREO)
t->audmode = V4L2_TUNER_MODE_STEREO;
else if (t->rxsubchans & V4L2_TUNER_SUB_LANG1)
t->audmode = V4L2_TUNER_MODE_LANG1;
else if (t->rxsubchans & V4L2_TUNER_SUB_LANG2)
t->audmode = V4L2_TUNER_MODE_LANG2;
#endif
}
if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03))
t->signal = 0xffff;
return 0;
}
case VIDIOC_S_TUNER:
{
#if 0
struct v4l2_tuner *t = arg;
#endif
return 0;
}
case VIDIOC_G_FREQUENCY:
{
struct v4l2_frequency *f = arg;
memset(f,0,sizeof(*f));
f->type = 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 (V4L2_TUNER_ANALOG_TV != f->type)
return -EINVAL;
down(&dev->lock);
dev->ctl_freq = f->frequency;
saa7134_i2c_call_clients(dev,VIDIOCSFREQ,&dev->ctl_freq);
up(&dev->lock);
return 0;
}
/* --- control ioctls ---------------------------------------- */
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,fh,arg);
up(&dev->lock);
return err;
}
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;
}
/* --- preview ioctls ---------------------------------------- */
case VIDIOC_ENUM_FMT:
{
struct v4l2_fmtdesc *f = arg;
int index;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
index = f->index;
if (index < 0 || index >= FORMATS)
return -EINVAL;
if (f->type == V4L2_BUF_TYPE_VIDEO_OVERLAY &&
formats[index].planar)
return -EINVAL;
memset(f,0,sizeof(*f));
f->index = index;
strncpy(f->description,formats[index].name,31);
f->pixelformat = formats[index].fourcc;
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;
int i;
q = saa7134_queue(fh);
down(&q->lock);
err = videobuf_mmap_setup(file,q,gbuffers,gbufsize);
if (err < 0) {
up(&q->lock);
return err;
}
memset(mbuf,0,sizeof(*mbuf));
mbuf->frames = gbuffers;
mbuf->size = gbuffers * gbufsize;
for (i = 0; i < gbuffers; i++)
mbuf->offsets[i] = i * gbufsize;
up(&q->lock);
return 0;
}
case VIDIOC_REQBUFS:
return videobuf_reqbufs(file,saa7134_queue(fh),arg);
case VIDIOC_QUERYBUF:
return videobuf_querybuf(saa7134_queue(fh),arg);
case VIDIOC_QBUF:
return videobuf_qbuf(file,saa7134_queue(fh),arg);
case VIDIOC_DQBUF:
return videobuf_dqbuf(file,saa7134_queue(fh),arg);
case VIDIOC_STREAMON:
{
int res = saa7134_resource(fh);
if (!res_get(dev,fh,res))
return -EBUSY;
return videobuf_streamon(file,saa7134_queue(fh));
}
case VIDIOC_STREAMOFF:
{
int res = saa7134_resource(fh);
err = videobuf_streamoff(file,saa7134_queue(fh));
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");
strncpy(cap->card, saa7134_boards[dev->board].name,
sizeof(cap->card));
sprintf(cap->bus_info,"PCI:%s",dev->pci->slot_name);
cap->version = SAA7134_VERSION_CODE;
cap->capabilities = V4L2_CAP_TUNER;
return 0;
}
case VIDIOC_G_TUNER:
{
struct v4l2_tuner *t = arg;
struct video_tuner vt;
memset(t,0,sizeof(*t));
strcpy(t->name, "Radio");
t->rangelow = (int)(65*16);
t->rangehigh = (int)(108*16);
memset(&vt,0,sizeof(vt));
saa7134_i2c_call_clients(dev,VIDIOCGTUNER,&vt);
t->signal = vt.signal;
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_S_AUDIO:
case VIDIOC_S_TUNER:
case VIDIOC_S_INPUT:
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_init(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_invert = 0;
dev->ctl_mute = 1;
dev->automute = 0;
INIT_LIST_HEAD(&dev->video_q.queue);
dev->video_q.timeout.function = saa7134_buffer_timeout;
dev->video_q.timeout.data = (unsigned long)(&dev->video_q);
dev->video_q.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);
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_fini(struct saa7134_dev *dev)
{
/* nothing */
return 0;
}
void saa7134_irq_video_intl(struct saa7134_dev *dev)
{
static const char *st[] = {
"no signal", "found NTSC", "found PAL", "found SECAM" };
int norm;
norm = saa_readb(SAA7134_STATUS_VIDEO1) & 0x03;
printk("%s/video: DCSDT: %s\n",dev->name,st[norm]);
if (0 != norm) {
/* wake up tvaudio audio carrier scan thread */
saa7134_tvaudio_do_scan(dev);
} else {
/* no video signal -> mute audio */
dev->automute = 1;
saa7134_tvaudio_setmute(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) {
field = dev->video_q.curr->vb.field;
if (V4L2_FIELD_HAS_BOTH(field)) {
/* make sure we have seen both fields */
if ((status & 0x10) == 0x10) {
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;
}
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:
*/
/*
* 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/pci.h>
#include <linux/i2c.h>
#include <linux/videodev.h>
#include <linux/kdev_t.h>
#include "video-buf.h"
#define SAA7134_VERSION_CODE KERNEL_VERSION(0,2,1)
#ifndef TRUE
# define TRUE (1==1)
#endif
#ifndef FALSE
# define FALSE (1==0)
#endif
/* 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,
};
enum saa7134_video_out {
CCIR656 = 1,
};
/* ----------------------------------------------------------- */
/* static data */
struct saa7134_tvnorm {
char *name;
v4l2_std_id id;
int width;
int height;
/* video decoder */
int sync_control;
int luma_control;
int chroma_ctrl1;
int chroma_gain;
int chroma_ctrl2;
/* video scaler */
int h_start;
int h_stop;
int video_v_start;
int video_v_stop;
int vbi_v_start;
int vbi_v_stop;
};
struct saa7134_tvaudio {
char *name;
int std;
enum saa7134_tvaudio_mode mode;
int carr1;
int carr2;
};
struct saa7134_format {
char *name;
int fourcc;
int depth;
int pm;
int vshift; /* vertical downsampling (for planar yuv) */
int hshift; /* horizontal downsampling (for planar yuv) */
int bswap:1;
int wswap:1;
int yuv:1;
int planar:1;
};
/* ----------------------------------------------------------- */
/* card configuration */
#define SAA7134_BOARD_NOAUTO -1
#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_INPUT_MAX 8
struct saa7134_input {
char *name;
int vmux;
enum saa7134_audio_in amux;
int gpio;
int tv:1;
};
struct saa7134_board {
char *name;
int audio_clock;
/* input switching */
int gpiomask;
struct saa7134_input inputs[SAA7134_INPUT_MAX];
struct saa7134_input radio;
struct saa7134_input mute;
/* peripheral I/O */
int i2s_rate;
int has_ts;
enum saa7134_video_out video_out;
/* i2c chip info */
int tuner_type;
int need_tda9887:1;
};
#define card_has_audio(dev) (dev->pci->device == PCI_DEVICE_ID_PHILIPS_SAA7134)
#define card_has_radio(dev) (NULL != saa7134_boards[dev->board].radio.name)
#define card_has_ts(dev) (saa7134_boards[dev->board].has_ts)
#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 3
#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 {
struct task_struct *task;
wait_queue_head_t wq;
struct semaphore *notify;
int exit;
int scan1;
int scan2;
};
/* 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;
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;
};
/* video filehandle status */
struct saa7134_fh {
struct saa7134_dev *dev;
int radio;
enum v4l2_buf_type type;
struct v4l2_window win;
struct v4l2_clip clips[8];
int nclips;
int resources;
/* video capture */
struct saa7134_format *fmt;
int width,height;
enum v4l2_field field;
struct videobuf_queue cap;
struct saa7134_pgtable pt_cap;
/* vbi capture */
struct videobuf_queue vbi;
struct saa7134_pgtable pt_vbi;
};
/* TS status */
struct saa7134_ts {
int users;
/* TS capture */
struct videobuf_queue ts;
struct saa7134_pgtable pt_ts;
};
/* oss dsp status */
struct saa7134_oss {
struct semaphore lock;
int minor_mixer;
int minor_dsp;
int users_dsp;
/* mixer */
enum saa7134_audio_in input;
int count;
int line1;
int line2;
/* dsp */
int afmt;
int rate;
int channels;
int recording;
int blocks;
int blksize;
int bufsize;
struct saa7134_pgtable pt;
struct videobuf_dmabuf dma;
wait_queue_head_t wq;
int dma_blk;
int read_offset;
int read_count;
};
/* global device status */
struct saa7134_dev {
struct list_head devlist;
struct semaphore lock;
spinlock_t slock;
/* various device info */
int resources;
struct video_device video_dev;
struct video_device ts_dev;
struct video_device radio_dev;
struct video_device vbi_dev;
struct saa7134_oss oss;
struct saa7134_ts ts;
/* pci i/o */
char name[32];
struct pci_dev *pci;
unsigned char pci_rev,pci_lat;
__u32 *lmmio;
__u8 *bmmio;
/* config info */
int board;
int tuner_type;
/* 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;
int ovenable;
enum v4l2_field ovfield;
/* video+ts+vbi capture */
struct saa7134_dmaqueue video_q;
struct saa7134_dmaqueue ts_q;
struct saa7134_dmaqueue vbi_q;
int vbi_fieldcount;
/* various v4l controls */
struct saa7134_tvnorm *tvnorm; /* video */
struct saa7134_tvaudio *tvaudio;
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;
/* other global state info */
int automute;
struct saa7134_thread thread;
struct saa7134_input *input;
struct saa7134_input *hw_input;
int hw_mute;
};
/* ----------------------------------------------------------- */
#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)
/* ----------------------------------------------------------- */
/* saa7134-core.c */
extern struct list_head saa7134_devlist;
extern int saa7134_devcount;
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, int length,
int startpage);
void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt);
int saa7134_buffer_count(int size, 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,
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 int saa7134_bcount;
extern struct pci_device_id __devinitdata saa7134_pci_tbl[];
/* ----------------------------------------------------------- */
/* 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_init(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 */
extern struct video_device saa7134_ts_template;
int saa7134_ts_init(struct saa7134_dev *dev);
int saa7134_ts_fini(struct saa7134_dev *dev);
void saa7134_irq_ts_done(struct saa7134_dev *dev, unsigned long status);
/* ----------------------------------------------------------- */
/* saa7134-vbi.c */
extern struct videobuf_queue_ops saa7134_vbi_qops;
extern struct video_device saa7134_vbi_template;
int saa7134_vbi_init(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 */
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,
struct saa7134_tvaudio *audio);
int saa7134_tvaudio_init(struct saa7134_dev *dev);
int saa7134_tvaudio_fini(struct saa7134_dev *dev);
int saa7134_tvaudio_do_scan(struct saa7134_dev *dev);
/* ----------------------------------------------------------- */
/* saa7134-oss.c */
extern struct file_operations saa7134_dsp_fops;
extern struct file_operations saa7134_mixer_fops;
int saa7134_oss_init(struct saa7134_dev *dev);
int saa7134_oss_fini(struct saa7134_dev *dev);
void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status);
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment