📄 tm6000-video.c
字号:
/* tm6000-video.c - driver for TM5600/TM6000 USB video capture devices Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> - Fixed module load/unload This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */#include <linux/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/version.h>#include <linux/usb.h>#include "compat.h"#include <linux/videodev2.h>#include <media/v4l2-ioctl.h>#include <linux/interrupt.h>#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)#include <linux/kthread.h>#endif#include <linux/highmem.h>#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)#include <linux/freezer.h>#endif#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_MAX_ISO_PACKETS 40 /* 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 *//* Debug level */int tm6000_debug;/* supported controls */static struct v4l2_queryctrl tm6000_qctrl[] = { { .id = V4L2_CID_BRIGHTNESS, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Brightness", .minimum = 0, .maximum = 255, .step = 1, .default_value = 54, .flags = 0, }, { .id = V4L2_CID_CONTRAST, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Contrast", .minimum = 0, .maximum = 255, .step = 0x1, .default_value = 119, .flags = 0, }, { .id = V4L2_CID_SATURATION, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Saturation", .minimum = 0, .maximum = 255, .step = 0x1, .default_value = 112, .flags = 0, }, { .id = V4L2_CID_HUE, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Hue", .minimum = -128, .maximum = 127, .step = 0x1, .default_value = 0, .flags = 0, }};static int qctl_regs[ARRAY_SIZE(tm6000_qctrl)];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, }};static LIST_HEAD(tm6000_corelist);/* ------------------------------------------------------------------ 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 1 char *outp;#endif if (list_empty(&dma_q->active)) { dprintk(dev, V4L2_DEBUG_QUEUE, "No active queue to serve\n"); return; } *buf = list_entry(dma_q->active.next, struct tm6000_buffer, vb.queue);#if 1 if (!buf) return; /* Cleans up buffer - Usefull for testing for frame/URB loss */ outp = videobuf_to_vmalloc(&(*buf)->vb); memset(outp, 0, (*buf)->vb.size);#endif return;}/* * 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++; do_gettimeofday(&buf->vb.ts); list_del(&buf->vb.queue); wake_up(&buf->vb.done);}const char *tm6000_msg_type[] = { "unknown(0)", /* 0 */ "video", /* 1 */ "audio", /* 2 */ "vbi", /* 3 */ "pts", /* 4 */ "err", /* 5 */ "unknown(6)", /* 6 */ "unknown(7)", /* 7 */};/* * Identify the tm5600/6000 buffer header type and properly handles */static int copy_packet(struct urb *urb, u32 header, u8 **ptr, u8 *endp, u8 *out_p, struct tm6000_buffer **buf){ struct tm6000_dmaqueue *dma_q = urb->context; struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); u8 c; unsigned int cmd, cpysize, pktsize, size, field, block, line, pos = 0; int rc = 0; /* FIXME: move to tm6000-isoc */ static int last_line = -2, start_line = -2, last_field = -2; /* FIXME: this is the hardcoded window size */ unsigned int linewidth = (*buf)->vb.width << 1; if (!dev->isoc_ctl.cmd) { c = (header >> 24) & 0xff; /* split the header fields */ size = (((header & 0x7e) << 1) -1) *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; if (cmd == TM6000_URB_MSG_VIDEO) { if ((block+1)*TM6000_URB_MSG_LEN>linewidth) cmd = TM6000_URB_MSG_ERR; /* FIXME: Mounts the image as field0+field1 * It should, instead, check if the user selected * entrelaced or non-entrelaced mode */ pos= ((line<<1)+field)*linewidth + block*TM6000_URB_MSG_LEN; /* Don't allow to write out of the buffer */ if (pos+TM6000_URB_MSG_LEN > (*buf)->vb.size) { dprintk(dev, V4L2_DEBUG_ISOC, "ERR: size=%d, num=%d, line=%d, " "field=%d\n", size, block, line, field); cmd = TM6000_URB_MSG_ERR; } } else { pos=0; } /* Prints debug info */ dprintk(dev, V4L2_DEBUG_ISOC, "size=%d, num=%d, " " line=%d, field=%d\n", size, block, line, field); if ((last_line!=line)&&(last_line+1!=line) && (cmd != TM6000_URB_MSG_ERR) ) { if (cmd != TM6000_URB_MSG_VIDEO) { dprintk(dev, V4L2_DEBUG_ISOC, "cmd=%d, " "size=%d, num=%d, line=%d, field=%d\n", cmd, size, block, line, field); } if (start_line<0) start_line=last_line; /* Prints debug info */ dprintk(dev, V4L2_DEBUG_ISOC, "lines= %d-%d, " "field=%d\n", start_line, last_line, field); if ((start_line<6 && last_line>200) && (last_field != field) ) { dev->isoc_ctl.nfields++; if (dev->isoc_ctl.nfields>=2) { dev->isoc_ctl.nfields=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) return rc; } } start_line=line; last_field=field; } last_line=line; pktsize = TM6000_URB_MSG_LEN; } 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; }#if 0 /* This never happens at this point */ if (!buf) cmd=TM6000_URB_MSG_ERR;#endif cpysize = (endp-(*ptr) > size) ? size : endp - *ptr; if (cpysize) { /* handles each different URB message */ switch(cmd) { case TM6000_URB_MSG_VIDEO: /* Fills video buffer */ memcpy(&out_p[pos], *ptr, cpysize); break; case TM6000_URB_MSG_PTS: break; case TM6000_URB_MSG_AUDIO:/* Need some code to process audio */printk ("%ld: cmd=%s, size=%d\n", jiffies, tm6000_msg_type[cmd],size); break; default: dprintk (dev, V4L2_DEBUG_ISOC, "cmd=%s, size=%d\n", tm6000_msg_type[cmd],size); } } if (cpysize<size) { /* 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.pktsize = pktsize-cpysize; (*ptr)+=cpysize; } else { dev->isoc_ctl.cmd = 0; (*ptr)+=pktsize; } return rc;}static int copy_streams(u8 *data, u8 *out_p, unsigned long len, struct urb *urb, struct tm6000_buffer **buf){ 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; for (ptr=data; ptr<endp;) { if (!dev->isoc_ctl.cmd) { u8 *p=(u8 *)&dev->isoc_ctl.tmp_buf; /* FIXME: This seems very complex * It just recovers up to 3 bytes of the header that * might be at the previous packet */ if (dev->isoc_ctl.tmp_buf_len) { while (dev->isoc_ctl.tmp_buf_len) { if ( *(ptr+3-dev->isoc_ctl.tmp_buf_len) == 0x47) { break; } p++; dev->isoc_ctl.tmp_buf_len--; } if (dev->isoc_ctl.tmp_buf_len) { memcpy (&header,p, dev->isoc_ctl.tmp_buf_len); memcpy (((u8 *)header)+ dev->isoc_ctl.tmp_buf, ptr, 4-dev->isoc_ctl.tmp_buf_len); ptr+=4-dev->isoc_ctl.tmp_buf_len; goto HEADER; } } /* Seek for sync */ for (;ptr<endp-3;ptr++) { if (*(ptr+3)==0x47) break; } if (ptr+3>=endp) { dev->isoc_ctl.tmp_buf_len=endp-ptr; memcpy (&dev->isoc_ctl.tmp_buf,ptr, dev->isoc_ctl.tmp_buf_len); dev->isoc_ctl.cmd=0; return rc; } /* Get message header */ header=*(unsigned long *)ptr; ptr+=4; }HEADER: /* Copy or continue last copy */ rc=copy_packet(urb,header,&ptr,endp,out_p,buf); if (rc<0) { buf=NULL; printk(KERN_ERR "tm6000: buffer underrun at %ld\n", jiffies); return rc; } } return 0;}/* * Identify the tm5600/6000 buffer header type and properly handles */static int copy_multiplexed(u8 *ptr, u8 *out_p, unsigned long len, struct urb *urb, struct tm6000_buffer **buf){ 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; while (len>0) { cpysize=min(len,(*buf)->vb.size-pos);//printk("Copying %d bytes (max=%lu) from %p to %p[%u]\n",cpysize,(*buf)->vb.size,ptr,out_p,pos); memcpy(&out_p[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; } } dev->isoc_ctl.pos=pos; return rc;}static void inline print_err_status (struct tm6000_core *dev, int packet, int status){ char *errmsg = "Unknown"; switch(status) { case -ENOENT: errmsg = "unlinked synchronuously"; break; case -ECONNRESET: errmsg = "unlinked asynchronuously"; break; case -ENOSR: errmsg = "Buffer error (overrun)"; break; case -EPIPE: errmsg = "Stalled (device not responding)"; break; case -EOVERFLOW: errmsg = "Babble (bad cable?)"; break; case -EPROTO: errmsg = "Bit-stuff error (bad cable?)"; break; case -EILSEQ: errmsg = "CRC/Timeout (could be anything)"; break; case -ETIME: errmsg = "Device does not respond"; break; } if (packet<0) { 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_buffer **buf){ struct tm6000_dmaqueue *dma_q = urb->context; struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq); void *outp=videobuf_to_vmalloc (&((*buf)->vb)); int i, len=0, rc=1; int size=(*buf)->vb.size; char *p; unsigned long copied; copied=0; if (urb->status<0) { print_err_status (dev,-1,urb->status); return 0; } for (i = 0; i < urb->number_of_packets; i++) { int status = urb->iso_frame_desc[i].status; if (status<0) { print_err_status (dev,i,status); continue; } len=urb->iso_frame_desc[i].actual_length;// if (len>=TM6000_URB_MSG_LEN) { p=urb->transfer_buffer + urb->iso_frame_desc[i].offset; if (!urb->iso_frame_desc[i].status) { if (((*buf)->fmt->fourcc)==V4L2_PIX_FMT_TM6000) { rc=copy_multiplexed(p,outp,len,urb,buf); if (rc<=0) return rc; } else { copy_streams(p,outp,len,urb,buf); } } copied += len; if (copied>=size) break;// } } return rc;}/* ------------------------------------------------------------------ URB control ------------------------------------------------------------------*//* * IRQ callback, called by URB callback */#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)static void tm6000_irq_callback(struct urb *urb, struct pt_regs *regs)#elsestatic void tm6000_irq_callback(struct urb *urb)#endif{ struct tm6000_buffer *buf = NULL; struct tm6000_dmaqueue *dma_q = urb->context; struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); unsigned long flags;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -