mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-25 16:25:27 +07:00
b3491d8430
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJb2F9AAAoJEAhfPr2O5OEVm5YP/Ak53aAEI1oJNequwdTYKc+/ 2xWRpYWREa1g+x4MlqWO+szlPWbGXCUVwye8ii2N/xihLapsKVrLCr/dDd5khsvw bDux33BzpU3Ug/ncQKD6ZZv4vVRzG8DMPcpkOwSs0OoboJns6AkHVGCugR32qZsH 3SH/r1aJce0oK1rrzgbYYZHTvaPshvY2IOLPKrtFmO+73iCVRhpSdWjFsY+q2Alp +3Ho/06iQYB2i+enXrwoIKHAYoXArXYbxS2dhaNz+NURrOAytmgfMisvvt67heHx IEilE0AcSjjlN/eyOxp+WCZrg9JLXVzZLX6ZnqqM2OEu1AS/XBultJBsGaN0hOiV dir2enoHNNOStI40hNSdbumg9I0Txmag2jtpaGyaBnnGmGRJ/JIYegCPRVMLygAf HHFHjR4fnRnqZrlh9OGAHaqc9RNlUgFVdlyqFtdyIah+aNeuij3o69mWM35QMLhw /0dTXBUXw9aD1dEg1cZ6PdzLWJgDd7n1gIdfzzzzLnzmBwmmhqxW8+evu9qSAXsP rnEZuE77HYKVfiacWMwpZK6+lT51STAE8ouo3N8fmaC+4RQmpq0dYXtR8RnlcSUD hKpJ6UsIIb5A6xKX7ed8x6FxV14TEEaa042A4eclxsAFiqqkNfWSozqV0vfW5vCD 2lrsuN3knpfh7XDBSr0y =V4X4 -----END PGP SIGNATURE----- Merge tag 'media/v4.20-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media Pull new experimental media request API from Mauro Carvalho Chehab: "A new media request API This API is needed to support device drivers that can dynamically change their parameters for each new frame. The latest versions of Google camera and codec HAL depends on such feature. At this stage, it supports only stateless codecs. It has been discussed for a long time (at least over the last 3-4 years), and we finally reached to something that seem to work. This series contain both the API and core changes required to support it and a new m2m decoder driver (cedrus). As the current API is still experimental, the only real driver using it (cedrus) was added at staging[1]. We intend to keep it there for a while, in order to test the API. Only when we're sure that this API works for other cases (like encoders), we'll move this driver out of staging and set the API into a stone. [1] We added support for the vivid virtual driver (used only for testing) to it too, as it makes easier to test the API for the ones that don't have the cedrus hardware" * tag 'media/v4.20-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (53 commits) media: dt-bindings: Document the Rockchip VPU bindings media: platform: Add Cedrus VPU decoder driver media: dt-bindings: media: Document bindings for the Cedrus VPU driver media: v4l: Add definition for the Sunxi tiled NV12 format media: v4l: Add definitions for MPEG-2 slice format and metadata media: videobuf2-core: Rework and rename helper for request buffer count media: v4l2-ctrls.c: initialize an error return code with zero media: v4l2-compat-ioctl32.c: add missing documentation for a field media: media-request: update documentation media: media-request: EPERM -> EACCES/EBUSY media: v4l2-ctrls: improve media_request_(un)lock_for_update media: v4l2-ctrls: use media_request_(un)lock_for_access media: media-request: add media_request_(un)lock_for_access media: vb2: set reqbufs/create_bufs capabilities media: videodev2.h: add new capabilities for buffer types media: buffer.rst: only set V4L2_BUF_FLAG_REQUEST_FD for QBUF media: v4l2-ctrls: return -EACCES if request wasn't completed media: media-request: return -EINVAL for invalid request_fds media: vivid: add request support media: vivid: add mc ...
1712 lines
41 KiB
C
1712 lines
41 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// tm6000-video.c - driver for TM5600/TM6000/TM6010 USB video capture devices
|
|
//
|
|
// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
|
|
//
|
|
// Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
|
|
// - Fixed module load/unload
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/random.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/videodev2.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/tuner.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/freezer.h>
|
|
|
|
#include "tm6000-regs.h"
|
|
#include "tm6000.h"
|
|
|
|
#define BUFFER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */
|
|
|
|
/* Limits minimum and default number of buffers */
|
|
#define TM6000_MIN_BUF 4
|
|
#define TM6000_DEF_BUF 8
|
|
|
|
#define TM6000_NUM_URB_BUF 8
|
|
|
|
#define TM6000_MAX_ISO_PACKETS 46 /* Max number of ISO packets */
|
|
|
|
/* Declare static vars that will be used as parameters */
|
|
static unsigned int vid_limit = 16; /* Video memory limit, in Mb */
|
|
static int video_nr = -1; /* /dev/videoN, -1 for autodetect */
|
|
static int radio_nr = -1; /* /dev/radioN, -1 for autodetect */
|
|
static bool keep_urb; /* keep urb buffers allocated */
|
|
|
|
/* Debug level */
|
|
int tm6000_debug;
|
|
EXPORT_SYMBOL_GPL(tm6000_debug);
|
|
|
|
static struct tm6000_fmt format[] = {
|
|
{
|
|
.name = "4:2:2, packed, YVY2",
|
|
.fourcc = V4L2_PIX_FMT_YUYV,
|
|
.depth = 16,
|
|
}, {
|
|
.name = "4:2:2, packed, UYVY",
|
|
.fourcc = V4L2_PIX_FMT_UYVY,
|
|
.depth = 16,
|
|
}, {
|
|
.name = "A/V + VBI mux packet",
|
|
.fourcc = V4L2_PIX_FMT_TM6000,
|
|
.depth = 16,
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------
|
|
* DMA and thread functions
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
#define norm_maxw(a) 720
|
|
#define norm_maxh(a) 576
|
|
|
|
#define norm_minw(a) norm_maxw(a)
|
|
#define norm_minh(a) norm_maxh(a)
|
|
|
|
/*
|
|
* video-buf generic routine to get the next available buffer
|
|
*/
|
|
static inline void get_next_buf(struct tm6000_dmaqueue *dma_q,
|
|
struct tm6000_buffer **buf)
|
|
{
|
|
struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
|
|
|
|
if (list_empty(&dma_q->active)) {
|
|
dprintk(dev, V4L2_DEBUG_QUEUE, "No active queue to serve\n");
|
|
*buf = NULL;
|
|
return;
|
|
}
|
|
|
|
*buf = list_entry(dma_q->active.next,
|
|
struct tm6000_buffer, vb.queue);
|
|
}
|
|
|
|
/*
|
|
* Announces that a buffer were filled and request the next
|
|
*/
|
|
static inline void buffer_filled(struct tm6000_core *dev,
|
|
struct tm6000_dmaqueue *dma_q,
|
|
struct tm6000_buffer *buf)
|
|
{
|
|
/* Advice that buffer was filled */
|
|
dprintk(dev, V4L2_DEBUG_ISOC, "[%p/%d] wakeup\n", buf, buf->vb.i);
|
|
buf->vb.state = VIDEOBUF_DONE;
|
|
buf->vb.field_count++;
|
|
v4l2_get_timestamp(&buf->vb.ts);
|
|
|
|
list_del(&buf->vb.queue);
|
|
wake_up(&buf->vb.done);
|
|
}
|
|
|
|
/*
|
|
* Identify the tm5600/6000 buffer header type and properly handles
|
|
*/
|
|
static int copy_streams(u8 *data, unsigned long len,
|
|
struct urb *urb)
|
|
{
|
|
struct tm6000_dmaqueue *dma_q = urb->context;
|
|
struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
|
|
u8 *ptr = data, *endp = data+len;
|
|
unsigned long header = 0;
|
|
int rc = 0;
|
|
unsigned int cmd, cpysize, pktsize, size, field, block, line, pos = 0;
|
|
struct tm6000_buffer *vbuf = NULL;
|
|
char *voutp = NULL;
|
|
unsigned int linewidth;
|
|
|
|
if (!dev->radio) {
|
|
/* get video buffer */
|
|
get_next_buf(dma_q, &vbuf);
|
|
|
|
if (!vbuf)
|
|
return rc;
|
|
voutp = videobuf_to_vmalloc(&vbuf->vb);
|
|
|
|
if (!voutp)
|
|
return 0;
|
|
}
|
|
|
|
for (ptr = data; ptr < endp;) {
|
|
if (!dev->isoc_ctl.cmd) {
|
|
/* Header */
|
|
if (dev->isoc_ctl.tmp_buf_len > 0) {
|
|
/* from last urb or packet */
|
|
header = dev->isoc_ctl.tmp_buf;
|
|
if (4 - dev->isoc_ctl.tmp_buf_len > 0) {
|
|
memcpy((u8 *)&header +
|
|
dev->isoc_ctl.tmp_buf_len,
|
|
ptr,
|
|
4 - dev->isoc_ctl.tmp_buf_len);
|
|
ptr += 4 - dev->isoc_ctl.tmp_buf_len;
|
|
}
|
|
dev->isoc_ctl.tmp_buf_len = 0;
|
|
} else {
|
|
if (ptr + 3 >= endp) {
|
|
/* have incomplete header */
|
|
dev->isoc_ctl.tmp_buf_len = endp - ptr;
|
|
memcpy(&dev->isoc_ctl.tmp_buf, ptr,
|
|
dev->isoc_ctl.tmp_buf_len);
|
|
return rc;
|
|
}
|
|
/* Seek for sync */
|
|
for (; ptr < endp - 3; ptr++) {
|
|
if (*(ptr + 3) == 0x47)
|
|
break;
|
|
}
|
|
/* Get message header */
|
|
header = *(unsigned long *)ptr;
|
|
ptr += 4;
|
|
}
|
|
|
|
/* split the header fields */
|
|
size = ((header & 0x7e) << 1);
|
|
if (size > 0)
|
|
size -= 4;
|
|
block = (header >> 7) & 0xf;
|
|
field = (header >> 11) & 0x1;
|
|
line = (header >> 12) & 0x1ff;
|
|
cmd = (header >> 21) & 0x7;
|
|
/* Validates haeder fields */
|
|
if (size > TM6000_URB_MSG_LEN)
|
|
size = TM6000_URB_MSG_LEN;
|
|
pktsize = TM6000_URB_MSG_LEN;
|
|
/*
|
|
* calculate position in buffer and change the buffer
|
|
*/
|
|
switch (cmd) {
|
|
case TM6000_URB_MSG_VIDEO:
|
|
if (!dev->radio) {
|
|
if ((dev->isoc_ctl.vfield != field) &&
|
|
(field == 1)) {
|
|
/*
|
|
* Announces that a new buffer
|
|
* were filled
|
|
*/
|
|
buffer_filled(dev, dma_q, vbuf);
|
|
dprintk(dev, V4L2_DEBUG_ISOC,
|
|
"new buffer filled\n");
|
|
get_next_buf(dma_q, &vbuf);
|
|
if (!vbuf)
|
|
return rc;
|
|
voutp = videobuf_to_vmalloc(&vbuf->vb);
|
|
if (!voutp)
|
|
return rc;
|
|
memset(voutp, 0, vbuf->vb.size);
|
|
}
|
|
linewidth = vbuf->vb.width << 1;
|
|
pos = ((line << 1) - field - 1) *
|
|
linewidth + block * TM6000_URB_MSG_LEN;
|
|
/* Don't allow to write out of the buffer */
|
|
if (pos + size > vbuf->vb.size)
|
|
cmd = TM6000_URB_MSG_ERR;
|
|
dev->isoc_ctl.vfield = field;
|
|
}
|
|
break;
|
|
case TM6000_URB_MSG_VBI:
|
|
break;
|
|
case TM6000_URB_MSG_AUDIO:
|
|
case TM6000_URB_MSG_PTS:
|
|
size = pktsize; /* Size is always 180 bytes */
|
|
break;
|
|
}
|
|
} else {
|
|
/* Continue the last copy */
|
|
cmd = dev->isoc_ctl.cmd;
|
|
size = dev->isoc_ctl.size;
|
|
pos = dev->isoc_ctl.pos;
|
|
pktsize = dev->isoc_ctl.pktsize;
|
|
field = dev->isoc_ctl.field;
|
|
}
|
|
cpysize = (endp - ptr > size) ? size : endp - ptr;
|
|
if (cpysize) {
|
|
/* copy data in different buffers */
|
|
switch (cmd) {
|
|
case TM6000_URB_MSG_VIDEO:
|
|
/* Fills video buffer */
|
|
if (vbuf)
|
|
memcpy(&voutp[pos], ptr, cpysize);
|
|
break;
|
|
case TM6000_URB_MSG_AUDIO: {
|
|
int i;
|
|
for (i = 0; i < cpysize; i += 2)
|
|
swab16s((u16 *)(ptr + i));
|
|
|
|
tm6000_call_fillbuf(dev, TM6000_AUDIO, ptr, cpysize);
|
|
break;
|
|
}
|
|
case TM6000_URB_MSG_VBI:
|
|
/* Need some code to copy vbi buffer */
|
|
break;
|
|
case TM6000_URB_MSG_PTS: {
|
|
/* Need some code to copy pts */
|
|
u32 pts;
|
|
pts = *(u32 *)ptr;
|
|
dprintk(dev, V4L2_DEBUG_ISOC, "field %d, PTS %x",
|
|
field, pts);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (ptr + pktsize > endp) {
|
|
/*
|
|
* End of URB packet, but cmd processing is not
|
|
* complete. Preserve the state for a next packet
|
|
*/
|
|
dev->isoc_ctl.pos = pos + cpysize;
|
|
dev->isoc_ctl.size = size - cpysize;
|
|
dev->isoc_ctl.cmd = cmd;
|
|
dev->isoc_ctl.field = field;
|
|
dev->isoc_ctl.pktsize = pktsize - (endp - ptr);
|
|
ptr += endp - ptr;
|
|
} else {
|
|
dev->isoc_ctl.cmd = 0;
|
|
ptr += pktsize;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Identify the tm5600/6000 buffer header type and properly handles
|
|
*/
|
|
static int copy_multiplexed(u8 *ptr, unsigned long len,
|
|
struct urb *urb)
|
|
{
|
|
struct tm6000_dmaqueue *dma_q = urb->context;
|
|
struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
|
|
unsigned int pos = dev->isoc_ctl.pos, cpysize;
|
|
int rc = 1;
|
|
struct tm6000_buffer *buf;
|
|
char *outp = NULL;
|
|
|
|
get_next_buf(dma_q, &buf);
|
|
if (buf)
|
|
outp = videobuf_to_vmalloc(&buf->vb);
|
|
|
|
if (!outp)
|
|
return 0;
|
|
|
|
while (len > 0) {
|
|
cpysize = min(len, buf->vb.size-pos);
|
|
memcpy(&outp[pos], ptr, cpysize);
|
|
pos += cpysize;
|
|
ptr += cpysize;
|
|
len -= cpysize;
|
|
if (pos >= buf->vb.size) {
|
|
pos = 0;
|
|
/* Announces that a new buffer were filled */
|
|
buffer_filled(dev, dma_q, buf);
|
|
dprintk(dev, V4L2_DEBUG_ISOC, "new buffer filled\n");
|
|
get_next_buf(dma_q, &buf);
|
|
if (!buf)
|
|
break;
|
|
outp = videobuf_to_vmalloc(&(buf->vb));
|
|
if (!outp)
|
|
return rc;
|
|
pos = 0;
|
|
}
|
|
}
|
|
|
|
dev->isoc_ctl.pos = pos;
|
|
return rc;
|
|
}
|
|
|
|
static inline void print_err_status(struct tm6000_core *dev,
|
|
int packet, int status)
|
|
{
|
|
char *errmsg = "Unknown";
|
|
|
|
switch (status) {
|
|
case -ENOENT:
|
|
errmsg = "unlinked synchronously";
|
|
break;
|
|
case -ECONNRESET:
|
|
errmsg = "unlinked asynchronously";
|
|
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) {
|
|
dprintk(dev, V4L2_DEBUG_QUEUE, "URB status %d [%s].\n",
|
|
status, errmsg);
|
|
} else {
|
|
dprintk(dev, V4L2_DEBUG_QUEUE, "URB packet %d, status %d [%s].\n",
|
|
packet, status, errmsg);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Controls the isoc copy of each urb packet
|
|
*/
|
|
static inline int tm6000_isoc_copy(struct urb *urb)
|
|
{
|
|
struct tm6000_dmaqueue *dma_q = urb->context;
|
|
struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
|
|
int i, len = 0, rc = 1, status;
|
|
char *p;
|
|
|
|
if (urb->status < 0) {
|
|
print_err_status(dev, -1, urb->status);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
status = urb->iso_frame_desc[i].status;
|
|
|
|
if (status < 0) {
|
|
print_err_status(dev, i, status);
|
|
continue;
|
|
}
|
|
|
|
len = urb->iso_frame_desc[i].actual_length;
|
|
|
|
if (len > 0) {
|
|
p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
|
|
if (!urb->iso_frame_desc[i].status) {
|
|
if ((dev->fourcc) == V4L2_PIX_FMT_TM6000) {
|
|
rc = copy_multiplexed(p, len, urb);
|
|
if (rc <= 0)
|
|
return rc;
|
|
} else {
|
|
copy_streams(p, len, urb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
* URB control
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* IRQ callback, called by URB callback
|
|
*/
|
|
static void tm6000_irq_callback(struct urb *urb)
|
|
{
|
|
struct tm6000_dmaqueue *dma_q = urb->context;
|
|
struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
|
|
unsigned long flags;
|
|
int i;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
case -ETIMEDOUT:
|
|
break;
|
|
|
|
case -ECONNRESET:
|
|
case -ENOENT:
|
|
case -ESHUTDOWN:
|
|
return;
|
|
|
|
default:
|
|
tm6000_err("urb completion error %d.\n", urb->status);
|
|
break;
|
|
}
|
|
|
|
spin_lock_irqsave(&dev->slock, flags);
|
|
tm6000_isoc_copy(urb);
|
|
spin_unlock_irqrestore(&dev->slock, flags);
|
|
|
|
/* 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 = usb_submit_urb(urb, GFP_ATOMIC);
|
|
if (urb->status)
|
|
tm6000_err("urb resubmit failed (error=%i)\n",
|
|
urb->status);
|
|
}
|
|
|
|
/*
|
|
* Allocate URB buffers
|
|
*/
|
|
static int tm6000_alloc_urb_buffers(struct tm6000_core *dev)
|
|
{
|
|
int num_bufs = TM6000_NUM_URB_BUF;
|
|
int i;
|
|
|
|
if (dev->urb_buffer)
|
|
return 0;
|
|
|
|
dev->urb_buffer = kmalloc_array(num_bufs, sizeof(void *), GFP_KERNEL);
|
|
if (!dev->urb_buffer)
|
|
return -ENOMEM;
|
|
|
|
dev->urb_dma = kmalloc_array(num_bufs, sizeof(dma_addr_t *),
|
|
GFP_KERNEL);
|
|
if (!dev->urb_dma)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num_bufs; i++) {
|
|
dev->urb_buffer[i] = usb_alloc_coherent(
|
|
dev->udev, dev->urb_size,
|
|
GFP_KERNEL, &dev->urb_dma[i]);
|
|
if (!dev->urb_buffer[i]) {
|
|
tm6000_err("unable to allocate %i bytes for transfer buffer %i\n",
|
|
dev->urb_size, i);
|
|
return -ENOMEM;
|
|
}
|
|
memset(dev->urb_buffer[i], 0, dev->urb_size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Free URB buffers
|
|
*/
|
|
static int tm6000_free_urb_buffers(struct tm6000_core *dev)
|
|
{
|
|
int i;
|
|
|
|
if (!dev->urb_buffer)
|
|
return 0;
|
|
|
|
for (i = 0; i < TM6000_NUM_URB_BUF; i++) {
|
|
if (dev->urb_buffer[i]) {
|
|
usb_free_coherent(dev->udev,
|
|
dev->urb_size,
|
|
dev->urb_buffer[i],
|
|
dev->urb_dma[i]);
|
|
dev->urb_buffer[i] = NULL;
|
|
}
|
|
}
|
|
kfree(dev->urb_buffer);
|
|
kfree(dev->urb_dma);
|
|
dev->urb_buffer = NULL;
|
|
dev->urb_dma = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Stop and Deallocate URBs
|
|
*/
|
|
static void tm6000_uninit_isoc(struct tm6000_core *dev)
|
|
{
|
|
struct urb *urb;
|
|
int i;
|
|
|
|
dev->isoc_ctl.buf = NULL;
|
|
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
|
|
urb = dev->isoc_ctl.urb[i];
|
|
if (urb) {
|
|
usb_kill_urb(urb);
|
|
usb_unlink_urb(urb);
|
|
usb_free_urb(urb);
|
|
dev->isoc_ctl.urb[i] = NULL;
|
|
}
|
|
dev->isoc_ctl.transfer_buffer[i] = NULL;
|
|
}
|
|
|
|
if (!keep_urb)
|
|
tm6000_free_urb_buffers(dev);
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Assign URBs and start IRQ
|
|
*/
|
|
static int tm6000_prepare_isoc(struct tm6000_core *dev)
|
|
{
|
|
struct tm6000_dmaqueue *dma_q = &dev->vidq;
|
|
int i, j, sb_size, pipe, size, max_packets;
|
|
int num_bufs = TM6000_NUM_URB_BUF;
|
|
struct urb *urb;
|
|
|
|
/* De-allocates all pending stuff */
|
|
tm6000_uninit_isoc(dev);
|
|
/* Stop interrupt USB pipe */
|
|
tm6000_ir_int_stop(dev);
|
|
|
|
usb_set_interface(dev->udev,
|
|
dev->isoc_in.bInterfaceNumber,
|
|
dev->isoc_in.bAlternateSetting);
|
|
|
|
/* Start interrupt USB pipe */
|
|
tm6000_ir_int_start(dev);
|
|
|
|
pipe = usb_rcvisocpipe(dev->udev,
|
|
dev->isoc_in.endp->desc.bEndpointAddress &
|
|
USB_ENDPOINT_NUMBER_MASK);
|
|
|
|
size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe));
|
|
|
|
if (size > dev->isoc_in.maxsize)
|
|
size = dev->isoc_in.maxsize;
|
|
|
|
dev->isoc_ctl.max_pkt_size = size;
|
|
|
|
max_packets = TM6000_MAX_ISO_PACKETS;
|
|
sb_size = max_packets * size;
|
|
dev->urb_size = sb_size;
|
|
|
|
dev->isoc_ctl.num_bufs = num_bufs;
|
|
|
|
dev->isoc_ctl.urb = kmalloc_array(num_bufs, sizeof(void *),
|
|
GFP_KERNEL);
|
|
if (!dev->isoc_ctl.urb)
|
|
return -ENOMEM;
|
|
|
|
dev->isoc_ctl.transfer_buffer = kmalloc_array(num_bufs,
|
|
sizeof(void *),
|
|
GFP_KERNEL);
|
|
if (!dev->isoc_ctl.transfer_buffer) {
|
|
kfree(dev->isoc_ctl.urb);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dprintk(dev, V4L2_DEBUG_QUEUE, "Allocating %d x %d packets (%d bytes) of %d bytes each to handle %u size\n",
|
|
max_packets, num_bufs, sb_size,
|
|
dev->isoc_in.maxsize, size);
|
|
|
|
|
|
if (tm6000_alloc_urb_buffers(dev) < 0) {
|
|
tm6000_err("cannot allocate memory for urb buffers\n");
|
|
|
|
/* call free, as some buffers might have been allocated */
|
|
tm6000_free_urb_buffers(dev);
|
|
kfree(dev->isoc_ctl.urb);
|
|
kfree(dev->isoc_ctl.transfer_buffer);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* 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) {
|
|
tm6000_uninit_isoc(dev);
|
|
tm6000_free_urb_buffers(dev);
|
|
return -ENOMEM;
|
|
}
|
|
dev->isoc_ctl.urb[i] = urb;
|
|
|
|
urb->transfer_dma = dev->urb_dma[i];
|
|
dev->isoc_ctl.transfer_buffer[i] = dev->urb_buffer[i];
|
|
|
|
usb_fill_bulk_urb(urb, dev->udev, pipe,
|
|
dev->isoc_ctl.transfer_buffer[i], sb_size,
|
|
tm6000_irq_callback, dma_q);
|
|
urb->interval = dev->isoc_in.endp->desc.bInterval;
|
|
urb->number_of_packets = max_packets;
|
|
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
|
|
|
|
for (j = 0; j < max_packets; j++) {
|
|
urb->iso_frame_desc[j].offset = size * j;
|
|
urb->iso_frame_desc[j].length = size;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tm6000_start_thread(struct tm6000_core *dev)
|
|
{
|
|
struct tm6000_dmaqueue *dma_q = &dev->vidq;
|
|
int i;
|
|
|
|
dma_q->frame = 0;
|
|
dma_q->ini_jiffies = jiffies;
|
|
|
|
init_waitqueue_head(&dma_q->wq);
|
|
|
|
/* submit urbs and enables IRQ */
|
|
for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
|
|
int rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC);
|
|
if (rc) {
|
|
tm6000_err("submit of urb %i failed (error=%i)\n", i,
|
|
rc);
|
|
tm6000_uninit_isoc(dev);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Videobuf operations
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
static int
|
|
buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
|
|
{
|
|
struct tm6000_fh *fh = vq->priv_data;
|
|
|
|
*size = fh->fmt->depth * fh->width * fh->height >> 3;
|
|
if (0 == *count)
|
|
*count = TM6000_DEF_BUF;
|
|
|
|
if (*count < TM6000_MIN_BUF)
|
|
*count = TM6000_MIN_BUF;
|
|
|
|
while (*size * *count > vid_limit * 1024 * 1024)
|
|
(*count)--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void free_buffer(struct videobuf_queue *vq, struct tm6000_buffer *buf)
|
|
{
|
|
struct tm6000_fh *fh = vq->priv_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
unsigned long flags;
|
|
|
|
BUG_ON(in_interrupt());
|
|
|
|
/* 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 tm6000_fh *fh = vq->priv_data;
|
|
struct tm6000_buffer *buf = container_of(vb, struct tm6000_buffer, vb);
|
|
struct tm6000_core *dev = fh->dev;
|
|
int rc = 0;
|
|
|
|
BUG_ON(NULL == fh->fmt);
|
|
|
|
|
|
/* FIXME: It assumes depth=2 */
|
|
/* The only currently supported format is 16 bits/pixel */
|
|
buf->vb.size = fh->fmt->depth*fh->width*fh->height >> 3;
|
|
if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
|
|
return -EINVAL;
|
|
|
|
if (buf->fmt != fh->fmt ||
|
|
buf->vb.width != fh->width ||
|
|
buf->vb.height != fh->height ||
|
|
buf->vb.field != field) {
|
|
buf->fmt = fh->fmt;
|
|
buf->vb.width = fh->width;
|
|
buf->vb.height = fh->height;
|
|
buf->vb.field = field;
|
|
buf->vb.state = VIDEOBUF_NEEDS_INIT;
|
|
}
|
|
|
|
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
|
|
rc = videobuf_iolock(vq, &buf->vb, NULL);
|
|
if (rc != 0)
|
|
goto fail;
|
|
}
|
|
|
|
if (!dev->isoc_ctl.num_bufs) {
|
|
rc = tm6000_prepare_isoc(dev);
|
|
if (rc < 0)
|
|
goto fail;
|
|
|
|
rc = tm6000_start_thread(dev);
|
|
if (rc < 0)
|
|
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 tm6000_buffer *buf = container_of(vb, struct tm6000_buffer, vb);
|
|
struct tm6000_fh *fh = vq->priv_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
struct tm6000_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 tm6000_buffer *buf = container_of(vb, struct tm6000_buffer, vb);
|
|
|
|
free_buffer(vq, buf);
|
|
}
|
|
|
|
static const struct videobuf_queue_ops tm6000_video_qops = {
|
|
.buf_setup = buffer_setup,
|
|
.buf_prepare = buffer_prepare,
|
|
.buf_queue = buffer_queue,
|
|
.buf_release = buffer_release,
|
|
};
|
|
|
|
/* ------------------------------------------------------------------
|
|
* IOCTL handling
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
static bool is_res_read(struct tm6000_core *dev, struct tm6000_fh *fh)
|
|
{
|
|
/* Is the current fh handling it? if so, that's OK */
|
|
if (dev->resources == fh && dev->is_res_read)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool is_res_streaming(struct tm6000_core *dev, struct tm6000_fh *fh)
|
|
{
|
|
/* Is the current fh handling it? if so, that's OK */
|
|
if (dev->resources == fh)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool res_get(struct tm6000_core *dev, struct tm6000_fh *fh,
|
|
bool is_res_read)
|
|
{
|
|
/* Is the current fh handling it? if so, that's OK */
|
|
if (dev->resources == fh && dev->is_res_read == is_res_read)
|
|
return true;
|
|
|
|
/* is it free? */
|
|
if (dev->resources)
|
|
return false;
|
|
|
|
/* grab it */
|
|
dev->resources = fh;
|
|
dev->is_res_read = is_res_read;
|
|
dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: get\n");
|
|
return true;
|
|
}
|
|
|
|
static void res_free(struct tm6000_core *dev, struct tm6000_fh *fh)
|
|
{
|
|
/* Is the current fh handling it? if so, that's OK */
|
|
if (dev->resources != fh)
|
|
return;
|
|
|
|
dev->resources = NULL;
|
|
dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: put\n");
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
* IOCTL vidioc handling
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
static int vidioc_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev;
|
|
struct video_device *vdev = video_devdata(file);
|
|
|
|
strscpy(cap->driver, "tm6000", sizeof(cap->driver));
|
|
strscpy(cap->card, "Trident TVMaster TM5600/6000/6010",
|
|
sizeof(cap->card));
|
|
usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
|
|
if (dev->tuner_type != TUNER_ABSENT)
|
|
cap->device_caps |= V4L2_CAP_TUNER;
|
|
if (vdev->vfl_type == VFL_TYPE_GRABBER)
|
|
cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE |
|
|
V4L2_CAP_STREAMING |
|
|
V4L2_CAP_READWRITE;
|
|
else
|
|
cap->device_caps |= V4L2_CAP_RADIO;
|
|
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS |
|
|
V4L2_CAP_RADIO | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *f)
|
|
{
|
|
if (f->index >= ARRAY_SIZE(format))
|
|
return -EINVAL;
|
|
|
|
strscpy(f->description, format[f->index].name, sizeof(f->description));
|
|
f->pixelformat = format[f->index].fourcc;
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
|
|
f->fmt.pix.width = fh->width;
|
|
f->fmt.pix.height = fh->height;
|
|
f->fmt.pix.field = fh->vb_vidq.field;
|
|
f->fmt.pix.pixelformat = fh->fmt->fourcc;
|
|
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
|
|
f->fmt.pix.bytesperline =
|
|
(f->fmt.pix.width * fh->fmt->depth) >> 3;
|
|
f->fmt.pix.sizeimage =
|
|
f->fmt.pix.height * f->fmt.pix.bytesperline;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct tm6000_fmt *format_by_fourcc(unsigned int fourcc)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(format); i++)
|
|
if (format[i].fourcc == fourcc)
|
|
return format+i;
|
|
return NULL;
|
|
}
|
|
|
|
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev;
|
|
struct tm6000_fmt *fmt;
|
|
enum v4l2_field field;
|
|
|
|
fmt = format_by_fourcc(f->fmt.pix.pixelformat);
|
|
if (NULL == fmt) {
|
|
dprintk(dev, 2, "Fourcc format (0x%08x) invalid.\n",
|
|
f->fmt.pix.pixelformat);
|
|
return -EINVAL;
|
|
}
|
|
|
|
field = f->fmt.pix.field;
|
|
|
|
field = V4L2_FIELD_INTERLACED;
|
|
|
|
tm6000_get_std_res(dev);
|
|
|
|
f->fmt.pix.width = dev->width;
|
|
f->fmt.pix.height = dev->height;
|
|
|
|
f->fmt.pix.width &= ~0x01;
|
|
|
|
f->fmt.pix.field = field;
|
|
|
|
f->fmt.pix.bytesperline =
|
|
(f->fmt.pix.width * fmt->depth) >> 3;
|
|
f->fmt.pix.sizeimage =
|
|
f->fmt.pix.height * f->fmt.pix.bytesperline;
|
|
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*FIXME: This seems to be generic enough to be at videodev2 */
|
|
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
int ret = vidioc_try_fmt_vid_cap(file, fh, f);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
|
|
fh->width = f->fmt.pix.width;
|
|
fh->height = f->fmt.pix.height;
|
|
fh->vb_vidq.field = f->fmt.pix.field;
|
|
fh->type = f->type;
|
|
|
|
dev->fourcc = f->fmt.pix.pixelformat;
|
|
|
|
tm6000_set_fourcc_format(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_reqbufs(struct file *file, void *priv,
|
|
struct v4l2_requestbuffers *p)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
|
|
return videobuf_reqbufs(&fh->vb_vidq, p);
|
|
}
|
|
|
|
static int vidioc_querybuf(struct file *file, void *priv,
|
|
struct v4l2_buffer *p)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
|
|
return videobuf_querybuf(&fh->vb_vidq, p);
|
|
}
|
|
|
|
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
|
|
return videobuf_qbuf(&fh->vb_vidq, p);
|
|
}
|
|
|
|
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
|
|
return videobuf_dqbuf(&fh->vb_vidq, p,
|
|
file->f_flags & O_NONBLOCK);
|
|
}
|
|
|
|
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
if (i != fh->type)
|
|
return -EINVAL;
|
|
|
|
if (!res_get(dev, fh, false))
|
|
return -EBUSY;
|
|
return videobuf_streamon(&fh->vb_vidq);
|
|
}
|
|
|
|
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
if (i != fh->type)
|
|
return -EINVAL;
|
|
|
|
videobuf_streamoff(&fh->vb_vidq);
|
|
res_free(dev, fh);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
|
|
{
|
|
int rc = 0;
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
dev->norm = norm;
|
|
rc = tm6000_init_analog_mode(dev);
|
|
|
|
fh->width = dev->width;
|
|
fh->height = dev->height;
|
|
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, dev->norm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
*norm = dev->norm;
|
|
return 0;
|
|
}
|
|
|
|
static const char *iname[] = {
|
|
[TM6000_INPUT_TV] = "Television",
|
|
[TM6000_INPUT_COMPOSITE1] = "Composite 1",
|
|
[TM6000_INPUT_COMPOSITE2] = "Composite 2",
|
|
[TM6000_INPUT_SVIDEO] = "S-Video",
|
|
};
|
|
|
|
static int vidioc_enum_input(struct file *file, void *priv,
|
|
struct v4l2_input *i)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
unsigned int n;
|
|
|
|
n = i->index;
|
|
if (n >= 3)
|
|
return -EINVAL;
|
|
|
|
if (!dev->vinput[n].type)
|
|
return -EINVAL;
|
|
|
|
i->index = n;
|
|
|
|
if (dev->vinput[n].type == TM6000_INPUT_TV)
|
|
i->type = V4L2_INPUT_TYPE_TUNER;
|
|
else
|
|
i->type = V4L2_INPUT_TYPE_CAMERA;
|
|
|
|
strscpy(i->name, iname[dev->vinput[n].type], sizeof(i->name));
|
|
|
|
i->std = TM6000_STD;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
*i = dev->input;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
int rc = 0;
|
|
|
|
if (i >= 3)
|
|
return -EINVAL;
|
|
if (!dev->vinput[i].type)
|
|
return -EINVAL;
|
|
|
|
dev->input = i;
|
|
|
|
rc = vidioc_s_std(file, priv, dev->norm);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* --- controls ---------------------------------------------- */
|
|
|
|
static int tm6000_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct tm6000_core *dev = container_of(ctrl->handler, struct tm6000_core, ctrl_handler);
|
|
u8 val = ctrl->val;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_CONTRAST:
|
|
tm6000_set_reg(dev, TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, val);
|
|
return 0;
|
|
case V4L2_CID_BRIGHTNESS:
|
|
tm6000_set_reg(dev, TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, val);
|
|
return 0;
|
|
case V4L2_CID_SATURATION:
|
|
tm6000_set_reg(dev, TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, val);
|
|
return 0;
|
|
case V4L2_CID_HUE:
|
|
tm6000_set_reg(dev, TM6010_REQ07_R0B_CHROMA_HUE_PHASE_ADJ, val);
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct v4l2_ctrl_ops tm6000_ctrl_ops = {
|
|
.s_ctrl = tm6000_s_ctrl,
|
|
};
|
|
|
|
static int tm6000_radio_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct tm6000_core *dev = container_of(ctrl->handler,
|
|
struct tm6000_core, radio_ctrl_handler);
|
|
u8 val = ctrl->val;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_AUDIO_MUTE:
|
|
dev->ctl_mute = val;
|
|
tm6000_tvaudio_set_mute(dev, val);
|
|
return 0;
|
|
case V4L2_CID_AUDIO_VOLUME:
|
|
dev->ctl_volume = val;
|
|
tm6000_set_volume(dev, val);
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const struct v4l2_ctrl_ops tm6000_radio_ctrl_ops = {
|
|
.s_ctrl = tm6000_radio_s_ctrl,
|
|
};
|
|
|
|
static int vidioc_g_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (UNSET == dev->tuner_type)
|
|
return -ENOTTY;
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
|
|
strscpy(t->name, "Television", sizeof(t->name));
|
|
t->type = V4L2_TUNER_ANALOG_TV;
|
|
t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO;
|
|
t->rangehigh = 0xffffffffUL;
|
|
t->rxsubchans = V4L2_TUNER_SUB_STEREO;
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
|
|
|
|
t->audmode = dev->amode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_tuner(struct file *file, void *priv,
|
|
const struct v4l2_tuner *t)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (UNSET == dev->tuner_type)
|
|
return -ENOTTY;
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
|
|
if (t->audmode > V4L2_TUNER_MODE_STEREO)
|
|
dev->amode = V4L2_TUNER_MODE_STEREO;
|
|
else
|
|
dev->amode = t->audmode;
|
|
dprintk(dev, 3, "audio mode: %x\n", t->audmode);
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_g_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *f)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (UNSET == dev->tuner_type)
|
|
return -ENOTTY;
|
|
if (f->tuner)
|
|
return -EINVAL;
|
|
|
|
f->frequency = dev->freq;
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vidioc_s_frequency(struct file *file, void *priv,
|
|
const struct v4l2_frequency *f)
|
|
{
|
|
struct tm6000_fh *fh = priv;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (UNSET == dev->tuner_type)
|
|
return -ENOTTY;
|
|
if (f->tuner != 0)
|
|
return -EINVAL;
|
|
|
|
dev->freq = f->frequency;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int radio_g_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *t)
|
|
{
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
|
|
memset(t, 0, sizeof(*t));
|
|
strscpy(t->name, "Radio", sizeof(t->name));
|
|
t->type = V4L2_TUNER_RADIO;
|
|
t->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
|
|
t->rxsubchans = V4L2_TUNER_SUB_STEREO;
|
|
t->audmode = V4L2_TUNER_MODE_STEREO;
|
|
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int radio_s_tuner(struct file *file, void *priv,
|
|
const struct v4l2_tuner *t)
|
|
{
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (0 != t->index)
|
|
return -EINVAL;
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
File operations for the device
|
|
------------------------------------------------------------------*/
|
|
|
|
static int __tm6000_open(struct file *file)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct tm6000_core *dev = video_drvdata(file);
|
|
struct tm6000_fh *fh;
|
|
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
int rc;
|
|
int radio = 0;
|
|
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: open called (dev=%s)\n",
|
|
video_device_node_name(vdev));
|
|
|
|
switch (vdev->vfl_type) {
|
|
case VFL_TYPE_GRABBER:
|
|
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
break;
|
|
case VFL_TYPE_VBI:
|
|
type = V4L2_BUF_TYPE_VBI_CAPTURE;
|
|
break;
|
|
case VFL_TYPE_RADIO:
|
|
radio = 1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* If more than one user, mutex should be added */
|
|
dev->users++;
|
|
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "open dev=%s type=%s users=%d\n",
|
|
video_device_node_name(vdev), v4l2_type_names[type],
|
|
dev->users);
|
|
|
|
/* allocate + initialize per filehandle data */
|
|
fh = kzalloc(sizeof(*fh), GFP_KERNEL);
|
|
if (NULL == fh) {
|
|
dev->users--;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
v4l2_fh_init(&fh->fh, vdev);
|
|
file->private_data = fh;
|
|
fh->dev = dev;
|
|
fh->radio = radio;
|
|
dev->radio = radio;
|
|
fh->type = type;
|
|
dev->fourcc = format[0].fourcc;
|
|
|
|
fh->fmt = format_by_fourcc(dev->fourcc);
|
|
|
|
tm6000_get_std_res(dev);
|
|
|
|
fh->width = dev->width;
|
|
fh->height = dev->height;
|
|
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "Open: fh=%p, dev=%p, dev->vidq=%p\n",
|
|
fh, dev, &dev->vidq);
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty queued=%d\n",
|
|
list_empty(&dev->vidq.queued));
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty active=%d\n",
|
|
list_empty(&dev->vidq.active));
|
|
|
|
/* initialize hardware on analog mode */
|
|
rc = tm6000_init_analog_mode(dev);
|
|
if (rc < 0) {
|
|
v4l2_fh_exit(&fh->fh);
|
|
kfree(fh);
|
|
return rc;
|
|
}
|
|
|
|
dev->mode = TM6000_MODE_ANALOG;
|
|
|
|
if (!fh->radio) {
|
|
videobuf_queue_vmalloc_init(&fh->vb_vidq, &tm6000_video_qops,
|
|
NULL, &dev->slock,
|
|
fh->type,
|
|
V4L2_FIELD_INTERLACED,
|
|
sizeof(struct tm6000_buffer), fh, &dev->lock);
|
|
} else {
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "video_open: setting radio device\n");
|
|
tm6000_set_audio_rinput(dev);
|
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_radio);
|
|
tm6000_prepare_isoc(dev);
|
|
tm6000_start_thread(dev);
|
|
}
|
|
v4l2_fh_add(&fh->fh);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tm6000_open(struct file *file)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
int res;
|
|
|
|
mutex_lock(vdev->lock);
|
|
res = __tm6000_open(file);
|
|
mutex_unlock(vdev->lock);
|
|
return res;
|
|
}
|
|
|
|
static ssize_t
|
|
tm6000_read(struct file *file, char __user *data, size_t count, loff_t *pos)
|
|
{
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
|
|
if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
int res;
|
|
|
|
if (!res_get(fh->dev, fh, true))
|
|
return -EBUSY;
|
|
|
|
if (mutex_lock_interruptible(&dev->lock))
|
|
return -ERESTARTSYS;
|
|
res = videobuf_read_stream(&fh->vb_vidq, data, count, pos, 0,
|
|
file->f_flags & O_NONBLOCK);
|
|
mutex_unlock(&dev->lock);
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static __poll_t
|
|
__tm6000_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
__poll_t req_events = poll_requested_events(wait);
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_buffer *buf;
|
|
__poll_t res = 0;
|
|
|
|
if (v4l2_event_pending(&fh->fh))
|
|
res = EPOLLPRI;
|
|
else if (req_events & EPOLLPRI)
|
|
poll_wait(file, &fh->fh.wait, wait);
|
|
if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
|
|
return res | EPOLLERR;
|
|
|
|
if (!!is_res_streaming(fh->dev, fh))
|
|
return res | EPOLLERR;
|
|
|
|
if (!is_res_read(fh->dev, fh)) {
|
|
/* streaming capture */
|
|
if (list_empty(&fh->vb_vidq.stream))
|
|
return res | EPOLLERR;
|
|
buf = list_entry(fh->vb_vidq.stream.next, struct tm6000_buffer, vb.stream);
|
|
poll_wait(file, &buf->vb.done, wait);
|
|
if (buf->vb.state == VIDEOBUF_DONE ||
|
|
buf->vb.state == VIDEOBUF_ERROR)
|
|
return res | EPOLLIN | EPOLLRDNORM;
|
|
} else if (req_events & (EPOLLIN | EPOLLRDNORM)) {
|
|
/* read() capture */
|
|
return res | videobuf_poll_stream(file, &fh->vb_vidq, wait);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static __poll_t tm6000_poll(struct file *file, struct poll_table_struct *wait)
|
|
{
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
__poll_t res;
|
|
|
|
mutex_lock(&dev->lock);
|
|
res = __tm6000_poll(file, wait);
|
|
mutex_unlock(&dev->lock);
|
|
return res;
|
|
}
|
|
|
|
static int tm6000_release(struct file *file)
|
|
{
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
struct video_device *vdev = video_devdata(file);
|
|
|
|
dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: close called (dev=%s, users=%d)\n",
|
|
video_device_node_name(vdev), dev->users);
|
|
|
|
mutex_lock(&dev->lock);
|
|
dev->users--;
|
|
|
|
res_free(dev, fh);
|
|
|
|
if (!dev->users) {
|
|
tm6000_uninit_isoc(dev);
|
|
|
|
/* Stop interrupt USB pipe */
|
|
tm6000_ir_int_stop(dev);
|
|
|
|
usb_reset_configuration(dev->udev);
|
|
|
|
if (dev->int_in.endp)
|
|
usb_set_interface(dev->udev,
|
|
dev->isoc_in.bInterfaceNumber, 2);
|
|
else
|
|
usb_set_interface(dev->udev,
|
|
dev->isoc_in.bInterfaceNumber, 0);
|
|
|
|
/* Start interrupt USB pipe */
|
|
tm6000_ir_int_start(dev);
|
|
|
|
if (!fh->radio)
|
|
videobuf_mmap_free(&fh->vb_vidq);
|
|
}
|
|
v4l2_fh_del(&fh->fh);
|
|
v4l2_fh_exit(&fh->fh);
|
|
kfree(fh);
|
|
mutex_unlock(&dev->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tm6000_mmap(struct file *file, struct vm_area_struct * vma)
|
|
{
|
|
struct tm6000_fh *fh = file->private_data;
|
|
struct tm6000_core *dev = fh->dev;
|
|
int res;
|
|
|
|
if (mutex_lock_interruptible(&dev->lock))
|
|
return -ERESTARTSYS;
|
|
res = videobuf_mmap_mapper(&fh->vb_vidq, vma);
|
|
mutex_unlock(&dev->lock);
|
|
return res;
|
|
}
|
|
|
|
static const struct v4l2_file_operations tm6000_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tm6000_open,
|
|
.release = tm6000_release,
|
|
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
|
|
.read = tm6000_read,
|
|
.poll = tm6000_poll,
|
|
.mmap = tm6000_mmap,
|
|
};
|
|
|
|
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,
|
|
.vidioc_s_std = vidioc_s_std,
|
|
.vidioc_g_std = vidioc_g_std,
|
|
.vidioc_enum_input = vidioc_enum_input,
|
|
.vidioc_g_input = vidioc_g_input,
|
|
.vidioc_s_input = vidioc_s_input,
|
|
.vidioc_g_tuner = vidioc_g_tuner,
|
|
.vidioc_s_tuner = vidioc_s_tuner,
|
|
.vidioc_g_frequency = vidioc_g_frequency,
|
|
.vidioc_s_frequency = vidioc_s_frequency,
|
|
.vidioc_streamon = vidioc_streamon,
|
|
.vidioc_streamoff = vidioc_streamoff,
|
|
.vidioc_reqbufs = vidioc_reqbufs,
|
|
.vidioc_querybuf = vidioc_querybuf,
|
|
.vidioc_qbuf = vidioc_qbuf,
|
|
.vidioc_dqbuf = vidioc_dqbuf,
|
|
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
|
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
|
};
|
|
|
|
static struct video_device tm6000_template = {
|
|
.name = "tm6000",
|
|
.fops = &tm6000_fops,
|
|
.ioctl_ops = &video_ioctl_ops,
|
|
.release = video_device_release_empty,
|
|
.tvnorms = TM6000_STD,
|
|
};
|
|
|
|
static const struct v4l2_file_operations radio_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tm6000_open,
|
|
.poll = v4l2_ctrl_poll,
|
|
.release = tm6000_release,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
};
|
|
|
|
static const struct v4l2_ioctl_ops radio_ioctl_ops = {
|
|
.vidioc_querycap = vidioc_querycap,
|
|
.vidioc_g_tuner = radio_g_tuner,
|
|
.vidioc_s_tuner = radio_s_tuner,
|
|
.vidioc_g_frequency = vidioc_g_frequency,
|
|
.vidioc_s_frequency = vidioc_s_frequency,
|
|
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
|
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
|
};
|
|
|
|
static struct video_device tm6000_radio_template = {
|
|
.name = "tm6000",
|
|
.fops = &radio_fops,
|
|
.ioctl_ops = &radio_ioctl_ops,
|
|
};
|
|
|
|
/* -----------------------------------------------------------------
|
|
* Initialization and module stuff
|
|
* ------------------------------------------------------------------
|
|
*/
|
|
|
|
static void vdev_init(struct tm6000_core *dev,
|
|
struct video_device *vfd,
|
|
const struct video_device
|
|
*template, const char *type_name)
|
|
{
|
|
*vfd = *template;
|
|
vfd->v4l2_dev = &dev->v4l2_dev;
|
|
vfd->release = video_device_release_empty;
|
|
vfd->lock = &dev->lock;
|
|
|
|
snprintf(vfd->name, sizeof(vfd->name), "%s %s", dev->name, type_name);
|
|
|
|
video_set_drvdata(vfd, dev);
|
|
}
|
|
|
|
int tm6000_v4l2_register(struct tm6000_core *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
v4l2_ctrl_handler_init(&dev->ctrl_handler, 6);
|
|
v4l2_ctrl_handler_init(&dev->radio_ctrl_handler, 2);
|
|
v4l2_ctrl_new_std(&dev->radio_ctrl_handler, &tm6000_radio_ctrl_ops,
|
|
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0);
|
|
v4l2_ctrl_new_std(&dev->radio_ctrl_handler, &tm6000_radio_ctrl_ops,
|
|
V4L2_CID_AUDIO_VOLUME, -15, 15, 1, 0);
|
|
v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
|
|
V4L2_CID_BRIGHTNESS, 0, 255, 1, 54);
|
|
v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
|
|
V4L2_CID_CONTRAST, 0, 255, 1, 119);
|
|
v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
|
|
V4L2_CID_SATURATION, 0, 255, 1, 112);
|
|
v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
|
|
V4L2_CID_HUE, -128, 127, 1, 0);
|
|
v4l2_ctrl_add_handler(&dev->ctrl_handler,
|
|
&dev->radio_ctrl_handler, NULL, false);
|
|
|
|
if (dev->radio_ctrl_handler.error)
|
|
ret = dev->radio_ctrl_handler.error;
|
|
if (!ret && dev->ctrl_handler.error)
|
|
ret = dev->ctrl_handler.error;
|
|
if (ret)
|
|
goto free_ctrl;
|
|
|
|
vdev_init(dev, &dev->vfd, &tm6000_template, "video");
|
|
|
|
dev->vfd.ctrl_handler = &dev->ctrl_handler;
|
|
|
|
/* init video dma queues */
|
|
INIT_LIST_HEAD(&dev->vidq.active);
|
|
INIT_LIST_HEAD(&dev->vidq.queued);
|
|
|
|
ret = video_register_device(&dev->vfd, VFL_TYPE_GRABBER, video_nr);
|
|
|
|
if (ret < 0) {
|
|
printk(KERN_INFO "%s: can't register video device\n",
|
|
dev->name);
|
|
goto free_ctrl;
|
|
}
|
|
|
|
printk(KERN_INFO "%s: registered device %s\n",
|
|
dev->name, video_device_node_name(&dev->vfd));
|
|
|
|
if (dev->caps.has_radio) {
|
|
vdev_init(dev, &dev->radio_dev, &tm6000_radio_template,
|
|
"radio");
|
|
dev->radio_dev.ctrl_handler = &dev->radio_ctrl_handler;
|
|
ret = video_register_device(&dev->radio_dev, VFL_TYPE_RADIO,
|
|
radio_nr);
|
|
if (ret < 0) {
|
|
printk(KERN_INFO "%s: can't register radio device\n",
|
|
dev->name);
|
|
goto unreg_video;
|
|
}
|
|
|
|
printk(KERN_INFO "%s: registered device %s\n",
|
|
dev->name, video_device_node_name(&dev->radio_dev));
|
|
}
|
|
|
|
printk(KERN_INFO "Trident TVMaster TM5600/TM6000/TM6010 USB2 board (Load status: %d)\n", ret);
|
|
return ret;
|
|
|
|
unreg_video:
|
|
video_unregister_device(&dev->vfd);
|
|
free_ctrl:
|
|
v4l2_ctrl_handler_free(&dev->ctrl_handler);
|
|
v4l2_ctrl_handler_free(&dev->radio_ctrl_handler);
|
|
return ret;
|
|
}
|
|
|
|
int tm6000_v4l2_unregister(struct tm6000_core *dev)
|
|
{
|
|
video_unregister_device(&dev->vfd);
|
|
|
|
/* if URB buffers are still allocated free them now */
|
|
tm6000_free_urb_buffers(dev);
|
|
|
|
video_unregister_device(&dev->radio_dev);
|
|
return 0;
|
|
}
|
|
|
|
int tm6000_v4l2_exit(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
module_param(video_nr, int, 0);
|
|
MODULE_PARM_DESC(video_nr, "Allow changing video device number");
|
|
|
|
module_param_named(debug, tm6000_debug, int, 0444);
|
|
MODULE_PARM_DESC(debug, "activates debug info");
|
|
|
|
module_param(vid_limit, int, 0644);
|
|
MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes");
|
|
|
|
module_param(keep_urb, bool, 0);
|
|
MODULE_PARM_DESC(keep_urb, "Keep urb buffers allocated even when the device is closed by the user");
|