mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-29 14:05:19 +08:00
f41737ece4
s_std didn't belong in the tuner ops. Stricly speaking it should be part of the video ops, but it is used by audio and tuner devices as well, so it is more efficient to make it part of the core ops. Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
1713 lines
41 KiB
C
1713 lines
41 KiB
C
/*
|
|
* Auvitek AU0828 USB Bridge (Analog video support)
|
|
*
|
|
* Copyright (C) 2009 Devin Heitmueller <dheitmueller@linuxtv.org>
|
|
* Copyright (C) 2005-2008 Auvitek International, Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* As published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
/* Developer Notes:
|
|
*
|
|
* VBI support is not yet working
|
|
* The hardware scaler supported is unimplemented
|
|
* AC97 audio support is unimplemented (only i2s audio mode)
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/version.h>
|
|
#include <media/v4l2-common.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-chip-ident.h>
|
|
#include <media/tuner.h>
|
|
#include "au0828.h"
|
|
#include "au0828-reg.h"
|
|
|
|
static LIST_HEAD(au0828_devlist);
|
|
static DEFINE_MUTEX(au0828_sysfs_lock);
|
|
|
|
#define AU0828_VERSION_CODE KERNEL_VERSION(0, 0, 1)
|
|
|
|
/* ------------------------------------------------------------------
|
|
Videobuf operations
|
|
------------------------------------------------------------------*/
|
|
|
|
static unsigned int isoc_debug;
|
|
module_param(isoc_debug, int, 0644);
|
|
MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
|
|
|
|
#define au0828_isocdbg(fmt, arg...) \
|
|
do {\
|
|
if (isoc_debug) { \
|
|
printk(KERN_INFO "au0828 %s :"fmt, \
|
|
__func__ , ##arg); \
|
|
} \
|
|
} while (0)
|
|
|
|
static inline void print_err_status(struct au0828_dev *dev,
|
|
int packet, int status)
|
|
{
|
|
char *errmsg = "Unknown";
|
|
|
|
switch (status) {
|
|
case -ENOENT:
|
|
errmsg = "unlinked synchronuously";
|
|
break;
|
|
case -ECONNRESET:
|
|
errmsg = "unlinked asynchronuously";
|
|
break;
|
|
case -ENOSR:
|
|
errmsg = "Buffer error (overrun)";
|
|
break;
|
|
case -EPIPE:
|
|
errmsg = "Stalled (device not responding)";
|
|
break;
|
|
case -EOVERFLOW:
|
|
errmsg = "Babble (bad cable?)";
|
|
break;
|
|
case -EPROTO:
|
|
errmsg = "Bit-stuff error (bad cable?)";
|
|
break;
|
|
case -EILSEQ:
|
|
errmsg = "CRC/Timeout (could be anything)";
|
|
break;
|
|
case -ETIME:
|
|
errmsg = "Device does not respond";
|
|
break;
|
|
}
|
|
if (packet < 0) {
|
|
au0828_isocdbg("URB status %d [%s].\n", status, errmsg);
|
|
} else {
|
|
au0828_isocdbg("URB packet %d, status %d [%s].\n",
|
|
packet, status, errmsg);
|
|
}
|
|
}
|
|
|
|
static int check_dev(struct au0828_dev *dev)
|
|
{
|
|
if (dev->dev_state & DEV_DISCONNECTED) {
|
|
printk(KERN_INFO "v4l2 ioctl: device not present\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (dev->dev_state & DEV_MISCONFIGURED) {
|
|
printk(KERN_INFO "v4l2 ioctl: device is misconfigured; "
|
|
"close and open it again\n");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* IRQ callback, called by URB callback
|
|
*/
|
|
static void au0828_irq_callback(struct urb *urb)
|
|
{
|
|
struct au0828_dmaqueue *dma_q = urb->context;
|
|
struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vidq);
|
|
int rc, i;
|
|
|
|
switch (urb->status) {
|
|
case 0: /* success */
|
|
case -ETIMEDOUT: /* NAK */
|
|
break;
|
|
case -ECONNRESET: /* kill */
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
au0828_isocdbg("au0828_irq_callback called: status kill\n");
|
|
return;
|
|
default: /* unknown error */
|
|
au0828_isocdbg("urb completition error %d.\n", urb->status);
|
|
break;
|
|
}
|
|
|
|
/* Copy data from URB */
|
|
spin_lock(&dev->slock);
|
|
rc = dev->isoc_ctl.isoc_copy(dev, urb);
|
|
spin_unlock(&dev->slock);
|
|
|
|
/* Reset urb buffers */
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
urb->iso_frame_desc[i].status = 0;
|
|
urb->iso_frame_desc[i].actual_length = 0;
|
|
}
|
|
urb->status = 0;
|
|
|
|
urb->status = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (urb->status) {
|
|
au0828_isocdbg("urb resubmit failed (error=%i)\n",
|
|
urb->status);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stop and Deallocate URBs
|
|
*/
|
|
void au0828_uninit_isoc(struct au0828_dev *dev)
|
|
{
|
|
struct urb *urb;
|
|
int i;
|
|
|
|
au0828_isocdbg("au0828: called au0828_uninit_isoc\n");
|
|
|
|
dev->isoc_ctl.nfields = -1;
|
|
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
|
|
urb = dev->isoc_ctl.urb[i];
|
|
if (urb) {
|
|
if (!irqs_disabled())
|
|
usb_kill_urb(urb);
|
|
else
|
|
usb_unlink_urb(urb);
|
|
|
|
if (dev->isoc_ctl.transfer_buffer[i]) {
|
|
usb_buffer_free(dev->usbdev,
|
|
urb->transfer_buffer_length,
|
|
dev->isoc_ctl.transfer_buffer[i],
|
|
urb->transfer_dma);
|
|
}
|
|
usb_free_urb(urb);
|
|
dev->isoc_ctl.urb[i] = NULL;
|
|
}
|
|
dev->isoc_ctl.transfer_buffer[i] = NULL;
|
|
}
|
|
|
|
kfree(dev->isoc_ctl.urb);
|
|
kfree(dev->isoc_ctl.transfer_buffer);
|
|
|
|
dev->isoc_ctl.urb = NULL;
|
|
dev->isoc_ctl.transfer_buffer = NULL;
|
|
dev->isoc_ctl.num_bufs = 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate URBs and start IRQ
|
|
*/
|
|
int au0828_init_isoc(struct au0828_dev *dev, int max_packets,
|
|
int num_bufs, int max_pkt_size,
|
|
int (*isoc_copy) (struct au0828_dev *dev, struct urb *urb))
|
|
{
|
|
struct au0828_dmaqueue *dma_q = &dev->vidq;
|
|
int i;
|
|
int sb_size, pipe;
|
|
struct urb *urb;
|
|
int j, k;
|
|
int rc;
|
|
|
|
au0828_isocdbg("au0828: called au0828_prepare_isoc\n");
|
|
|
|
/* De-allocates all pending stuff */
|
|
au0828_uninit_isoc(dev);
|
|
|
|
dev->isoc_ctl.isoc_copy = isoc_copy;
|
|
dev->isoc_ctl.num_bufs = num_bufs;
|
|
|
|
dev->isoc_ctl.urb = kzalloc(sizeof(void *)*num_bufs, GFP_KERNEL);
|
|
if (!dev->isoc_ctl.urb) {
|
|
au0828_isocdbg("cannot alloc memory for usb buffers\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dev->isoc_ctl.transfer_buffer = kzalloc(sizeof(void *)*num_bufs,
|
|
GFP_KERNEL);
|
|
if (!dev->isoc_ctl.transfer_buffer) {
|
|
au0828_isocdbg("cannot allocate memory for usb transfer\n");
|
|
kfree(dev->isoc_ctl.urb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dev->isoc_ctl.max_pkt_size = max_pkt_size;
|
|
dev->isoc_ctl.buf = NULL;
|
|
|
|
sb_size = max_packets * dev->isoc_ctl.max_pkt_size;
|
|
|
|
/* allocate urbs and transfer buffers */
|
|
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
|
|
urb = usb_alloc_urb(max_packets, GFP_KERNEL);
|
|
if (!urb) {
|
|
au0828_isocdbg("cannot alloc isoc_ctl.urb %i\n", i);
|
|
au0828_uninit_isoc(dev);
|
|
return -ENOMEM;
|
|
}
|
|
dev->isoc_ctl.urb[i] = urb;
|
|
|
|
dev->isoc_ctl.transfer_buffer[i] = usb_buffer_alloc(dev->usbdev,
|
|
sb_size, GFP_KERNEL, &urb->transfer_dma);
|
|
if (!dev->isoc_ctl.transfer_buffer[i]) {
|
|
printk("unable to allocate %i bytes for transfer"
|
|
" buffer %i%s\n",
|
|
sb_size, i,
|
|
in_interrupt() ? " while in int" : "");
|
|
au0828_uninit_isoc(dev);
|
|
return -ENOMEM;
|
|
}
|
|
memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size);
|
|
|
|
pipe = usb_rcvisocpipe(dev->usbdev,
|
|
dev->isoc_in_endpointaddr),
|
|
|
|
usb_fill_int_urb(urb, dev->usbdev, pipe,
|
|
dev->isoc_ctl.transfer_buffer[i], sb_size,
|
|
au0828_irq_callback, dma_q, 1);
|
|
|
|
urb->number_of_packets = max_packets;
|
|
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
|
|
|
|
k = 0;
|
|
for (j = 0; j < max_packets; j++) {
|
|
urb->iso_frame_desc[j].offset = k;
|
|
urb->iso_frame_desc[j].length =
|
|
dev->isoc_ctl.max_pkt_size;
|
|
k += dev->isoc_ctl.max_pkt_size;
|
|
}
|
|
}
|
|
|
|
init_waitqueue_head(&dma_q->wq);
|
|
|
|
/* submit urbs and enables IRQ */
|
|
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
|
|
rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC);
|
|
if (rc) {
|
|
au0828_isocdbg("submit of urb %i failed (error=%i)\n",
|
|
i, rc);
|
|
au0828_uninit_isoc(dev);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Announces that a buffer were filled and request the next
|
|
*/
|
|
static inline void buffer_filled(struct au0828_dev *dev,
|
|
struct au0828_dmaqueue *dma_q,
|
|
struct au0828_buffer *buf)
|
|
{
|
|
/* Advice that buffer was filled */
|
|
au0828_isocdbg("[%p/%d] wakeup\n", buf, buf->vb.i);
|
|
|
|
buf->vb.state = VIDEOBUF_DONE;
|
|
buf->vb.field_count++;
|
|
do_gettimeofday(&buf->vb.ts);
|
|
|
|
dev->isoc_ctl.buf = NULL;
|
|
|
|
list_del(&buf->vb.queue);
|
|
wake_up(&buf->vb.done);
|
|
}
|
|
|
|
/*
|
|
* Identify the buffer header type and properly handles
|
|
*/
|
|
static void au0828_copy_video(struct au0828_dev *dev,
|
|
struct au0828_dmaqueue *dma_q,
|
|
struct au0828_buffer *buf,
|
|
unsigned char *p,
|
|
unsigned char *outp, unsigned long len)
|
|
{
|
|
void *fieldstart, *startwrite, *startread;
|
|
int linesdone, currlinedone, offset, lencopy, remain;
|
|
int bytesperline = dev->width << 1; /* Assumes 16-bit depth @@@@ */
|
|
|
|
if (dma_q->pos + len > buf->vb.size)
|
|
len = buf->vb.size - dma_q->pos;
|
|
|
|
startread = p;
|
|
remain = len;
|
|
|
|
/* Interlaces frame */
|
|
if (buf->top_field)
|
|
fieldstart = outp;
|
|
else
|
|
fieldstart = outp + bytesperline;
|
|
|
|
linesdone = dma_q->pos / bytesperline;
|
|
currlinedone = dma_q->pos % bytesperline;
|
|
offset = linesdone * bytesperline * 2 + currlinedone;
|
|
startwrite = fieldstart + offset;
|
|
lencopy = bytesperline - currlinedone;
|
|
lencopy = lencopy > remain ? remain : lencopy;
|
|
|
|
if ((char *)startwrite + lencopy > (char *)outp + buf->vb.size) {
|
|
au0828_isocdbg("Overflow of %zi bytes past buffer end (1)\n",
|
|
((char *)startwrite + lencopy) -
|
|
((char *)outp + buf->vb.size));
|
|
remain = (char *)outp + buf->vb.size - (char *)startwrite;
|
|
lencopy = remain;
|
|
}
|
|
if (lencopy <= 0)
|
|
return;
|
|
memcpy(startwrite, startread, lencopy);
|
|
|
|
remain -= lencopy;
|
|
|
|
while (remain > 0) {
|
|
startwrite += lencopy + bytesperline;
|
|
startread += lencopy;
|
|
if (bytesperline > remain)
|
|
lencopy = remain;
|
|
else
|
|
lencopy = bytesperline;
|
|
|
|
if ((char *)startwrite + lencopy > (char *)outp +
|
|
buf->vb.size) {
|
|
au0828_isocdbg("Overflow %zi bytes past buf end (2)\n",
|
|
((char *)startwrite + lencopy) -
|
|
((char *)outp + buf->vb.size));
|
|
lencopy = remain = (char *)outp + buf->vb.size -
|
|
(char *)startwrite;
|
|
}
|
|
if (lencopy <= 0)
|
|
break;
|
|
|
|
memcpy(startwrite, startread, lencopy);
|
|
|
|
remain -= lencopy;
|
|
}
|
|
|
|
if (offset > 1440) {
|
|
/* We have enough data to check for greenscreen */
|
|
if (outp[0] < 0x60 && outp[1440] < 0x60)
|
|
dev->greenscreen_detected = 1;
|
|
}
|
|
|
|
dma_q->pos += len;
|
|
}
|
|
|
|
/*
|
|
* video-buf generic routine to get the next available buffer
|
|
*/
|
|
static inline void get_next_buf(struct au0828_dmaqueue *dma_q,
|
|
struct au0828_buffer **buf)
|
|
{
|
|
struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vidq);
|
|
|
|
if (list_empty(&dma_q->active)) {
|
|
au0828_isocdbg("No active queue to serve\n");
|
|
dev->isoc_ctl.buf = NULL;
|
|
*buf = NULL;
|
|
return;
|
|
}
|
|
|
|
/* Get the next buffer */
|
|
*buf = list_entry(dma_q->active.next, struct au0828_buffer, vb.queue);
|
|
dev->isoc_ctl.buf = *buf;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Controls the isoc copy of each urb packet
|
|
*/
|
|
static inline int au0828_isoc_copy(struct au0828_dev *dev, struct urb *urb)
|
|
{
|
|
struct au0828_buffer *buf;
|
|
struct au0828_dmaqueue *dma_q = urb->context;
|
|
unsigned char *outp = NULL;
|
|
int i, len = 0, rc = 1;
|
|
unsigned char *p;
|
|
unsigned char fbyte;
|
|
|
|
if (!dev)
|
|
return 0;
|
|
|
|
if ((dev->dev_state & DEV_DISCONNECTED) ||
|
|
(dev->dev_state & DEV_MISCONFIGURED))
|
|
return 0;
|
|
|
|
if (urb->status < 0) {
|
|
print_err_status(dev, -1, urb->status);
|
|
if (urb->status == -ENOENT)
|
|
return 0;
|
|
}
|
|
|
|
buf = dev->isoc_ctl.buf;
|
|
if (buf != NULL)
|
|
outp = videobuf_to_vmalloc(&buf->vb);
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
int status = urb->iso_frame_desc[i].status;
|
|
|
|
if (status < 0) {
|
|
print_err_status(dev, i, status);
|
|
if (urb->iso_frame_desc[i].status != -EPROTO)
|
|
continue;
|
|
}
|
|
|
|
if (urb->iso_frame_desc[i].actual_length <= 0)
|
|
continue;
|
|
|
|
if (urb->iso_frame_desc[i].actual_length >
|
|
dev->max_pkt_size) {
|
|
au0828_isocdbg("packet bigger than packet size");
|
|
continue;
|
|
}
|
|
|
|
p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
|
|
fbyte = p[0];
|
|
len = urb->iso_frame_desc[i].actual_length - 4;
|
|
p += 4;
|
|
|
|
if (fbyte & 0x80) {
|
|
len -= 4;
|
|
p += 4;
|
|
au0828_isocdbg("Video frame %s\n",
|
|
(fbyte & 0x40) ? "odd" : "even");
|
|
if (!(fbyte & 0x40)) {
|
|
if (buf != NULL)
|
|
buffer_filled(dev, dma_q, buf);
|
|
get_next_buf(dma_q, &buf);
|
|
if (buf == NULL)
|
|
outp = NULL;
|
|
else
|
|
outp = videobuf_to_vmalloc(&buf->vb);
|
|
}
|
|
|
|
if (buf != NULL) {
|
|
if (fbyte & 0x40)
|
|
buf->top_field = 1;
|
|
else
|
|
buf->top_field = 0;
|
|
}
|
|
|
|
dma_q->pos = 0;
|
|
}
|
|
if (buf != NULL)
|
|
au0828_copy_video(dev, dma_q, buf, p, outp, len);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
buffer_setup(struct videobuf_queue *vq, unsigned int *count,
|
|
unsigned int *size)
|
|
{
|
|
struct au0828_fh *fh = vq->priv_data;
|
|
*size = (fh->dev->width * fh->dev->height * 16 + 7) >> 3;
|
|
|
|
if (0 == *count)
|
|
*count = AU0828_DEF_BUF;
|
|
|
|
if (*count < AU0828_MIN_BUF)
|
|
*count = AU0828_MIN_BUF;
|
|
return 0;
|
|
}
|
|
|
|
/* This is called *without* dev->slock held; please keep it that way */
|
|
static void free_buffer(struct videobuf_queue *vq, struct au0828_buffer *buf)
|
|
{
|
|
struct au0828_fh *fh = vq->priv_data;
|
|
struct au0828_dev *dev = fh->dev;
|
|
unsigned long flags = 0;
|
|
if (in_interrupt())
|
|
BUG();
|
|
|
|
/* We used to wait for the buffer to finish here, but this didn't work
|
|
because, as we were keeping the state as VIDEOBUF_QUEUED,
|
|
videobuf_queue_cancel marked it as finished for us.
|
|
(Also, it could wedge forever if the hardware was misconfigured.)
|
|
|
|
This should be safe; by the time we get here, the buffer isn't
|
|
queued anymore. If we ever start marking the buffers as
|
|
VIDEOBUF_ACTIVE, it won't be, though.
|
|
*/
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
if (dev->isoc_ctl.buf == buf)
|
|
dev->isoc_ctl.buf = NULL;
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
|
|
videobuf_vmalloc_free(&buf->vb);
|
|
buf->vb.state = VIDEOBUF_NEEDS_INIT;
|
|
}
|
|
|
|
static int
|
|
buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
|
|
enum v4l2_field field)
|
|
{
|
|
struct au0828_fh *fh = vq->priv_data;
|
|
struct au0828_buffer *buf = container_of(vb, struct au0828_buffer, vb);
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc = 0, urb_init = 0;
|
|
|
|
buf->vb.size = (fh->dev->width * fh->dev->height * 16 + 7) >> 3;
|
|
|
|
if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
|
|
return -EINVAL;
|
|
|
|
buf->vb.width = dev->width;
|
|
buf->vb.height = dev->height;
|
|
buf->vb.field = field;
|
|
|
|
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
|
|
rc = videobuf_iolock(vq, &buf->vb, NULL);
|
|
if (rc < 0) {
|
|
printk(KERN_INFO "videobuf_iolock failed\n");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (!dev->isoc_ctl.num_bufs)
|
|
urb_init = 1;
|
|
|
|
if (urb_init) {
|
|
rc = au0828_init_isoc(dev, AU0828_ISO_PACKETS_PER_URB,
|
|
AU0828_MAX_ISO_BUFS, dev->max_pkt_size,
|
|
au0828_isoc_copy);
|
|
if (rc < 0) {
|
|
printk(KERN_INFO "au0828_init_isoc failed\n");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
buf->vb.state = VIDEOBUF_PREPARED;
|
|
return 0;
|
|
|
|
fail:
|
|
free_buffer(vq, buf);
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
|
|
{
|
|
struct au0828_buffer *buf = container_of(vb,
|
|
struct au0828_buffer,
|
|
vb);
|
|
struct au0828_fh *fh = vq->priv_data;
|
|
struct au0828_dev *dev = fh->dev;
|
|
struct au0828_dmaqueue *vidq = &dev->vidq;
|
|
|
|
buf->vb.state = VIDEOBUF_QUEUED;
|
|
list_add_tail(&buf->vb.queue, &vidq->active);
|
|
}
|
|
|
|
static void buffer_release(struct videobuf_queue *vq,
|
|
struct videobuf_buffer *vb)
|
|
{
|
|
struct au0828_buffer *buf = container_of(vb,
|
|
struct au0828_buffer,
|
|
vb);
|
|
|
|
free_buffer(vq, buf);
|
|
}
|
|
|
|
static struct videobuf_queue_ops au0828_video_qops = {
|
|
.buf_setup = buffer_setup,
|
|
.buf_prepare = buffer_prepare,
|
|
.buf_queue = buffer_queue,
|
|
.buf_release = buffer_release,
|
|
};
|
|
|
|
/* ------------------------------------------------------------------
|
|
V4L2 interface
|
|
------------------------------------------------------------------*/
|
|
|
|
static int au0828_i2s_init(struct au0828_dev *dev)
|
|
{
|
|
/* Enable i2s mode */
|
|
au0828_writereg(dev, AU0828_AUDIOCTRL_50C, 0x01);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Auvitek au0828 analog stream enable
|
|
* Please set interface0 to AS5 before enable the stream
|
|
*/
|
|
int au0828_analog_stream_enable(struct au0828_dev *d)
|
|
{
|
|
dprintk(1, "au0828_analog_stream_enable called\n");
|
|
au0828_writereg(d, AU0828_SENSORCTRL_VBI_103, 0x00);
|
|
au0828_writereg(d, 0x106, 0x00);
|
|
/* set x position */
|
|
au0828_writereg(d, 0x110, 0x00);
|
|
au0828_writereg(d, 0x111, 0x00);
|
|
au0828_writereg(d, 0x114, 0xa0);
|
|
au0828_writereg(d, 0x115, 0x05);
|
|
/* set y position */
|
|
au0828_writereg(d, 0x112, 0x02);
|
|
au0828_writereg(d, 0x113, 0x00);
|
|
au0828_writereg(d, 0x116, 0xf2);
|
|
au0828_writereg(d, 0x117, 0x00);
|
|
au0828_writereg(d, AU0828_SENSORCTRL_100, 0xb3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int au0828_analog_stream_disable(struct au0828_dev *d)
|
|
{
|
|
dprintk(1, "au0828_analog_stream_disable called\n");
|
|
au0828_writereg(d, AU0828_SENSORCTRL_100, 0x0);
|
|
return 0;
|
|
}
|
|
|
|
void au0828_analog_stream_reset(struct au0828_dev *dev)
|
|
{
|
|
dprintk(1, "au0828_analog_stream_reset called\n");
|
|
au0828_writereg(dev, AU0828_SENSORCTRL_100, 0x0);
|
|
mdelay(30);
|
|
au0828_writereg(dev, AU0828_SENSORCTRL_100, 0xb3);
|
|
}
|
|
|
|
/*
|
|
* Some operations needs to stop current streaming
|
|
*/
|
|
static int au0828_stream_interrupt(struct au0828_dev *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
dev->stream_state = STREAM_INTERRUPT;
|
|
if (dev->dev_state == DEV_DISCONNECTED)
|
|
return -ENODEV;
|
|
else if (ret) {
|
|
dev->dev_state = DEV_MISCONFIGURED;
|
|
dprintk(1, "%s device is misconfigured!\n", __func__);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* au0828_release_resources
|
|
* unregister v4l2 devices
|
|
*/
|
|
void au0828_analog_unregister(struct au0828_dev *dev)
|
|
{
|
|
dprintk(1, "au0828_release_resources called\n");
|
|
mutex_lock(&au0828_sysfs_lock);
|
|
|
|
if (dev->vdev) {
|
|
list_del(&dev->au0828list);
|
|
video_unregister_device(dev->vdev);
|
|
}
|
|
if (dev->vbi_dev)
|
|
video_unregister_device(dev->vbi_dev);
|
|
|
|
mutex_unlock(&au0828_sysfs_lock);
|
|
}
|
|
|
|
|
|
/* Usage lock check functions */
|
|
static int res_get(struct au0828_fh *fh)
|
|
{
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc = 0;
|
|
|
|
/* This instance already has stream_on */
|
|
if (fh->stream_on)
|
|
return rc;
|
|
|
|
if (dev->stream_on)
|
|
return -EBUSY;
|
|
|
|
dev->stream_on = 1;
|
|
fh->stream_on = 1;
|
|
return rc;
|
|
}
|
|
|
|
static int res_check(struct au0828_fh *fh)
|
|
{
|
|
return fh->stream_on;
|
|
}
|
|
|
|
static void res_free(struct au0828_fh *fh)
|
|
{
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
fh->stream_on = 0;
|
|
dev->stream_on = 0;
|
|
}
|
|
|
|
static int au0828_v4l2_open(struct file *filp)
|
|
{
|
|
int minor = video_devdata(filp)->minor;
|
|
int ret = 0;
|
|
struct au0828_dev *h, *dev = NULL;
|
|
struct au0828_fh *fh;
|
|
int type = 0;
|
|
struct list_head *list;
|
|
|
|
list_for_each(list, &au0828_devlist) {
|
|
h = list_entry(list, struct au0828_dev, au0828list);
|
|
if (h->vdev->minor == minor) {
|
|
dev = h;
|
|
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
}
|
|
#ifdef VBI_IS_WORKING
|
|
if (h->vbi_dev->minor == minor) {
|
|
dev = h;
|
|
type = V4L2_BUF_TYPE_VBI_CAPTURE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (NULL == dev)
|
|
return -ENODEV;
|
|
|
|
fh = kzalloc(sizeof(struct au0828_fh), GFP_KERNEL);
|
|
if (NULL == fh) {
|
|
dprintk(1, "Failed allocate au0828_fh struct!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
fh->type = type;
|
|
fh->dev = dev;
|
|
filp->private_data = fh;
|
|
|
|
if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->users == 0) {
|
|
/* set au0828 interface0 to AS5 here again */
|
|
ret = usb_set_interface(dev->usbdev, 0, 5);
|
|
if (ret < 0) {
|
|
printk(KERN_INFO "Au0828 can't set alternate to 5!\n");
|
|
return -EBUSY;
|
|
}
|
|
dev->width = NTSC_STD_W;
|
|
dev->height = NTSC_STD_H;
|
|
dev->frame_size = dev->width * dev->height * 2;
|
|
dev->field_size = dev->width * dev->height;
|
|
dev->bytesperline = dev->width * 2;
|
|
|
|
au0828_analog_stream_enable(dev);
|
|
au0828_analog_stream_reset(dev);
|
|
|
|
/* If we were doing ac97 instead of i2s, it would go here...*/
|
|
au0828_i2s_init(dev);
|
|
|
|
dev->stream_state = STREAM_OFF;
|
|
dev->dev_state |= DEV_INITIALIZED;
|
|
}
|
|
|
|
dev->users++;
|
|
|
|
videobuf_queue_vmalloc_init(&fh->vb_vidq, &au0828_video_qops,
|
|
NULL, &dev->slock, fh->type,
|
|
V4L2_FIELD_INTERLACED,
|
|
sizeof(struct au0828_buffer), fh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int au0828_v4l2_close(struct file *filp)
|
|
{
|
|
int ret;
|
|
struct au0828_fh *fh = filp->private_data;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
mutex_lock(&dev->lock);
|
|
if (res_check(fh))
|
|
res_free(fh);
|
|
|
|
if (dev->users == 1) {
|
|
videobuf_stop(&fh->vb_vidq);
|
|
videobuf_mmap_free(&fh->vb_vidq);
|
|
|
|
if (dev->dev_state & DEV_DISCONNECTED) {
|
|
au0828_analog_unregister(dev);
|
|
mutex_unlock(&dev->lock);
|
|
kfree(dev);
|
|
return 0;
|
|
}
|
|
|
|
au0828_analog_stream_disable(dev);
|
|
|
|
au0828_uninit_isoc(dev);
|
|
|
|
/* When close the device, set the usb intf0 into alt0 to free
|
|
USB bandwidth */
|
|
ret = usb_set_interface(dev->usbdev, 0, 0);
|
|
if (ret < 0)
|
|
printk(KERN_INFO "Au0828 can't set alternate to 0!\n");
|
|
}
|
|
|
|
kfree(fh);
|
|
dev->users--;
|
|
wake_up_interruptible_nr(&dev->open, 1);
|
|
mutex_unlock(&dev->lock);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t au0828_v4l2_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
struct au0828_fh *fh = filp->private_data;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
mutex_lock(&dev->lock);
|
|
rc = res_get(fh);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
if (unlikely(rc < 0))
|
|
return rc;
|
|
|
|
return videobuf_read_stream(&fh->vb_vidq, buf, count, pos, 0,
|
|
filp->f_flags & O_NONBLOCK);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int au0828_v4l2_poll(struct file *filp, poll_table *wait)
|
|
{
|
|
struct au0828_fh *fh = filp->private_data;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
mutex_lock(&dev->lock);
|
|
rc = res_get(fh);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
if (unlikely(rc < 0))
|
|
return POLLERR;
|
|
|
|
if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
|
|
return POLLERR;
|
|
|
|
return videobuf_poll_stream(filp, &fh->vb_vidq, wait);
|
|
}
|
|
|
|
static int au0828_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
|
|
{
|
|
struct au0828_fh *fh = filp->private_data;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
mutex_lock(&dev->lock);
|
|
rc = res_get(fh);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
if (unlikely(rc < 0))
|
|
return rc;
|
|
|
|
rc = videobuf_mmap_mapper(&fh->vb_vidq, vma);
|
|
|
|
dprintk(2, "vma start=0x%08lx, size=%ld, ret=%d\n",
|
|
(unsigned long)vma->vm_start,
|
|
(unsigned long)vma->vm_end-(unsigned long)vma->vm_start,
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int au0828_set_format(struct au0828_dev *dev, unsigned int cmd,
|
|
struct v4l2_format *format)
|
|
{
|
|
int ret;
|
|
int width = format->fmt.pix.width;
|
|
int height = format->fmt.pix.height;
|
|
unsigned int maxwidth, maxheight;
|
|
|
|
maxwidth = 720;
|
|
maxheight = 480;
|
|
|
|
#ifdef VBI_IS_WORKING
|
|
if (format->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) {
|
|
dprintk(1, "VBI format set: to be supported!\n");
|
|
return 0;
|
|
}
|
|
if (format->type == V4L2_BUF_TYPE_VBI_CAPTURE)
|
|
return 0;
|
|
#endif
|
|
if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
/* If they are demanding a format other than the one we support,
|
|
bail out (tvtime asks for UYVY and then retries with YUYV) */
|
|
if (format->fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY)
|
|
return -EINVAL;
|
|
|
|
/* format->fmt.pix.width only support 720 and height 480 */
|
|
if (width != 720)
|
|
width = 720;
|
|
if (height != 480)
|
|
height = 480;
|
|
|
|
format->fmt.pix.width = width;
|
|
format->fmt.pix.height = height;
|
|
format->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
|
|
format->fmt.pix.bytesperline = width * 2;
|
|
format->fmt.pix.sizeimage = width * height * 2;
|
|
format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
|
|
format->fmt.pix.field = V4L2_FIELD_INTERLACED;
|
|
|
|
if (cmd == VIDIOC_TRY_FMT)
|
|
return 0;
|
|
|
|
/* maybe set new image format, driver current only support 720*480 */
|
|
dev->width = width;
|
|
dev->height = height;
|
|
dev->frame_size = width * height * 2;
|
|
dev->field_size = width * height;
|
|
dev->bytesperline = width * 2;
|
|
|
|
if (dev->stream_state == STREAM_ON) {
|
|
dprintk(1, "VIDIOC_SET_FMT: interrupting stream!\n");
|
|
ret = au0828_stream_interrupt(dev);
|
|
if (ret != 0) {
|
|
dprintk(1, "error interrupting video stream!\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* set au0828 interface0 to AS5 here again */
|
|
ret = usb_set_interface(dev->usbdev, 0, 5);
|
|
if (ret < 0) {
|
|
printk(KERN_INFO "Au0828 can't set alt setting to 5!\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
au0828_analog_stream_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int vidioc_queryctrl(struct file *file, void *priv,
|
|
struct v4l2_queryctrl *qc)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, queryctrl, qc);
|
|
if (qc->type)
|
|
return 0;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int vidioc_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
strlcpy(cap->driver, "au0828", sizeof(cap->driver));
|
|
strlcpy(cap->card, dev->board.name, sizeof(cap->card));
|
|
strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
|
|
|
|
cap->version = AU0828_VERSION_CODE;
|
|
|
|
/*set the device capabilities */
|
|
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
|
|
#ifdef VBI_IS_WORKING
|
|
V4L2_CAP_VBI_CAPTURE |
|
|
#endif
|
|
V4L2_CAP_AUDIO |
|
|
V4L2_CAP_READWRITE |
|
|
V4L2_CAP_STREAMING |
|
|
V4L2_CAP_TUNER;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *f)
|
|
{
|
|
if (f->index)
|
|
return -EINVAL;
|
|
|
|
f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
strcpy(f->description, "Packed YUV2");
|
|
|
|
f->flags = 0;
|
|
f->pixelformat = V4L2_PIX_FMT_UYVY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
f->fmt.pix.width = dev->width;
|
|
f->fmt.pix.height = dev->height;
|
|
f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
|
|
f->fmt.pix.bytesperline = dev->bytesperline;
|
|
f->fmt.pix.sizeimage = dev->frame_size;
|
|
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; /* NTSC/PAL */
|
|
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
return au0828_set_format(dev, VIDIOC_TRY_FMT, f);
|
|
}
|
|
|
|
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
if (videobuf_queue_is_busy(&fh->vb_vidq)) {
|
|
printk(KERN_INFO "%s queue busy\n", __func__);
|
|
rc = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
if (dev->stream_on && !fh->stream_on) {
|
|
printk(KERN_INFO "%s device in use by another fh\n", __func__);
|
|
rc = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
return au0828_set_format(dev, VIDIOC_S_FMT, f);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id * norm)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
/* FIXME: when we support something other than NTSC, we are going to
|
|
have to make the au0828 bridge adjust the size of its capture
|
|
buffer, which is currently hardcoded at 720x480 */
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, *norm);
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_enum_input(struct file *file, void *priv,
|
|
struct v4l2_input *input)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
unsigned int tmp;
|
|
|
|
static const char *inames[] = {
|
|
[AU0828_VMUX_UNDEFINED] = "Undefined",
|
|
[AU0828_VMUX_COMPOSITE] = "Composite",
|
|
[AU0828_VMUX_SVIDEO] = "S-Video",
|
|
[AU0828_VMUX_CABLE] = "Cable TV",
|
|
[AU0828_VMUX_TELEVISION] = "Television",
|
|
[AU0828_VMUX_DVB] = "DVB",
|
|
[AU0828_VMUX_DEBUG] = "tv debug"
|
|
};
|
|
|
|
tmp = input->index;
|
|
|
|
if (tmp > AU0828_MAX_INPUT)
|
|
return -EINVAL;
|
|
if (AUVI_INPUT(tmp).type == 0)
|
|
return -EINVAL;
|
|
|
|
input->index = tmp;
|
|
strcpy(input->name, inames[AUVI_INPUT(tmp).type]);
|
|
if ((AUVI_INPUT(tmp).type == AU0828_VMUX_TELEVISION) ||
|
|
(AUVI_INPUT(tmp).type == AU0828_VMUX_CABLE))
|
|
input->type |= V4L2_INPUT_TYPE_TUNER;
|
|
else
|
|
input->type |= V4L2_INPUT_TYPE_CAMERA;
|
|
|
|
input->std = dev->vdev->tvnorms;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
*i = dev->ctrl_input;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_input(struct file *file, void *priv, unsigned int index)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int i;
|
|
struct v4l2_routing route;
|
|
|
|
dprintk(1, "VIDIOC_S_INPUT in function %s, input=%d\n", __func__,
|
|
index);
|
|
if (index >= AU0828_MAX_INPUT)
|
|
return -EINVAL;
|
|
if (AUVI_INPUT(index).type == 0)
|
|
return -EINVAL;
|
|
dev->ctrl_input = index;
|
|
|
|
switch (AUVI_INPUT(index).type) {
|
|
case AU0828_VMUX_SVIDEO:
|
|
dev->input_type = AU0828_VMUX_SVIDEO;
|
|
break;
|
|
case AU0828_VMUX_COMPOSITE:
|
|
dev->input_type = AU0828_VMUX_COMPOSITE;
|
|
break;
|
|
case AU0828_VMUX_TELEVISION:
|
|
dev->input_type = AU0828_VMUX_TELEVISION;
|
|
break;
|
|
default:
|
|
dprintk(1, "VIDIOC_S_INPUT unknown input type set [%d]\n",
|
|
AUVI_INPUT(index).type);
|
|
break;
|
|
}
|
|
|
|
route.input = AUVI_INPUT(index).vmux;
|
|
route.output = 0;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing, &route);
|
|
|
|
for (i = 0; i < AU0828_MAX_INPUT; i++) {
|
|
int enable = 0;
|
|
if (AUVI_INPUT(i).audio_setup == NULL)
|
|
continue;
|
|
|
|
if (i == index)
|
|
enable = 1;
|
|
else
|
|
enable = 0;
|
|
if (enable) {
|
|
(AUVI_INPUT(i).audio_setup)(dev, enable);
|
|
} else {
|
|
/* Make sure we leave it turned on if some
|
|
other input is routed to this callback */
|
|
if ((AUVI_INPUT(i).audio_setup) !=
|
|
((AUVI_INPUT(index).audio_setup))) {
|
|
(AUVI_INPUT(i).audio_setup)(dev, enable);
|
|
}
|
|
}
|
|
}
|
|
|
|
route.input = AUVI_INPUT(index).amux;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, audio, s_routing, &route);
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
unsigned int index = a->index;
|
|
|
|
if (a->index > 1)
|
|
return -EINVAL;
|
|
|
|
index = dev->ctrl_ainput;
|
|
if (index == 0)
|
|
strcpy(a->name, "Television");
|
|
else
|
|
strcpy(a->name, "Line in");
|
|
|
|
a->capability = V4L2_AUDCAP_STEREO;
|
|
a->index = index;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *a)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
if (a->index != dev->ctrl_ainput)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_ctrl(struct file *file, void *priv,
|
|
struct v4l2_control *ctrl)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, g_ctrl, ctrl);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int vidioc_s_ctrl(struct file *file, void *priv,
|
|
struct v4l2_control *ctrl)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_ctrl, ctrl);
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
if (t->index != 0)
|
|
return -EINVAL;
|
|
|
|
strcpy(t->name, "Auvitek tuner");
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
if (t->index != 0)
|
|
return -EINVAL;
|
|
|
|
t->type = V4L2_TUNER_ANALOG_TV;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
|
|
dprintk(1, "VIDIOC_S_TUNER: signal = %x, afc = %x\n", t->signal,
|
|
t->afc);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int vidioc_g_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *freq)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
freq->type = V4L2_TUNER_ANALOG_TV;
|
|
freq->frequency = dev->ctrl_freq;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *freq)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
if (freq->tuner != 0)
|
|
return -EINVAL;
|
|
if (freq->type != V4L2_TUNER_ANALOG_TV)
|
|
return -EINVAL;
|
|
|
|
dev->ctrl_freq = freq->frequency;
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, freq);
|
|
|
|
au0828_analog_stream_reset(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_chip_ident(struct file *file, void *priv,
|
|
struct v4l2_dbg_chip_ident *chip)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
chip->ident = V4L2_IDENT_NONE;
|
|
chip->revision = 0;
|
|
|
|
if (v4l2_chip_match_host(&chip->match)) {
|
|
chip->ident = V4L2_IDENT_AU0828;
|
|
return 0;
|
|
}
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, g_chip_ident, chip);
|
|
if (chip->ident == V4L2_IDENT_NONE)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_cropcap(struct file *file, void *priv,
|
|
struct v4l2_cropcap *cc)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
cc->bounds.left = 0;
|
|
cc->bounds.top = 0;
|
|
cc->bounds.width = dev->width;
|
|
cc->bounds.height = dev->height;
|
|
|
|
cc->defrect = cc->bounds;
|
|
|
|
cc->pixelaspect.numerator = 54;
|
|
cc->pixelaspect.denominator = 59;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_streamon(struct file *file, void *priv,
|
|
enum v4l2_buf_type type)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
au0828_analog_stream_enable(dev);
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 1);
|
|
}
|
|
|
|
mutex_lock(&dev->lock);
|
|
rc = res_get(fh);
|
|
|
|
if (likely(rc >= 0))
|
|
rc = videobuf_streamon(&fh->vb_vidq);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int vidioc_streamoff(struct file *file, void *priv,
|
|
enum v4l2_buf_type type)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int i;
|
|
int ret;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
if (type != fh->type)
|
|
return -EINVAL;
|
|
|
|
if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
|
|
ret = au0828_stream_interrupt(dev);
|
|
if (ret != 0)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < AU0828_MAX_INPUT; i++) {
|
|
if (AUVI_INPUT(i).audio_setup == NULL)
|
|
continue;
|
|
(AUVI_INPUT(i).audio_setup)(dev, 0);
|
|
}
|
|
|
|
mutex_lock(&dev->lock);
|
|
videobuf_streamoff(&fh->vb_vidq);
|
|
res_free(fh);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
|
static int vidioc_g_register(struct file *file, void *priv,
|
|
struct v4l2_dbg_register *reg)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
switch (reg->match.type) {
|
|
case V4L2_CHIP_MATCH_I2C_DRIVER:
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, g_register, reg);
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int vidioc_s_register(struct file *file, void *priv,
|
|
struct v4l2_dbg_register *reg)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
|
|
switch (reg->match.type) {
|
|
case V4L2_CHIP_MATCH_I2C_DRIVER:
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_register, reg);
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int vidioc_reqbufs(struct file *file, void *priv,
|
|
struct v4l2_requestbuffers *rb)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
return videobuf_reqbufs(&fh->vb_vidq, rb);
|
|
}
|
|
|
|
static int vidioc_querybuf(struct file *file, void *priv,
|
|
struct v4l2_buffer *b)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
return videobuf_querybuf(&fh->vb_vidq, b);
|
|
}
|
|
|
|
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
return videobuf_qbuf(&fh->vb_vidq, b);
|
|
}
|
|
|
|
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
struct au0828_dev *dev = fh->dev;
|
|
int rc;
|
|
|
|
rc = check_dev(dev);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* Workaround for a bug in the au0828 hardware design that sometimes
|
|
results in the colorspace being inverted */
|
|
if (dev->greenscreen_detected == 1) {
|
|
dprintk(1, "Detected green frame. Resetting stream...\n");
|
|
au0828_analog_stream_reset(dev);
|
|
dev->greenscreen_detected = 0;
|
|
}
|
|
|
|
return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags & O_NONBLOCK);
|
|
}
|
|
|
|
#ifdef CONFIG_VIDEO_V4L1_COMPAT
|
|
static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
|
|
{
|
|
struct au0828_fh *fh = priv;
|
|
|
|
return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8);
|
|
}
|
|
#endif
|
|
|
|
static struct v4l2_file_operations au0828_v4l_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = au0828_v4l2_open,
|
|
.release = au0828_v4l2_close,
|
|
.read = au0828_v4l2_read,
|
|
.poll = au0828_v4l2_poll,
|
|
.mmap = au0828_v4l2_mmap,
|
|
.ioctl = video_ioctl2,
|
|
};
|
|
|
|
static const struct v4l2_ioctl_ops video_ioctl_ops = {
|
|
.vidioc_querycap = vidioc_querycap,
|
|
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
|
|
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
|
|
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
|
|
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
|
|
#ifdef VBI_IS_WORKING
|
|
.vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap,
|
|
.vidioc_try_fmt_vbi_cap = vidioc_s_fmt_vbi_cap,
|
|
.vidioc_s_fmt_vbi_cap = vidioc_s_fmt_vbi_cap,
|
|
#endif
|
|
.vidioc_g_audio = vidioc_g_audio,
|
|
.vidioc_s_audio = vidioc_s_audio,
|
|
.vidioc_cropcap = vidioc_cropcap,
|
|
#ifdef VBI_IS_WORKING
|
|
.vidioc_g_fmt_sliced_vbi_cap = vidioc_g_fmt_sliced_vbi_cap,
|
|
.vidioc_try_fmt_sliced_vbi_cap = vidioc_try_set_sliced_vbi_cap,
|
|
.vidioc_s_fmt_sliced_vbi_cap = vidioc_try_set_sliced_vbi_cap,
|
|
#endif
|
|
.vidioc_reqbufs = vidioc_reqbufs,
|
|
.vidioc_querybuf = vidioc_querybuf,
|
|
.vidioc_qbuf = vidioc_qbuf,
|
|
.vidioc_dqbuf = vidioc_dqbuf,
|
|
.vidioc_s_std = vidioc_s_std,
|
|
.vidioc_enum_input = vidioc_enum_input,
|
|
.vidioc_g_input = vidioc_g_input,
|
|
.vidioc_s_input = vidioc_s_input,
|
|
.vidioc_queryctrl = vidioc_queryctrl,
|
|
.vidioc_g_ctrl = vidioc_g_ctrl,
|
|
.vidioc_s_ctrl = vidioc_s_ctrl,
|
|
.vidioc_streamon = vidioc_streamon,
|
|
.vidioc_streamoff = vidioc_streamoff,
|
|
.vidioc_g_tuner = vidioc_g_tuner,
|
|
.vidioc_s_tuner = vidioc_s_tuner,
|
|
.vidioc_g_frequency = vidioc_g_frequency,
|
|
.vidioc_s_frequency = vidioc_s_frequency,
|
|
#ifdef CONFIG_VIDEO_ADV_DEBUG
|
|
.vidioc_g_register = vidioc_g_register,
|
|
.vidioc_s_register = vidioc_s_register,
|
|
#endif
|
|
.vidioc_g_chip_ident = vidioc_g_chip_ident,
|
|
#ifdef CONFIG_VIDEO_V4L1_COMPAT
|
|
.vidiocgmbuf = vidiocgmbuf,
|
|
#endif
|
|
};
|
|
|
|
static const struct video_device au0828_video_template = {
|
|
.fops = &au0828_v4l_fops,
|
|
.release = video_device_release,
|
|
.ioctl_ops = &video_ioctl_ops,
|
|
.minor = -1,
|
|
.tvnorms = V4L2_STD_NTSC_M,
|
|
.current_norm = V4L2_STD_NTSC_M,
|
|
};
|
|
|
|
/**************************************************************************/
|
|
|
|
int au0828_analog_register(struct au0828_dev *dev,
|
|
struct usb_interface *interface)
|
|
{
|
|
int retval = -ENOMEM;
|
|
struct usb_host_interface *iface_desc;
|
|
struct usb_endpoint_descriptor *endpoint;
|
|
int i;
|
|
|
|
dprintk(1, "au0828_analog_register called!\n");
|
|
|
|
/* set au0828 usb interface0 to as5 */
|
|
retval = usb_set_interface(dev->usbdev,
|
|
interface->cur_altsetting->desc.bInterfaceNumber, 5);
|
|
if (retval != 0) {
|
|
printk(KERN_INFO "Failure setting usb interface0 to as5\n");
|
|
return retval;
|
|
}
|
|
|
|
/* Figure out which endpoint has the isoc interface */
|
|
iface_desc = interface->cur_altsetting;
|
|
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
|
|
endpoint = &iface_desc->endpoint[i].desc;
|
|
if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
|
|
== USB_DIR_IN) &&
|
|
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
|
|
== USB_ENDPOINT_XFER_ISOC)) {
|
|
|
|
/* we find our isoc in endpoint */
|
|
u16 tmp = le16_to_cpu(endpoint->wMaxPacketSize);
|
|
dev->max_pkt_size = (tmp & 0x07ff) *
|
|
(((tmp & 0x1800) >> 11) + 1);
|
|
dev->isoc_in_endpointaddr = endpoint->bEndpointAddress;
|
|
}
|
|
}
|
|
if (!(dev->isoc_in_endpointaddr)) {
|
|
printk(KERN_INFO "Could not locate isoc endpoint\n");
|
|
kfree(dev);
|
|
return -ENODEV;
|
|
}
|
|
|
|
init_waitqueue_head(&dev->open);
|
|
spin_lock_init(&dev->slock);
|
|
mutex_init(&dev->lock);
|
|
|
|
INIT_LIST_HEAD(&dev->vidq.active);
|
|
INIT_LIST_HEAD(&dev->vidq.queued);
|
|
|
|
dev->width = NTSC_STD_W;
|
|
dev->height = NTSC_STD_H;
|
|
dev->field_size = dev->width * dev->height;
|
|
dev->frame_size = dev->field_size << 1;
|
|
dev->bytesperline = dev->width << 1;
|
|
dev->ctrl_ainput = 0;
|
|
|
|
/* allocate and fill v4l2 video struct */
|
|
dev->vdev = video_device_alloc();
|
|
if (NULL == dev->vdev) {
|
|
dprintk(1, "Can't allocate video_device.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#ifdef VBI_IS_WORKING
|
|
dev->vbi_dev = video_device_alloc();
|
|
if (NULL == dev->vbi_dev) {
|
|
dprintk(1, "Can't allocate vbi_device.\n");
|
|
kfree(dev->vdev);
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
|
|
/* Fill the video capture device struct */
|
|
*dev->vdev = au0828_video_template;
|
|
dev->vdev->parent = &dev->usbdev->dev;
|
|
strcpy(dev->vdev->name, "au0828a video");
|
|
|
|
#ifdef VBI_IS_WORKING
|
|
/* Setup the VBI device */
|
|
*dev->vbi_dev = au0828_video_template;
|
|
dev->vbi_dev->parent = &dev->usbdev->dev;
|
|
strcpy(dev->vbi_dev->name, "au0828a vbi");
|
|
#endif
|
|
|
|
list_add_tail(&dev->au0828list, &au0828_devlist);
|
|
|
|
/* Register the v4l2 device */
|
|
retval = video_register_device(dev->vdev, VFL_TYPE_GRABBER, -1);
|
|
if (retval != 0) {
|
|
dprintk(1, "unable to register video device (error = %d).\n",
|
|
retval);
|
|
list_del(&dev->au0828list);
|
|
video_device_release(dev->vdev);
|
|
return -ENODEV;
|
|
}
|
|
|
|
#ifdef VBI_IS_WORKING
|
|
/* Register the vbi device */
|
|
retval = video_register_device(dev->vbi_dev, VFL_TYPE_VBI, -1);
|
|
if (retval != 0) {
|
|
dprintk(1, "unable to register vbi device (error = %d).\n",
|
|
retval);
|
|
list_del(&dev->au0828list);
|
|
video_device_release(dev->vbi_dev);
|
|
video_device_release(dev->vdev);
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
|
|
dprintk(1, "%s completed!\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|