fa027c2a0a
Many struct file_operations in the kernel can be "const". Marking them const moves these to the .rodata section, which avoids false sharing with potential dirty data. In addition it'll catch accidental writes at compile time to these shared resources. [akpm@sdl.org: dvb fix] Signed-off-by: Arjan van de Ven <arjan@linux.intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
445 lines
11 KiB
C
445 lines
11 KiB
C
/*
|
|
*
|
|
* (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "saa7134-reg.h"
|
|
#include "saa7134.h"
|
|
|
|
#include <media/saa6752hs.h>
|
|
#include <media/v4l2-common.h>
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static unsigned int empress_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
|
|
|
|
module_param_array(empress_nr, int, NULL, 0444);
|
|
MODULE_PARM_DESC(empress_nr,"ts device number");
|
|
|
|
static unsigned int debug = 0;
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug,"enable debug messages");
|
|
|
|
#define dprintk(fmt, arg...) if (debug) \
|
|
printk(KERN_DEBUG "%s/empress: " fmt, dev->name , ## arg)
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void ts_reset_encoder(struct saa7134_dev* dev)
|
|
{
|
|
if (!dev->empress_started)
|
|
return;
|
|
|
|
saa_writeb(SAA7134_SPECIAL_MODE, 0x00);
|
|
msleep(10);
|
|
saa_writeb(SAA7134_SPECIAL_MODE, 0x01);
|
|
msleep(100);
|
|
dev->empress_started = 0;
|
|
}
|
|
|
|
static int ts_init_encoder(struct saa7134_dev* dev)
|
|
{
|
|
struct v4l2_ext_controls ctrls = { V4L2_CTRL_CLASS_MPEG, 0 };
|
|
|
|
ts_reset_encoder(dev);
|
|
saa7134_i2c_call_clients(dev, VIDIOC_S_EXT_CTRLS, &ctrls);
|
|
dev->empress_started = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static int ts_open(struct inode *inode, struct file *file)
|
|
{
|
|
int minor = iminor(inode);
|
|
struct saa7134_dev *h,*dev = NULL;
|
|
struct list_head *list;
|
|
int err;
|
|
|
|
list_for_each(list,&saa7134_devlist) {
|
|
h = list_entry(list, struct saa7134_dev, devlist);
|
|
if (h->empress_dev && h->empress_dev->minor == minor)
|
|
dev = h;
|
|
}
|
|
if (NULL == dev)
|
|
return -ENODEV;
|
|
|
|
dprintk("open minor=%d\n",minor);
|
|
err = -EBUSY;
|
|
if (!mutex_trylock(&dev->empress_tsq.lock))
|
|
goto done;
|
|
if (dev->empress_users)
|
|
goto done_up;
|
|
|
|
dev->empress_users++;
|
|
file->private_data = dev;
|
|
err = 0;
|
|
|
|
done_up:
|
|
mutex_unlock(&dev->empress_tsq.lock);
|
|
done:
|
|
return err;
|
|
}
|
|
|
|
static int ts_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct saa7134_dev *dev = file->private_data;
|
|
|
|
if (dev->empress_tsq.streaming)
|
|
videobuf_streamoff(&dev->empress_tsq);
|
|
mutex_lock(&dev->empress_tsq.lock);
|
|
if (dev->empress_tsq.reading)
|
|
videobuf_read_stop(&dev->empress_tsq);
|
|
videobuf_mmap_free(&dev->empress_tsq);
|
|
dev->empress_users--;
|
|
|
|
/* stop the encoder */
|
|
ts_reset_encoder(dev);
|
|
|
|
mutex_unlock(&dev->empress_tsq.lock);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
ts_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
|
|
{
|
|
struct saa7134_dev *dev = file->private_data;
|
|
|
|
if (!dev->empress_started)
|
|
ts_init_encoder(dev);
|
|
|
|
return videobuf_read_stream(&dev->empress_tsq,
|
|
data, count, ppos, 0,
|
|
file->f_flags & O_NONBLOCK);
|
|
}
|
|
|
|
static unsigned int
|
|
ts_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
struct saa7134_dev *dev = file->private_data;
|
|
|
|
return videobuf_poll_stream(file, &dev->empress_tsq, wait);
|
|
}
|
|
|
|
|
|
static int
|
|
ts_mmap(struct file *file, struct vm_area_struct * vma)
|
|
{
|
|
struct saa7134_dev *dev = file->private_data;
|
|
|
|
return videobuf_mmap_mapper(&dev->empress_tsq, vma);
|
|
}
|
|
|
|
/*
|
|
* This function is _not_ called directly, but from
|
|
* video_generic_ioctl (and maybe others). userspace
|
|
* copying is done already, arg is a kernel pointer.
|
|
*/
|
|
static int ts_do_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, void *arg)
|
|
{
|
|
struct saa7134_dev *dev = file->private_data;
|
|
struct v4l2_ext_controls *ctrls = arg;
|
|
|
|
if (debug > 1)
|
|
v4l_print_ioctl(dev->name,cmd);
|
|
switch (cmd) {
|
|
case VIDIOC_QUERYCAP:
|
|
{
|
|
struct v4l2_capability *cap = arg;
|
|
|
|
memset(cap,0,sizeof(*cap));
|
|
strcpy(cap->driver, "saa7134");
|
|
strlcpy(cap->card, saa7134_boards[dev->board].name,
|
|
sizeof(cap->card));
|
|
sprintf(cap->bus_info,"PCI:%s",pci_name(dev->pci));
|
|
cap->version = SAA7134_VERSION_CODE;
|
|
cap->capabilities =
|
|
V4L2_CAP_VIDEO_CAPTURE |
|
|
V4L2_CAP_READWRITE |
|
|
V4L2_CAP_STREAMING;
|
|
return 0;
|
|
}
|
|
|
|
/* --- input switching --------------------------------------- */
|
|
case VIDIOC_ENUMINPUT:
|
|
{
|
|
struct v4l2_input *i = arg;
|
|
|
|
if (i->index != 0)
|
|
return -EINVAL;
|
|
i->type = V4L2_INPUT_TYPE_CAMERA;
|
|
strcpy(i->name,"CCIR656");
|
|
return 0;
|
|
}
|
|
case VIDIOC_G_INPUT:
|
|
{
|
|
int *i = arg;
|
|
*i = 0;
|
|
return 0;
|
|
}
|
|
case VIDIOC_S_INPUT:
|
|
{
|
|
int *i = arg;
|
|
|
|
if (*i != 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
/* --- capture ioctls ---------------------------------------- */
|
|
|
|
case VIDIOC_ENUM_FMT:
|
|
{
|
|
struct v4l2_fmtdesc *f = arg;
|
|
int index;
|
|
|
|
index = f->index;
|
|
if (index != 0)
|
|
return -EINVAL;
|
|
|
|
memset(f,0,sizeof(*f));
|
|
f->index = index;
|
|
strlcpy(f->description, "MPEG TS", sizeof(f->description));
|
|
f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
f->pixelformat = V4L2_PIX_FMT_MPEG;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_G_FMT:
|
|
{
|
|
struct v4l2_format *f = arg;
|
|
|
|
memset(f,0,sizeof(*f));
|
|
f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
saa7134_i2c_call_clients(dev, cmd, arg);
|
|
f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
|
|
f->fmt.pix.sizeimage = TS_PACKET_SIZE * dev->ts.nr_packets;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_S_FMT:
|
|
{
|
|
struct v4l2_format *f = arg;
|
|
|
|
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
saa7134_i2c_call_clients(dev, cmd, arg);
|
|
f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
|
|
f->fmt.pix.sizeimage = TS_PACKET_SIZE* dev->ts.nr_packets;
|
|
return 0;
|
|
}
|
|
|
|
case VIDIOC_REQBUFS:
|
|
return videobuf_reqbufs(&dev->empress_tsq,arg);
|
|
|
|
case VIDIOC_QUERYBUF:
|
|
return videobuf_querybuf(&dev->empress_tsq,arg);
|
|
|
|
case VIDIOC_QBUF:
|
|
return videobuf_qbuf(&dev->empress_tsq,arg);
|
|
|
|
case VIDIOC_DQBUF:
|
|
return videobuf_dqbuf(&dev->empress_tsq,arg,
|
|
file->f_flags & O_NONBLOCK);
|
|
|
|
case VIDIOC_STREAMON:
|
|
return videobuf_streamon(&dev->empress_tsq);
|
|
|
|
case VIDIOC_STREAMOFF:
|
|
return videobuf_streamoff(&dev->empress_tsq);
|
|
|
|
case VIDIOC_QUERYCTRL:
|
|
case VIDIOC_G_CTRL:
|
|
case VIDIOC_S_CTRL:
|
|
return saa7134_common_ioctl(dev, cmd, arg);
|
|
|
|
case VIDIOC_S_MPEGCOMP:
|
|
printk(KERN_WARNING "VIDIOC_S_MPEGCOMP is obsolete. "
|
|
"Replace with VIDIOC_S_EXT_CTRLS!");
|
|
saa7134_i2c_call_clients(dev, VIDIOC_S_MPEGCOMP, arg);
|
|
ts_init_encoder(dev);
|
|
return 0;
|
|
case VIDIOC_G_MPEGCOMP:
|
|
printk(KERN_WARNING "VIDIOC_G_MPEGCOMP is obsolete. "
|
|
"Replace with VIDIOC_G_EXT_CTRLS!");
|
|
saa7134_i2c_call_clients(dev, VIDIOC_G_MPEGCOMP, arg);
|
|
return 0;
|
|
case VIDIOC_S_EXT_CTRLS:
|
|
/* count == 0 is abused in saa6752hs.c, so that special
|
|
case is handled here explicitly. */
|
|
if (ctrls->count == 0)
|
|
return 0;
|
|
if (ctrls->ctrl_class != V4L2_CTRL_CLASS_MPEG)
|
|
return -EINVAL;
|
|
saa7134_i2c_call_clients(dev, VIDIOC_S_EXT_CTRLS, arg);
|
|
ts_init_encoder(dev);
|
|
return 0;
|
|
case VIDIOC_G_EXT_CTRLS:
|
|
if (ctrls->ctrl_class != V4L2_CTRL_CLASS_MPEG)
|
|
return -EINVAL;
|
|
saa7134_i2c_call_clients(dev, VIDIOC_G_EXT_CTRLS, arg);
|
|
return 0;
|
|
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ts_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
return video_usercopy(inode, file, cmd, arg, ts_do_ioctl);
|
|
}
|
|
|
|
static const struct file_operations ts_fops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.open = ts_open,
|
|
.release = ts_release,
|
|
.read = ts_read,
|
|
.poll = ts_poll,
|
|
.mmap = ts_mmap,
|
|
.ioctl = ts_ioctl,
|
|
.llseek = no_llseek,
|
|
};
|
|
|
|
/* ----------------------------------------------------------- */
|
|
|
|
static struct video_device saa7134_empress_template =
|
|
{
|
|
.name = "saa7134-empress",
|
|
.type = 0 /* FIXME */,
|
|
.type2 = 0 /* FIXME */,
|
|
.hardware = 0,
|
|
.fops = &ts_fops,
|
|
.minor = -1,
|
|
};
|
|
|
|
static void empress_signal_update(struct work_struct *work)
|
|
{
|
|
struct saa7134_dev* dev =
|
|
container_of(work, struct saa7134_dev, empress_workqueue);
|
|
|
|
if (dev->nosignal) {
|
|
dprintk("no video signal\n");
|
|
ts_reset_encoder(dev);
|
|
} else {
|
|
dprintk("video signal acquired\n");
|
|
if (dev->empress_users)
|
|
ts_init_encoder(dev);
|
|
}
|
|
}
|
|
|
|
static void empress_signal_change(struct saa7134_dev *dev)
|
|
{
|
|
schedule_work(&dev->empress_workqueue);
|
|
}
|
|
|
|
|
|
static int empress_init(struct saa7134_dev *dev)
|
|
{
|
|
int err;
|
|
|
|
dprintk("%s: %s\n",dev->name,__FUNCTION__);
|
|
dev->empress_dev = video_device_alloc();
|
|
if (NULL == dev->empress_dev)
|
|
return -ENOMEM;
|
|
*(dev->empress_dev) = saa7134_empress_template;
|
|
dev->empress_dev->dev = &dev->pci->dev;
|
|
dev->empress_dev->release = video_device_release;
|
|
snprintf(dev->empress_dev->name, sizeof(dev->empress_dev->name),
|
|
"%s empress (%s)", dev->name,
|
|
saa7134_boards[dev->board].name);
|
|
|
|
INIT_WORK(&dev->empress_workqueue, empress_signal_update);
|
|
|
|
err = video_register_device(dev->empress_dev,VFL_TYPE_GRABBER,
|
|
empress_nr[dev->nr]);
|
|
if (err < 0) {
|
|
printk(KERN_INFO "%s: can't register video device\n",
|
|
dev->name);
|
|
video_device_release(dev->empress_dev);
|
|
dev->empress_dev = NULL;
|
|
return err;
|
|
}
|
|
printk(KERN_INFO "%s: registered device video%d [mpeg]\n",
|
|
dev->name,dev->empress_dev->minor & 0x1f);
|
|
|
|
videobuf_queue_init(&dev->empress_tsq, &saa7134_ts_qops,
|
|
dev->pci, &dev->slock,
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
|
V4L2_FIELD_ALTERNATE,
|
|
sizeof(struct saa7134_buf),
|
|
dev);
|
|
|
|
empress_signal_update(&dev->empress_workqueue);
|
|
return 0;
|
|
}
|
|
|
|
static int empress_fini(struct saa7134_dev *dev)
|
|
{
|
|
dprintk("%s: %s\n",dev->name,__FUNCTION__);
|
|
|
|
if (NULL == dev->empress_dev)
|
|
return 0;
|
|
flush_scheduled_work();
|
|
video_unregister_device(dev->empress_dev);
|
|
dev->empress_dev = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static struct saa7134_mpeg_ops empress_ops = {
|
|
.type = SAA7134_MPEG_EMPRESS,
|
|
.init = empress_init,
|
|
.fini = empress_fini,
|
|
.signal_change = empress_signal_change,
|
|
};
|
|
|
|
static int __init empress_register(void)
|
|
{
|
|
return saa7134_ts_register(&empress_ops);
|
|
}
|
|
|
|
static void __exit empress_unregister(void)
|
|
{
|
|
saa7134_ts_unregister(&empress_ops);
|
|
}
|
|
|
|
module_init(empress_register);
|
|
module_exit(empress_unregister);
|
|
|
|
/* ----------------------------------------------------------- */
|
|
/*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|