// 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[] = { 
 | 
    { 
 | 
        .fourcc   = V4L2_PIX_FMT_YUYV, 
 | 
        .depth    = 16, 
 | 
    }, { 
 | 
        .fourcc   = V4L2_PIX_FMT_UYVY, 
 | 
        .depth    = 16, 
 | 
    }, { 
 | 
        .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++; 
 | 
    buf->vb.ts = ktime_get_ns(); 
 | 
  
 | 
    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 header 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(*dev->urb_buffer), 
 | 
                    GFP_KERNEL); 
 | 
    if (!dev->urb_buffer) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    dev->urb_dma = kmalloc_array(num_bufs, sizeof(*dev->urb_dma), 
 | 
                     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; 
 | 
  
 | 
    strscpy(cap->driver, "tm6000", sizeof(cap->driver)); 
 | 
    strscpy(cap->card, "Trident TM5600/6000/6010", sizeof(cap->card)); 
 | 
    usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info)); 
 | 
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | 
 | 
                V4L2_CAP_DEVICE_CAPS; 
 | 
    if (dev->tuner_type != TUNER_ABSENT) 
 | 
        cap->capabilities |= V4L2_CAP_TUNER; 
 | 
    if (dev->caps.has_radio) 
 | 
        cap->capabilities |= V4L2_CAP_RADIO; 
 | 
  
 | 
    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; 
 | 
  
 | 
    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_VIDEO: 
 | 
        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; 
 | 
    dev->vfd.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | 
 | 
                   V4L2_CAP_READWRITE; 
 | 
    if (dev->tuner_type != TUNER_ABSENT) 
 | 
        dev->vfd.device_caps |= V4L2_CAP_TUNER; 
 | 
  
 | 
    /* init video dma queues */ 
 | 
    INIT_LIST_HEAD(&dev->vidq.active); 
 | 
    INIT_LIST_HEAD(&dev->vidq.queued); 
 | 
  
 | 
    ret = video_register_device(&dev->vfd, VFL_TYPE_VIDEO, 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; 
 | 
        dev->radio_dev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; 
 | 
        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"); 
 |