/* * tw68 functions to handle video data * * Much of this code is derived from the cx88 and sa7134 drivers, which * were in turn derived from the bt87x driver. The original work was by * Gerd Knorr; more recently the code was enhanced by Mauro Carvalho Chehab, * Hans Verkuil, Andy Walls and many others. Their work is gratefully * acknowledged. Full credit goes to them - any problems within this code * are mine. * * Copyright (C) 2009 William M. Brack * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "tw68.h" #include "tw68-reg.h" unsigned int video_debug; static unsigned int gbuffers = 8; static unsigned int noninterlaced; /* 0 */ static unsigned int gbufsz = 768*576*4; static unsigned int gbufsz_max = 768*576*4; static char secam[] = "--"; module_param(video_debug, int, 0644); MODULE_PARM_DESC(video_debug, "enable debug messages [video]"); module_param(gbuffers, int, 0444); MODULE_PARM_DESC(gbuffers, "number of capture buffers, range 2-32"); module_param(noninterlaced, int, 0644); MODULE_PARM_DESC(noninterlaced, "capture non interlaced video"); module_param_string(secam, secam, sizeof(secam), 0644); MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc"); #define dprintk(level, fmt, arg...) if (video_debug & (level)) \ printk(KERN_DEBUG "%s/0: " fmt, dev->name , ## arg) /* ------------------------------------------------------------------ */ /* data structs for video */ /* * FIXME - * Note that the saa7134 has formats, e.g. YUV420, which are classified * as "planar". These affect overlay mode, and are flagged with a field * ".planar" in the format. Do we need to implement this in this driver? */ static struct tw68_format formats[] = { { .name = "15 bpp RGB, le", .fourcc = V4L2_PIX_FMT_RGB555, .depth = 16, .twformat = ColorFormatRGB15, }, { .name = "15 bpp RGB, be", .fourcc = V4L2_PIX_FMT_RGB555X, .depth = 16, .twformat = ColorFormatRGB15 | ColorFormatBSWAP, }, { .name = "16 bpp RGB, le", .fourcc = V4L2_PIX_FMT_RGB565, .depth = 16, .twformat = ColorFormatRGB16, }, { .name = "16 bpp RGB, be", .fourcc = V4L2_PIX_FMT_RGB565X, .depth = 16, .twformat = ColorFormatRGB16 | ColorFormatBSWAP, }, { .name = "24 bpp RGB, le", .fourcc = V4L2_PIX_FMT_BGR24, .depth = 24, .twformat = ColorFormatRGB24, }, { .name = "24 bpp RGB, be", .fourcc = V4L2_PIX_FMT_RGB24, .depth = 24, .twformat = ColorFormatRGB24 | ColorFormatBSWAP, }, { .name = "32 bpp RGB, le", .fourcc = V4L2_PIX_FMT_BGR32, .depth = 32, .twformat = ColorFormatRGB32, }, { .name = "32 bpp RGB, be", .fourcc = V4L2_PIX_FMT_RGB32, .depth = 32, .twformat = ColorFormatRGB32 | ColorFormatBSWAP | ColorFormatWSWAP, }, { .name = "4:2:2 packed, YUYV", .fourcc = V4L2_PIX_FMT_YUYV, .depth = 16, .twformat = ColorFormatYUY2, }, { .name = "4:2:2 packed, UYVY", .fourcc = V4L2_PIX_FMT_UYVY, .depth = 16, .twformat = ColorFormatYUY2 | ColorFormatBSWAP, } }; #define FORMATS ARRAY_SIZE(formats) #define NORM_625_50 \ .h_delay = 3, \ .h_delay0 = 133, \ .h_start = 0, \ .h_stop = 719, \ .v_delay = 24, \ .vbi_v_start_0 = 7, \ .vbi_v_stop_0 = 22, \ .video_v_start = 24, \ .video_v_stop = 311, \ .vbi_v_start_1 = 319 #define NORM_525_60 \ .h_delay = 8, \ .h_delay0 = 138, \ .h_start = 0, \ .h_stop = 719, \ .v_delay = 22, \ .vbi_v_start_0 = 10, \ .vbi_v_stop_0 = 21, \ .video_v_start = 22, \ .video_v_stop = 262, \ .vbi_v_start_1 = 273 /* * The following table is searched by tw68_s_std, first for a specific * match, then for an entry which contains the desired id. The table * entries should therefore be ordered in ascending order of specificity. */ static struct tw68_tvnorm tvnorms[] = { { .name = "PAL-BG", .id = V4L2_STD_PAL_BG, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPALBDGHI, }, { .name = "PAL-I", .id = V4L2_STD_PAL_I, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPALBDGHI, }, { .name = "PAL-DK", .id = V4L2_STD_PAL_DK, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPALBDGHI, }, { .name = "PAL", /* autodetect */ .id = V4L2_STD_PAL, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPALBDGHI, }, { .name = "NTSC", .id = V4L2_STD_NTSC, NORM_525_60, .sync_control = 0x59, .luma_control = 0x40, .chroma_ctrl1 = 0x89, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x0e, .vgate_misc = 0x18, .format = VideoFormatNTSC, }, { .name = "SECAM-DK", .id = V4L2_STD_SECAM_DK, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, .format = VideoFormatSECAM, }, { .name = "SECAM-L", .id = V4L2_STD_SECAM_L, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, .format = VideoFormatSECAM, }, { .name = "SECAM-LC", .id = V4L2_STD_SECAM_LC, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, .format = VideoFormatSECAM, }, { .name = "SECAM", .id = V4L2_STD_SECAM, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, .format = VideoFormatSECAM, }, { .name = "PAL-M", .id = V4L2_STD_PAL_M, NORM_525_60, .sync_control = 0x59, .luma_control = 0x40, .chroma_ctrl1 = 0xb9, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x0e, .vgate_misc = 0x18, .format = VideoFormatPALM, }, { .name = "PAL-Nc", .id = V4L2_STD_PAL_Nc, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0xa1, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPALNC, }, { .name = "PAL-60", .id = V4L2_STD_PAL_60, .h_delay = 186, .h_start = 0, .h_stop = 719, .v_delay = 26, .video_v_start = 23, .video_v_stop = 262, .vbi_v_start_0 = 10, .vbi_v_stop_0 = 21, .vbi_v_start_1 = 273, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPAL60, }, { /* * FIXME: The following are meant to be "catch-all", and need * to be further thought out! */ .name = "STD-525-60", .id = V4L2_STD_525_60, NORM_525_60, .sync_control = 0x59, .luma_control = 0x40, .chroma_ctrl1 = 0x89, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x0e, .vgate_misc = 0x18, .format = VideoFormatNTSC, }, { .name = "STD-625-50", .id = V4L2_STD_625_50, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, .format = VideoFormatPALBDGHI, } }; #define TVNORMS ARRAY_SIZE(tvnorms) static const struct v4l2_queryctrl no_ctrl = { .name = "42", .flags = V4L2_CTRL_FLAG_DISABLED, }; static const struct v4l2_queryctrl video_ctrls[] = { /* --- video --- */ { .id = V4L2_CID_BRIGHTNESS, .name = "Brightness", .minimum = -128, .maximum = 127, .step = 1, .default_value = 20, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_CONTRAST, .name = "Contrast", .minimum = 0, .maximum = 255, .step = 1, .default_value = 100, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_SATURATION, .name = "Saturation", .minimum = 0, .maximum = 255, .step = 1, .default_value = 128, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_HUE, .name = "Hue", .minimum = -128, .maximum = 127, .step = 1, .default_value = 0, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_COLOR_KILLER, .name = "Color Killer", .minimum = 0, .maximum = 1, .default_value = 1, .type = V4L2_CTRL_TYPE_BOOLEAN, }, { .id = V4L2_CID_CHROMA_AGC, .name = "Chroma AGC", .minimum = 0, .maximum = 1, .default_value = 1, .type = V4L2_CTRL_TYPE_BOOLEAN, }, /* --- audio --- */ { .id = V4L2_CID_AUDIO_MUTE, .name = "Mute", .minimum = 0, .maximum = 1, .type = V4L2_CTRL_TYPE_BOOLEAN, }, { .id = V4L2_CID_AUDIO_VOLUME, .name = "Volume", .minimum = -15, .maximum = 15, .step = 1, .default_value = 0, .type = V4L2_CTRL_TYPE_INTEGER, } }; static const unsigned int CTRLS = ARRAY_SIZE(video_ctrls); /* * Routine to lookup a control by its ID, and return a pointer * to the entry in the video_ctrls array for that control. */ static const struct v4l2_queryctrl *ctrl_by_id(unsigned int id) { unsigned int i; for (i = 0; i < CTRLS; i++) if (video_ctrls[i].id == id) return video_ctrls+i; return NULL; } static struct tw68_format *format_by_fourcc(unsigned int fourcc) { unsigned int i; for (i = 0; i < FORMATS; i++) if (formats[i].fourcc == fourcc) return formats+i; return NULL; } /* ----------------------------------------------------------------------- */ /* resource management */ static int res_get(struct tw68_fh *fh, unsigned int bit) { struct tw68_dev *dev = fh->dev; if (fh->resources & bit) /* have it already allocated */ return 1; /* is it free? */ mutex_lock(&dev->lock); if (dev->resources & bit) { /* no, someone else uses it */ mutex_unlock(&fh->dev->lock); return 0; } /* it's free, grab it */ fh->resources |= bit; dev->resources |= bit; dprintk(DBG_FLOW, "%s: %d\n", __func__, bit); mutex_unlock(&dev->lock); return 1; } static int res_check(struct tw68_fh *fh, unsigned int bit) { return fh->resources & bit; } static int res_locked(struct tw68_dev *dev, unsigned int bit) { return dev->resources & bit; } static void res_free(struct tw68_fh *fh, unsigned int bits) { struct tw68_dev *dev = fh->dev; BUG_ON((fh->resources & bits) != bits); mutex_lock(&fh->dev->lock); fh->resources &= ~bits; fh->dev->resources &= ~bits; dprintk(DBG_FLOW, "%s: %d\n", __func__, bits); mutex_unlock(&fh->dev->lock); } /* ------------------------------------------------------------------ */ /* * Note that the cropping rectangles are described in terms of a single * frame, i.e. line positions are only 1/2 the interlaced equivalent */ static void set_tvnorm(struct tw68_dev *dev, struct tw68_tvnorm *norm) { dprintk(DBG_FLOW, "%s: %s\n", __func__, norm->name); dev->tvnorm = norm; /* setup cropping */ dev->crop_bounds.left = norm->h_start; dev->crop_defrect.left = norm->h_start; dev->crop_bounds.width = norm->h_stop - norm->h_start + 1; dev->crop_defrect.width = norm->h_stop - norm->h_start + 1; dev->crop_bounds.top = norm->video_v_start; dev->crop_defrect.top = norm->video_v_start; dev->crop_bounds.height = (((norm->id & V4L2_STD_525_60) ? 524 : 624)) / 2 - dev->crop_bounds.top; dev->crop_defrect.height = (norm->video_v_stop - norm->video_v_start + 1); dev->crop_current = dev->crop_defrect; if (norm != dev->tvnorm) { dev->tvnorm = norm; tw68_set_tvnorm_hw(dev); } } static void video_mux(struct tw68_dev *dev, int input) { dprintk(DBG_FLOW, "%s: input = %d [%s]\n", __func__, input, card_in(dev, input).name); /* * dev->input shows current application request, * dev->hw_input shows current hardware setting */ dev->input = &card_in(dev, input); tw68_tvaudio_setinput(dev, &card_in(dev, input)); } /* * tw68_set_scale * * Scaling and Cropping for video decoding * * We are working with 3 values for horizontal and vertical - scale, * delay and active. * * HACTIVE represent the actual number of pixels in the "usable" image, * before scaling. HDELAY represents the number of pixels skipped * between the start of the horizontal sync and the start of the image. * HSCALE is calculated using the formula * HSCALE = (HACTIVE / (#pixels desired)) * 256 * * The vertical registers are similar, except based upon the total number * of lines in the image, and the first line of the image (i.e. ignoring * vertical sync and VBI). * * Note that the number of bytes reaching the FIFO (and hence needing * to be processed by the DMAP program) is completely dependent upon * these values, especially HSCALE. * * Parameters: * @dev pointer to the device structure, needed for * getting current norm (as well as debug print) * @width actual image width (from user buffer) * @height actual image height * @field indicates Top, Bottom or Interlaced */ static int tw68_set_scale(struct tw68_dev *dev, unsigned int width, unsigned int height, enum v4l2_field field) { /* set individually for debugging clarity */ int hactive, hdelay, hscale; int vactive, vdelay, vscale; int comb; if (V4L2_FIELD_HAS_BOTH(field)) /* if field is interlaced */ height /= 2; /* we must set for 1-frame */ dprintk(DBG_FLOW, "%s: width=%d, height=%d, both=%d\n Crop rect: " "top=%d, left=%d, width=%d height=%d\n" " tvnorm h_delay=%d, h_start=%d, h_stop=%d, " "v_delay=%d, v_start=%d, v_stop=%d\n" , __func__, width, height, V4L2_FIELD_HAS_BOTH(field), dev->crop_bounds.top, dev->crop_bounds.left, dev->crop_bounds.width, dev->crop_bounds.height, dev->tvnorm->h_delay, dev->tvnorm->h_start, dev->tvnorm->h_stop, dev->tvnorm->v_delay, dev->tvnorm->video_v_start, dev->tvnorm->video_v_stop); switch (dev->vdecoder) { case TW6800: hdelay = dev->tvnorm->h_delay0; break; default: hdelay = dev->tvnorm->h_delay; break; } hdelay += dev->crop_bounds.left; hactive = dev->crop_bounds.width; hscale = (hactive * 256) / (width); vdelay = dev->tvnorm->v_delay + dev->crop_bounds.top - dev->crop_defrect.top; vactive = dev->crop_bounds.height; vscale = (vactive * 256) / height; dprintk(DBG_FLOW, "%s: %dx%d [%s%s,%s]\n", __func__, width, height, V4L2_FIELD_HAS_TOP(field) ? "T" : "", V4L2_FIELD_HAS_BOTTOM(field) ? "B" : "", v4l2_norm_to_name(dev->tvnorm->id)); dprintk(DBG_FLOW, "%s: hactive=%d, hdelay=%d, hscale=%d; " "vactive=%d, vdelay=%d, vscale=%d\n", __func__, hactive, hdelay, hscale, vactive, vdelay, vscale); comb = ((vdelay & 0x300) >> 2) | ((vactive & 0x300) >> 4) | ((hdelay & 0x300) >> 6) | ((hactive & 0x300) >> 8); dprintk(DBG_FLOW, "%s: setting CROP_HI=%02x, VDELAY_LO=%02x, " "VACTIVE_LO=%02x, HDELAY_LO=%02x, HACTIVE_LO=%02x\n", __func__, comb, vdelay, vactive, hdelay, hactive); tw_writeb(TW68_CROP_HI, comb); tw_writeb(TW68_VDELAY_LO, vdelay & 0xff); tw_writeb(TW68_VACTIVE_LO, vactive & 0xff); tw_writeb(TW68_HDELAY_LO, hdelay & 0xff); tw_writeb(TW68_HACTIVE_LO, hactive & 0xff); comb = ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8); dprintk(DBG_FLOW, "%s: setting SCALE_HI=%02x, VSCALE_LO=%02x, " "HSCALE_LO=%02x\n", __func__, comb, vscale, hscale); tw_writeb(TW68_SCALE_HI, comb); tw_writeb(TW68_VSCALE_LO, vscale); tw_writeb(TW68_HSCALE_LO, hscale); return 0; } /* ------------------------------------------------------------------ */ static int tw68_video_start_dma(struct tw68_dev *dev, struct tw68_dmaqueue *q, struct tw68_buf *buf) { dprintk(DBG_FLOW, "%s: Starting risc program\n", __func__); /* Assure correct input */ if (dev->hw_input != dev->input) { dev->hw_input = dev->input; tw_andorb(TW68_INFORM, 0x03 << 2, dev->input->vmux << 2); } /* Set cropping and scaling */ tw68_set_scale(dev, buf->vb.width, buf->vb.height, buf->vb.field); /* * Set start address for RISC program. Note that if the DMAP * processor is currently running, it must be stopped before * a new address can be set. */ tw_clearl(TW68_DMAC, TW68_DMAP_EN); tw_writel(TW68_DMAP_SA, cpu_to_le32(buf->risc.dma)); /* Clear any pending interrupts */ tw_writel(TW68_INTSTAT, dev->board_virqmask); /* Enable the risc engine and the fifo */ tw_andorl(TW68_DMAC, 0xff, buf->fmt->twformat | ColorFormatGamma | TW68_DMAP_EN | TW68_FIFO_EN); dev->pci_irqmask |= dev->board_virqmask; tw_setl(TW68_INTMASK, dev->pci_irqmask); return 0; } /* ------------------------------------------------------------------ */ /* videobuf queue operations */ /* * check_buf_fmt * * callback from tw68-core buffer_queue to determine whether the * current buffer and the previous one are "compatible" (i.e. the * risc programs can be chained without requiring a format change) */ static int tw68_check_video_fmt(struct tw68_buf *prev, struct tw68_buf *buf) { return (prev->vb.width == buf->vb.width && prev->vb.height == buf->vb.height && prev->fmt == buf->fmt); } /* * buffer_setup * * Calculate required size of buffer and maximum number allowed */ static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) { struct tw68_fh *fh = q->priv_data; *size = fh->fmt->depth * fh->width * fh->height >> 3; if (0 == *count) *count = gbuffers; *count = tw68_buffer_count(*size, *count); return 0; } static int buffer_activate(struct tw68_dev *dev, struct tw68_buf *buf, struct tw68_buf *next) { dprintk(DBG_BUFF, "%s: dev=%p, buf=%p, next=%p\n", __func__, dev, buf, next); if (dev->hw_input != dev->input) { dev->hw_input = dev->input; tw_andorb(TW68_INFORM, 0x03 << 2, dev->hw_input->vmux << 2); } buf->vb.state = VIDEOBUF_ACTIVE; /* TODO - need to assure scaling/cropping are set correctly */ mod_timer(&dev->video_q.timeout, jiffies+BUFFER_TIMEOUT); return 0; } /* * buffer_prepare * * Set the ancilliary information into the buffer structure. This * includes generating the necessary risc program if it hasn't already * been done for the current buffer format. * The structure fh contains the details of the format requested by the * user - type, width, height and #fields. This is compared with the * last format set for the current buffer. If they differ, the risc * code (which controls the filling of the buffer) is (re-)generated. */ static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, enum v4l2_field field) { struct tw68_fh *fh = q->priv_data; struct tw68_dev *dev = fh->dev; struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb); struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb); int rc, init_buffer = 0; unsigned int maxw, maxh; BUG_ON(NULL == fh->fmt); maxw = dev->tvnorm->h_stop - dev->tvnorm->h_start + 1; maxh = 2*(dev->tvnorm->video_v_stop - dev->tvnorm->video_v_start + 1); if (fh->width < 48 || fh->width > maxw || fh->height > maxh || fh->height < 16) { dprintk(DBG_UNEXPECTED, "%s: invalid dimensions - " "fh->width=%d, fh->height=%d, maxw=%d, maxh=%d\n", __func__, fh->width, fh->height, maxw, maxh); return -EINVAL; } buf->vb.size = (fh->width * fh->height * (fh->fmt->depth)) >> 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) { dprintk(DBG_BUFF, "%s: buf - fmt=%p, width=%3d, height=%3d, " "field=%d\n%s: fh - fmt=%p, width=%3d, height=%3d, " "field=%d\n", __func__, buf->fmt, buf->vb.width, buf->vb.height, buf->vb.field, __func__, fh->fmt, fh->width, fh->height, field); buf->fmt = fh->fmt; buf->vb.width = fh->width; buf->vb.height = fh->height; buf->vb.field = field; init_buffer = 1; /* force risc code re-generation */ } buf->input = dev->input; if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { rc = videobuf_iolock(q, &buf->vb, NULL); if (0 != rc) goto fail; init_buffer = 1; /* force risc code re-generation */ } dprintk(DBG_BUFF, "%s: q=%p, vb=%p, init_buffer=%d\n", __func__, q, vb, init_buffer); if (init_buffer) { buf->bpl = buf->vb.width * (buf->fmt->depth) >> 3; dprintk(DBG_TESTING, "%s: Generating new risc code " "[%dx%dx%d](%d)\n", __func__, buf->vb.width, buf->vb.height, buf->fmt->depth, buf->bpl); switch (buf->vb.field) { case V4L2_FIELD_TOP: tw68_risc_buffer(dev->pci, &buf->risc, dma->sglist, 0, UNSET, buf->bpl, 0, buf->vb.height); break; case V4L2_FIELD_BOTTOM: tw68_risc_buffer(dev->pci, &buf->risc, dma->sglist, UNSET, 0, buf->bpl, 0, buf->vb.height); break; case V4L2_FIELD_INTERLACED: tw68_risc_buffer(dev->pci, &buf->risc, dma->sglist, 0, buf->bpl, buf->bpl, buf->bpl, buf->vb.height >> 1); break; case V4L2_FIELD_SEQ_TB: tw68_risc_buffer(dev->pci, &buf->risc, dma->sglist, 0, buf->bpl * (buf->vb.height >> 1), buf->bpl, 0, buf->vb.height >> 1); break; case V4L2_FIELD_SEQ_BT: tw68_risc_buffer(dev->pci, &buf->risc, dma->sglist, buf->bpl * (buf->vb.height >> 1), 0, buf->bpl, 0, buf->vb.height >> 1); break; default: BUG(); } } dprintk(DBG_BUFF, "%s: [%p/%d] - %dx%d %dbpp \"%s\" - dma=0x%08lx\n", __func__, buf, buf->vb.i, fh->width, fh->height, fh->fmt->depth, fh->fmt->name, (unsigned long)buf->risc.dma); buf->vb.state = VIDEOBUF_PREPARED; buf->activate = buffer_activate; return 0; fail: tw68_dma_free(q, buf); return rc; } /* * buffer_queue * * Callback whenever a buffer has been requested (by read() or QBUF) */ static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct tw68_fh *fh = q->priv_data; struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb); tw68_buffer_queue(fh->dev, &fh->dev->video_q, buf); } /* * buffer_release * * Free a buffer previously allocated. */ static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct tw68_buf *buf = container_of(vb, struct tw68_buf, vb); tw68_dma_free(q, buf); } static struct videobuf_queue_ops video_qops = { .buf_setup = buffer_setup, .buf_prepare = buffer_prepare, .buf_queue = buffer_queue, .buf_release = buffer_release, }; /* ------------------------------------------------------------------ */ static int tw68_g_ctrl_internal(struct tw68_dev *dev, struct tw68_fh *fh, struct v4l2_control *c) { const struct v4l2_queryctrl *ctrl; dprintk(DBG_FLOW, "%s\n", __func__); ctrl = ctrl_by_id(c->id); if (NULL == ctrl) return -EINVAL; switch (c->id) { case V4L2_CID_BRIGHTNESS: c->value = (char)tw_readb(TW68_BRIGHT); break; case V4L2_CID_HUE: c->value = (char)tw_readb(TW68_HUE); break; case V4L2_CID_CONTRAST: c->value = tw_readb(TW68_CONTRAST); break; case V4L2_CID_SATURATION: c->value = tw_readb(TW68_SAT_U); break; case V4L2_CID_COLOR_KILLER: c->value = 0 != (tw_readb(TW68_MISC2) & 0xe0); break; case V4L2_CID_CHROMA_AGC: c->value = 0 != (tw_readb(TW68_LOOP) & 0x30); break; case V4L2_CID_AUDIO_MUTE: /*hack to suppresss tvtime complaint */ c->value = 0; break; #if 0 case V4L2_CID_AUDIO_VOLUME: c->value = dev->ctl_volume; break; #endif default: return -EINVAL; } return 0; } static int tw68_g_ctrl(struct file *file, void *priv, struct v4l2_control *c) { struct tw68_fh *fh = priv; return tw68_g_ctrl_internal(fh->dev, fh, c); } static int tw68_s_ctrl_value(struct tw68_dev *dev, __u32 id, int val) { int err = 0; dprintk(DBG_FLOW, "%s\n", __func__); switch (id) { case V4L2_CID_BRIGHTNESS: tw_writeb(TW68_BRIGHT, val); break; case V4L2_CID_HUE: tw_writeb(TW68_HUE, val); break; case V4L2_CID_CONTRAST: tw_writeb(TW68_CONTRAST, val); break; case V4L2_CID_SATURATION: tw_writeb(TW68_SAT_U, val); tw_writeb(TW68_SAT_V, val); break; case V4L2_CID_COLOR_KILLER: if (val) tw_andorb(TW68_MISC2, 0xe0, 0xe0); else tw_andorb(TW68_MISC2, 0xe0, 0x00); break; case V4L2_CID_CHROMA_AGC: if (val) tw_andorb(TW68_LOOP, 0x30, 0x20); else tw_andorb(TW68_LOOP, 0x30, 0x00); break; case V4L2_CID_AUDIO_MUTE: /* hack to suppress tvtime complaint */ break; #if 0 case V4L2_CID_AUDIO_VOLUME: dev->ctl_volume = val; tw68_tvaudio_setvolume(dev, dev->ctl_volume); break; case V4L2_CID_HFLIP: dev->ctl_mirror = val; break; case V4L2_CID_PRIVATE_AUTOMUTE: { struct v4l2_priv_tun_config tda9887_cfg; tda9887_cfg.tuner = TUNER_TDA9887; tda9887_cfg.priv = &dev->tda9887_conf; dev->ctl_automute = val; if (dev->tda9887_conf) { if (dev->ctl_automute) dev->tda9887_conf |= TDA9887_AUTOMUTE; else dev->tda9887_conf &= ~TDA9887_AUTOMUTE; tw_call_all(dev, tuner, s_config, &tda9887_cfg); } break; } #endif default: err = -EINVAL; } return err; } static int tw68_s_ctrl_internal(struct tw68_dev *dev, struct tw68_fh *fh, struct v4l2_control *c) { const struct v4l2_queryctrl *ctrl; int err; dprintk(DBG_FLOW, "%s\n", __func__); if (fh) { #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) err = v4l2_prio_check(&dev->prio, &fh->prio); #else err = v4l2_prio_check(&dev->prio, fh->prio); #endif if (0 != err) return err; } mutex_lock(&dev->lock); ctrl = ctrl_by_id(c->id); if (NULL == ctrl) { err = -EINVAL; goto error; } dprintk(DBG_BUFF, "%s: name=%s val=%d\n", __func__, ctrl->name, c->value); switch (ctrl->type) { case V4L2_CTRL_TYPE_BOOLEAN: case V4L2_CTRL_TYPE_MENU: case V4L2_CTRL_TYPE_INTEGER: if (c->value < ctrl->minimum) c->value = ctrl->minimum; if (c->value > ctrl->maximum) c->value = ctrl->maximum; break; default: /* nothing */; }; err = tw68_s_ctrl_value(dev, c->id, c->value); error: mutex_unlock(&dev->lock); return err; } static int tw68_s_ctrl(struct file *file, void *f, struct v4l2_control *c) { struct tw68_fh *fh = f; return tw68_s_ctrl_internal(fh->dev, fh, c); } /* ------------------------------------------------------------------ */ /* * Returns a pointer to the currently used queue (e.g. video, vbi, etc.) */ static struct videobuf_queue *tw68_queue(struct tw68_fh *fh) { struct videobuf_queue *q = NULL; switch (fh->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: q = &fh->cap; break; case V4L2_BUF_TYPE_VBI_CAPTURE: q = &fh->vbi; break; default: BUG(); } return q; } static int tw68_resource(struct tw68_fh *fh) { if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) return RESOURCE_VIDEO; if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) return RESOURCE_VBI; BUG(); return 0; } static int video_open(struct file *file) { int minor = video_devdata(file)->minor; struct tw68_dev *dev; struct tw68_fh *fh; enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int radio = 0; mutex_lock(&tw68_devlist_lock); list_for_each_entry(dev, &tw68_devlist, devlist) { if (dev->video_dev && (dev->video_dev->minor == minor)) goto found; if (dev->radio_dev && (dev->radio_dev->minor == minor)) { radio = 1; goto found; } if (dev->vbi_dev && (dev->vbi_dev->minor == minor)) { type = V4L2_BUF_TYPE_VBI_CAPTURE; goto found; } } mutex_unlock(&tw68_devlist_lock); return -ENODEV; found: mutex_unlock(&tw68_devlist_lock); dprintk(DBG_FLOW, "%s: minor=%d radio=%d type=%s\n", __func__, minor, radio, v4l2_type_names[type]); /* allocate + initialize per filehandle data */ fh = kzalloc(sizeof(*fh), GFP_KERNEL); if (NULL == fh) return -ENOMEM; file->private_data = fh; fh->dev = dev; fh->radio = radio; fh->type = type; fh->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); fh->width = 720; fh->height = 576; v4l2_prio_open(&dev->prio, &fh->prio); videobuf_queue_sg_init(&fh->cap, &video_qops, &dev->pci->dev, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED, sizeof(struct tw68_buf), #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37) fh #else fh, &dev->lock #endif ); videobuf_queue_sg_init(&fh->vbi, &tw68_vbi_qops, &dev->pci->dev, &dev->slock, V4L2_BUF_TYPE_VBI_CAPTURE, V4L2_FIELD_SEQ_TB, sizeof(struct tw68_buf), #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,37) fh #else fh, &dev->lock #endif ); if (fh->radio) { /* switch to radio mode */ tw68_tvaudio_setinput(dev, &card(dev).radio); tw_call_all(dev, tuner, s_radio); } else { /* switch to video/vbi mode */ tw68_tvaudio_setinput(dev, dev->input); } return 0; } static ssize_t video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { struct tw68_fh *fh = file->private_data; switch (fh->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (res_locked(fh->dev, RESOURCE_VIDEO)) return -EBUSY; return videobuf_read_one(tw68_queue(fh), data, count, ppos, file->f_flags & O_NONBLOCK); case V4L2_BUF_TYPE_VBI_CAPTURE: if (!res_get(fh, RESOURCE_VBI)) return -EBUSY; return videobuf_read_stream(tw68_queue(fh), data, count, ppos, 1, file->f_flags & O_NONBLOCK); break; default: BUG(); return 0; } } static unsigned int video_poll(struct file *file, struct poll_table_struct *wait) { struct tw68_fh *fh = file->private_data; struct videobuf_buffer *buf = NULL; if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) return videobuf_poll_stream(file, &fh->vbi, wait); if (res_check(fh, RESOURCE_VIDEO)) { if (!list_empty(&fh->cap.stream)) buf = list_entry(fh->cap.stream.next, struct videobuf_buffer, stream); } else { mutex_lock(&fh->cap.vb_lock); if (UNSET == fh->cap.read_off) { /* need to capture a new frame */ if (res_locked(fh->dev, RESOURCE_VIDEO)) goto err; if (0 != fh->cap.ops->buf_prepare(&fh->cap, fh->cap.read_buf, fh->cap.field)) goto err; fh->cap.ops->buf_queue(&fh->cap, fh->cap.read_buf); fh->cap.read_off = 0; } mutex_unlock(&fh->cap.vb_lock); buf = fh->cap.read_buf; } if (!buf) return POLLERR; poll_wait(file, &buf->done, wait); if (buf->state == VIDEOBUF_DONE || buf->state == VIDEOBUF_ERROR) return POLLIN | POLLRDNORM; return 0; err: mutex_unlock(&fh->cap.vb_lock); return POLLERR; } static int video_release(struct file *file) { struct tw68_fh *fh = file->private_data; struct tw68_dev *dev = fh->dev; /* stop video capture */ if (res_check(fh, RESOURCE_VIDEO)) { videobuf_streamoff(&fh->cap); res_free(fh , RESOURCE_VIDEO); } if (fh->cap.read_buf) { buffer_release(&fh->cap, fh->cap.read_buf); kfree(fh->cap.read_buf); } /* stop vbi capture */ if (res_check(fh, RESOURCE_VBI)) { videobuf_stop(&fh->vbi); res_free(fh, RESOURCE_VBI); } #if 0 tw_call_all(dev, core, s_standby, 0); #endif /* free stuff */ videobuf_mmap_free(&fh->cap); videobuf_mmap_free(&fh->vbi); #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) v4l2_prio_close(&dev->prio, &fh->prio); #else v4l2_prio_close(&dev->prio, fh->prio); #endif file->private_data = NULL; kfree(fh); return 0; } static int video_mmap(struct file *file, struct vm_area_struct * vma) { struct tw68_fh *fh = file->private_data; return videobuf_mmap_mapper(tw68_queue(fh), vma); } /* ------------------------------------------------------------------ */ #if 0 static int tw68_try_get_set_fmt_vbi_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; struct tw68_tvnorm *norm = dev->tvnorm; f->fmt.vbi.sampling_rate = 6750000 * 4; f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; f->fmt.vbi.offset = 64 * 4; f->fmt.vbi.start[0] = norm->vbi_v_start_0; f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 + 1; f->fmt.vbi.start[1] = norm->vbi_v_start_1; f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ #if 0 if (V4L2_STD_PAL == norm->id) { /* FIXME */ f->fmt.vbi.start[0] += 3; f->fmt.vbi.start[1] += 3*2; } #endif return 0; } #endif /* * Note that this routine returns what is stored in the fh structure, and * does not interrogate any of the device registers. */ static int tw68_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); f->fmt.pix.width = fh->width; f->fmt.pix.height = fh->height; f->fmt.pix.field = fh->cap.field; f->fmt.pix.pixelformat = fh->fmt->fourcc; f->fmt.pix.bytesperline = (f->fmt.pix.width * (fh->fmt->depth)) >> 3; f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; return 0; } static int tw68_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; struct tw68_format *fmt; enum v4l2_field field; unsigned int maxw, maxh; dprintk(DBG_FLOW, "%s\n", __func__); fmt = format_by_fourcc(f->fmt.pix.pixelformat); if (NULL == fmt) return -EINVAL; field = f->fmt.pix.field; maxw = min(dev->crop_current.width*4, dev->crop_bounds.width); maxh = min(dev->crop_current.height*4, dev->crop_bounds.height); if (V4L2_FIELD_ANY == field) { field = (f->fmt.pix.height > maxh/2) ? V4L2_FIELD_INTERLACED : V4L2_FIELD_BOTTOM; } switch (field) { case V4L2_FIELD_TOP: case V4L2_FIELD_BOTTOM: break; case V4L2_FIELD_INTERLACED: maxh = maxh * 2; break; default: return -EINVAL; } f->fmt.pix.field = field; if (f->fmt.pix.width < 48) f->fmt.pix.width = 48; if (f->fmt.pix.height < 32) f->fmt.pix.height = 32; if (f->fmt.pix.width > maxw) f->fmt.pix.width = maxw; if (f->fmt.pix.height > maxh) f->fmt.pix.height = maxh; f->fmt.pix.width &= ~0x03; f->fmt.pix.bytesperline = (f->fmt.pix.width * (fmt->depth)) >> 3; f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; return 0; } /* * Note that tw68_s_fmt_vid_cap sets the information into the fh structure, * and it will be used for all future new buffers. However, there could be * some number of buffers on the "active" chain which will be filled before * the change takes place. */ static int tw68_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int err; dprintk(DBG_FLOW, "%s\n", __func__); err = tw68_try_fmt_vid_cap(file, priv, f); if (0 != err) return err; fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); fh->width = f->fmt.pix.width; fh->height = f->fmt.pix.height; fh->cap.field = f->fmt.pix.field; /* * The following lines are to make v4l2-test program happy. * The docs should be checked to assure they make sense. */ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; f->fmt.pix.priv = 0; return 0; } static int tw68_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *c) { const struct v4l2_queryctrl *ctrl; struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); if ((c->id < V4L2_CID_BASE || c->id >= V4L2_CID_LASTP1) #if 0 && (c->id < V4L2_CID_PRIVATE_BASE || c->id >= V4L2_CID_PRIVATE_LASTP1) #endif ) return -EINVAL; ctrl = ctrl_by_id(c->id); if (NULL == ctrl) return -EINVAL; *c = *ctrl; return 0; } static int tw68_enum_input(struct file *file, void *priv, struct v4l2_input *i) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; unsigned int n; n = i->index; dprintk(DBG_FLOW, "%s: index is %d\n", __func__, n); if (n >= TW68_INPUT_MAX) { dprintk(DBG_FLOW, "%s: INPUT_MAX reached\n", __func__); return -EINVAL; } if (NULL == card_in(dev, n).name) { dprintk(DBG_FLOW, "%s: End of list\n", __func__); return -EINVAL; } memset(i, 0, sizeof(*i)); i->index = n; i->type = V4L2_INPUT_TYPE_CAMERA; strcpy(i->name, card_in(dev, n).name); if (card_in(dev, n).tv) i->type = V4L2_INPUT_TYPE_TUNER; i->audioset = 1; /* If the query is for the current input, get live data */ if (n == dev->hw_input->vmux) { int v1 = tw_readb(TW68_STATUS1); int v2 = tw_readb(TW68_MVSN); if (0 != (v1 & (1 << 7))) i->status |= V4L2_IN_ST_NO_SYNC; if (0 != (v1 & (1 << 6))) i->status |= V4L2_IN_ST_NO_H_LOCK; if (0 != (v1 & (1 << 2))) i->status |= V4L2_IN_ST_NO_SIGNAL; if (0 != (v1 & 1 << 1)) i->status |= V4L2_IN_ST_NO_COLOR; if (0 != (v2 & (1 << 2))) i->status |= V4L2_IN_ST_MACROVISION; } i->std = TW68_NORMS; return 0; } static int tw68_g_input(struct file *file, void *priv, unsigned int *i) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); *i = dev->input->vmux; return 0; } static int tw68_s_input(struct file *file, void *priv, unsigned int i) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int err; dprintk(DBG_FLOW, "%s\n", __func__); #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) err = v4l2_prio_check(&dev->prio, &fh->prio); #else err = v4l2_prio_check(&dev->prio, fh->prio); #endif if (0 != err) if (0 != err) return err; if (i < 0 || i >= TW68_INPUT_MAX) return -EINVAL; if (NULL == card_in(dev, i).name) return -EINVAL; mutex_lock(&dev->lock); video_mux(dev, i); mutex_unlock(&dev->lock); return 0; } static int tw68_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; unsigned int tuner_type = dev->tuner_type; dprintk(DBG_FLOW, "%s\n", __func__); strcpy(cap->driver, "tw68"); strlcpy(cap->card, tw68_boards[dev->board].name, sizeof(cap->card)); sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci)); cap->version = TW68_VERSION_CODE; cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | V4L2_CAP_TUNER; if ((tuner_type == TUNER_ABSENT) || (tuner_type == UNSET)) cap->capabilities &= ~V4L2_CAP_TUNER; return 0; } static int tw68_s_std_internal(struct tw68_dev *dev, struct tw68_fh *fh, v4l2_std_id *id) { /* unsigned long flags; */ unsigned int i; v4l2_std_id fixup; int err; dprintk(DBG_FLOW, "%s\n", __func__); if (fh) { #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) err = v4l2_prio_check(&dev->prio, &fh->prio); #else err = v4l2_prio_check(&dev->prio, fh->prio); #endif if (0 != err) if (0 != err) return err; } /* Look for match on complete norm id (may have mult bits) */ for (i = 0; i < TVNORMS; i++) { if (*id == tvnorms[i].id) break; } /* If no exact match, look for norm which contains this one */ if (i == TVNORMS) for (i = 0; i < TVNORMS; i++) { if (*id & tvnorms[i].id) break; } /* If still not matched, give up */ if (i == TVNORMS) return -EINVAL; /* TODO - verify this additional work with SECAM applies to TW */ if ((*id & V4L2_STD_SECAM) && (secam[0] != '-')) { if (secam[0] == 'L' || secam[0] == 'l') { if (secam[1] == 'C' || secam[1] == 'c') fixup = V4L2_STD_SECAM_LC; else fixup = V4L2_STD_SECAM_L; } else { if (secam[0] == 'D' || secam[0] == 'd') fixup = V4L2_STD_SECAM_DK; else fixup = V4L2_STD_SECAM; } for (i = 0; i < TVNORMS; i++) if (fixup == tvnorms[i].id) break; } *id = tvnorms[i].id; mutex_lock(&dev->lock); set_tvnorm(dev, &tvnorms[i]); /* do the actual setting */ tw68_tvaudio_do_scan(dev); mutex_unlock(&dev->lock); return 0; } static int tw68_s_std(struct file *file, void *priv, v4l2_std_id *id) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); return tw68_s_std_internal(fh->dev, fh, id); } static int tw68_g_std(struct file *file, void *priv, v4l2_std_id *id) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); *id = dev->tvnorm->id; return 0; } static int tw68_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int n; if (unlikely(UNSET == dev->tuner_type)) return -EINVAL; if (0 != t->index) return -EINVAL; memset(t, 0, sizeof(*t)); for (n = 0; n < TW68_INPUT_MAX; n++) if (card_in(dev, n).tv) break; if (n == TW68_INPUT_MAX) return -EINVAL; #if 0 if (NULL != card_in(dev, n).name) { strcpy(t->name, "Television"); t->type = V4L2_TUNER_ANALOG_TV; t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; t->rangehigh = 0xffffffffUL; t->rxsubchans = tw68_tvaudio_getstereo(dev); t->audmode = tw68_tvaudio_rx2mode(t->rxsubchans); } if (0 != (saa_readb(TW68_STATUS_VIDEO1) & 0x03)) t->signal = 0xffff; #endif return 0; } static int tw68_s_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int err; #if 0 int rx, mode #endif #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) err = v4l2_prio_check(&dev->prio, &fh->prio); #else err = v4l2_prio_check(&dev->prio, fh->prio); #endif if (0 != err) if (0 != err) return err; #if 0 mode = dev->thread.mode; if (UNSET == mode) { rx = tw68_tvaudio_getstereo(dev); mode = tw68_tvaudio_rx2mode(t->rxsubchans); } if (mode != t->audmode) dev->thread.mode = t->audmode; #endif return 0; } static int tw68_g_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; if (unlikely(dev->tuner_type)) return -EINVAL; f->type = fh->radio ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; /* f->frequency = dev->ctl_freq; */ return 0; } static int tw68_s_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int err; if (unlikely(UNSET == dev->tuner_type)) return -EINVAL; #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) err = v4l2_prio_check(&dev->prio, &fh->prio); #else err = v4l2_prio_check(&dev->prio, fh->prio); #endif if (0 != err) if (0 != err) return err; if (0 != f->tuner) return -EINVAL; if (0 == fh->radio && V4L2_TUNER_ANALOG_TV != f->type) return -EINVAL; if (1 == fh->radio && V4L2_TUNER_RADIO != f->type) return -EINVAL; mutex_lock(&dev->lock); /* dev->ctl_freq = f->frequency; */ tw_call_all(dev, tuner, s_frequency, f); tw68_tvaudio_do_scan(dev); mutex_unlock(&dev->lock); return 0; } static int tw68_g_audio(struct file *file, void *priv, struct v4l2_audio *a) { strcpy(a->name, "audio"); return 0; } static int tw68_s_audio(struct file *file, void *priv, struct v4l2_audio *a) { return 0; } static int tw68_g_priority(struct file *file, void *f, enum v4l2_priority *p) { struct tw68_fh *fh = f; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); *p = v4l2_prio_max(&dev->prio); return 0; } static int tw68_s_priority(struct file *file, void *f, enum v4l2_priority prio) { struct tw68_fh *fh = f; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); return v4l2_prio_change(&dev->prio, &fh->prio, prio); } static int tw68_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); if (f->index >= FORMATS) return -EINVAL; strlcpy(f->description, formats[f->index].name, sizeof(f->description)); f->pixelformat = formats[f->index].fourcc; return 0; } static int tw68_cropcap(struct file *file, void *priv, struct v4l2_cropcap *cap) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; cap->bounds = dev->crop_bounds; cap->defrect = dev->crop_defrect; cap->pixelaspect.numerator = 1; cap->pixelaspect.denominator = 1; if (dev->tvnorm->id & V4L2_STD_525_60) { cap->pixelaspect.numerator = 11; cap->pixelaspect.denominator = 10; } if (dev->tvnorm->id & V4L2_STD_625_50) { cap->pixelaspect.numerator = 54; cap->pixelaspect.denominator = 59; } return 0; } static int tw68_g_crop(struct file *file, void *f, struct v4l2_crop *crop) { struct tw68_fh *fh = f; struct tw68_dev *dev = fh->dev; dprintk(DBG_FLOW, "%s\n", __func__); if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; crop->c = dev->crop_current; return 0; } static int tw68_s_crop(struct file *file, void *f, struct v4l2_crop *crop) { struct tw68_fh *fh = f; struct tw68_dev *dev = fh->dev; struct v4l2_rect *b = &dev->crop_bounds; dprintk(DBG_FLOW, "%s\n", __func__); if (res_locked(fh->dev, RESOURCE_VIDEO)) return -EBUSY; if ((crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || (crop->c.height < 0) || (crop->c.width < 0)) { dprintk(DBG_UNEXPECTED, "%s: invalid request\n", __func__); return -EINVAL; } if (crop->c.top < b->top) crop->c.top = b->top; if (crop->c.top > b->top + b->height) crop->c.top = b->top + b->height; if (crop->c.height > b->top - crop->c.top + b->height) crop->c.height = b->top - crop->c.top + b->height; if (crop->c.left < b->left) crop->c.left = b->left; if (crop->c.left > b->left + b->width) crop->c.left = b->left + b->width; if (crop->c.width > b->left - crop->c.left + b->width) crop->c.width = b->left - crop->c.left + b->width; dprintk(DBG_FLOW, "%s: setting cropping rectangle: top=%d, left=%d, " "width=%d, height=%d\n", __func__, crop->c.top, crop->c.left, crop->c.width, crop->c.height); dev->crop_current = crop->c; return 0; } /* * Wrappers for the v4l2_ioctl_ops functions */ #ifdef CONFIG_VIDEO_V4L1_COMPAT static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf) { struct tw68_fh *fh = file->private_data; return videobuf_cgmbuf(tw68_queue(fh), mbuf, 8); } #endif static int tw68_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { struct tw68_fh *fh = priv; return videobuf_reqbufs(tw68_queue(fh), p); } static int tw68_querybuf(struct file *file, void *priv, struct v4l2_buffer *b) { struct tw68_fh *fh = priv; return videobuf_querybuf(tw68_queue(fh), b); } static int tw68_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) { struct tw68_fh *fh = priv; return videobuf_qbuf(tw68_queue(fh), b); } static int tw68_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) { struct tw68_fh *fh = priv; return videobuf_dqbuf(tw68_queue(fh), b, file->f_flags & O_NONBLOCK); } static int tw68_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int res = tw68_resource(fh); dprintk(DBG_FLOW, "%s\n", __func__); if (!res_get(fh, res)) return -EBUSY; tw68_buffer_requeue(dev, &dev->video_q); return videobuf_streamon(tw68_queue(fh)); } static int tw68_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { int err; struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; int res = tw68_resource(fh); dprintk(DBG_FLOW, "%s\n", __func__); err = videobuf_streamoff(tw68_queue(fh)); if (err < 0) return err; res_free(fh, res); return 0; } #ifdef CONFIG_VIDEO_ADV_DEBUG /* * Used strictly for internal development and debugging, this routine * prints out the current register contents for the tw68xx device. */ static void tw68_dump_regs(struct tw68_dev *dev) { unsigned char line[80]; int i, j, k; unsigned char *cptr; printk(KERN_DEBUG "Full dump of TW68 registers:\n"); /* First we do the PCI regs, 8 4-byte regs per line */ for (i = 0; i < 0x100; i += 32) { cptr = line; cptr += sprintf(cptr, "%03x ", i); /* j steps through the next 4 words */ for (j = i; j < i + 16; j += 4) cptr += sprintf(cptr, "%08x ", tw_readl(j)); *cptr++ = ' '; for (; j < i + 32; j += 4) cptr += sprintf(cptr, "%08x ", tw_readl(j)); *cptr++ = '\n'; *cptr = 0; printk(KERN_DEBUG "%s", line); } /* Next the control regs, which are single-byte, address mod 4 */ while (i < 0x400) { cptr = line; cptr += sprintf(cptr, "%03x ", i); /* Print out 4 groups of 4 bytes */ for (j = 0; j < 4; j++) { for (k = 0; k < 4; k++) { cptr += sprintf(cptr, "%02x ", tw_readb(i)); i += 4; } *cptr++ = ' '; } *cptr++ = '\n'; *cptr = 0; printk(KERN_DEBUG "%s", line); } } static int vidioc_log_status(struct file *file, void *priv) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; tw68_dump_regs(dev); return 0; } static int vidioc_g_register(struct file *file, void *priv, struct v4l2_dbg_register *reg) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; /* needed for tw_readb */ dprintk(DBG_FLOW, "%s\n", __func__); if (!v4l2_chip_match_host(®->match)) dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__); return -EINVAL; if (reg->size == 1) reg->val = tw_readb(reg->reg); else reg->val = tw_readl(reg->reg); return 0; } static int vidioc_s_register(struct file *file, void *priv, struct v4l2_dbg_register *reg) { struct tw68_fh *fh = priv; struct tw68_dev *dev = fh->dev; /* needed for tw_writeb */ dprintk(DBG_FLOW, "%s: request to set reg 0x%04x to 0x%02x\n", __func__, (unsigned int)reg->reg, (unsigned int)reg->val); if (!v4l2_chip_match_host(®->match)) { dprintk(DBG_UNEXPECTED, "%s: match failed\n", __func__); return -EINVAL; } if (reg->size == 1) tw_writeb(reg->reg, reg->val); else tw_writel(reg->reg & 0xffff, reg->val); return 0; } #endif static const struct v4l2_file_operations video_fops = { .owner = THIS_MODULE, .open = video_open, .release = video_release, .read = video_read, .poll = video_poll, .mmap = video_mmap, .ioctl = video_ioctl2, }; static const struct v4l2_ioctl_ops video_ioctl_ops = { .vidioc_querycap = tw68_querycap, .vidioc_enum_fmt_vid_cap = tw68_enum_fmt_vid_cap, .vidioc_reqbufs = tw68_reqbufs, .vidioc_querybuf = tw68_querybuf, .vidioc_qbuf = tw68_qbuf, .vidioc_dqbuf = tw68_dqbuf, .vidioc_s_std = tw68_s_std, .vidioc_g_std = tw68_g_std, .vidioc_enum_input = tw68_enum_input, .vidioc_g_input = tw68_g_input, .vidioc_s_input = tw68_s_input, .vidioc_queryctrl = tw68_queryctrl, .vidioc_g_ctrl = tw68_g_ctrl, .vidioc_s_ctrl = tw68_s_ctrl, .vidioc_streamon = tw68_streamon, .vidioc_streamoff = tw68_streamoff, .vidioc_g_priority = tw68_g_priority, .vidioc_s_priority = tw68_s_priority, .vidioc_g_fmt_vid_cap = tw68_g_fmt_vid_cap, .vidioc_try_fmt_vid_cap = tw68_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = tw68_s_fmt_vid_cap, .vidioc_cropcap = tw68_cropcap, .vidioc_g_crop = tw68_g_crop, .vidioc_s_crop = tw68_s_crop, /* * Functions not yet implemented / not yet passing tests. */ #if 0 .vidioc_g_fmt_vbi_cap = tw68_try_get_set_fmt_vbi_cap, .vidioc_try_fmt_vbi_cap = tw68_try_get_set_fmt_vbi_cap, .vidioc_s_fmt_vbi_cap = tw68_try_get_set_fmt_vbi_cap, #endif .vidioc_g_audio = tw68_g_audio, .vidioc_s_audio = tw68_s_audio, .vidioc_g_tuner = tw68_g_tuner, .vidioc_s_tuner = tw68_s_tuner, .vidioc_g_frequency = tw68_g_frequency, .vidioc_s_frequency = tw68_s_frequency, #ifdef CONFIG_VIDEO_V4L1_COMPAT .vidiocgmbuf = vidiocgmbuf, #endif #ifdef CONFIG_VIDEO_ADV_DEBUG .vidioc_log_status = vidioc_log_status, .vidioc_g_register = vidioc_g_register, .vidioc_s_register = vidioc_s_register, #endif }; /* ------------------------------------------------------------------ */ /* exported stuff */ struct video_device tw68_video_template = { .name = "tw68_video", .fops = &video_fops, .ioctl_ops = &video_ioctl_ops, .minor = -1, .tvnorms = TW68_NORMS, .current_norm = V4L2_STD_PAL, }; struct video_device tw68_radio_template = { .name = "tw68_radio", }; int tw68_videoport_init(struct tw68_dev *dev) { return 0; } void tw68_set_tvnorm_hw(struct tw68_dev *dev) { tw_andorb(TW68_SDT, 0x07, dev->tvnorm->format); return; } int tw68_video_init1(struct tw68_dev *dev) { int i; dprintk(DBG_FLOW, "%s\n", __func__); /* sanitycheck insmod options */ if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) gbuffers = 2; if (gbufsz < 0 || gbufsz > gbufsz_max) gbufsz = gbufsz_max; gbufsz = (gbufsz + PAGE_SIZE - 1) & PAGE_MASK; /* put some sensible defaults into the data structures ... */ for (i = 0; i < CTRLS; i++) tw68_s_ctrl_value(dev, video_ctrls[i].id, video_ctrls[i].default_value); #if 0 if (dev->tda9887_conf && dev->ctl_automute) dev->tda9887_conf |= TDA9887_AUTOMUTE; dev->automute = 0; #endif INIT_LIST_HEAD(&dev->video_q.queued); INIT_LIST_HEAD(&dev->video_q.active); init_timer(&dev->video_q.timeout); dev->video_q.timeout.function = tw68_buffer_timeout; dev->video_q.timeout.data = (unsigned long)(&dev->video_q); dev->video_q.dev = dev; dev->video_q.buf_compat = tw68_check_video_fmt; dev->video_q.start_dma = tw68_video_start_dma; tw68_risc_stopper(dev->pci, &dev->video_q.stopper); if (tw68_boards[dev->board].video_out) tw68_videoport_init(dev); return 0; } int tw68_video_init2(struct tw68_dev *dev) { dprintk(DBG_FLOW, "%s\n", __func__); set_tvnorm(dev, &tvnorms[0]); video_mux(dev, 0); /* tw68_tvaudio_setmut(dev); tw68_tvaudio_setvolume(dev, dev->ctl_volume); */ return 0; } /* * tw68_irq_video_signalchange * * TODO: * Check for presence of video signal. If not present, mute audio. * If present, log type of signal present. */ void tw68_irq_video_signalchange(struct tw68_dev *dev) { return; } /* * tw68_irq_video_done */ void tw68_irq_video_done(struct tw68_dev *dev, unsigned long status) { __u32 reg; /* reset interrupts handled by this routine */ tw_writel(TW68_INTSTAT, status); /* * Check most likely first * * DMAPI shows we have reached the end of the risc code * for the current buffer. */ if (status & TW68_DMAPI) { struct tw68_dmaqueue *q = &dev->video_q; dprintk(DBG_FLOW | DBG_TESTING, "DMAPI interrupt\n"); spin_lock(&dev->slock); /* * tw68_wakeup will take care of the buffer handling, * plus any non-video requirements. */ tw68_wakeup(q, &dev->video_fieldcount); spin_unlock(&dev->slock); /* Check whether we have gotten into 'stopper' code */ reg = tw_readl(TW68_DMAP_PP); if ((reg >= q->stopper.dma) && (reg < q->stopper.dma + q->stopper.size)) { /* Yes - log the information */ dprintk(DBG_FLOW | DBG_TESTING, "%s: stopper risc code entered\n", __func__); } status &= ~(TW68_DMAPI); if (0 == status) return; } if (status & (TW68_VLOCK | TW68_HLOCK)) { /* lost sync */ dprintk(DBG_UNUSUAL, "Lost sync\n"); } if (status & TW68_PABORT) { /* TODO - what should we do? */ dprintk(DBG_UNEXPECTED, "PABORT interrupt\n"); } if (status & TW68_DMAPERR) { dprintk(DBG_UNEXPECTED, "DMAPERR interrupt\n"); #if 0 /* Stop risc & fifo */ tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); tw_clearl(TW68_INTMASK, dev->board_virqmask); dev->pci_irqmask &= ~dev->board_virqmask; #endif } /* * On TW6800, FDMIS is apparently generated if video input is switched * during operation. Therefore, it is not enabled for that chip. */ if (status & TW68_FDMIS) { /* logic error somewhere */ dprintk(DBG_UNEXPECTED, "FDMIS interrupt\n"); /* Stop risc & fifo */ // tw_clearl(TW68_DMAC, TW68_DMAP_EN | TW68_FIFO_EN); // tw_clearl(TW68_INTMASK, dev->board_virqmask); // dev->pci_irqmask &= ~dev->board_virqmask; } if (status & TW68_FFOF) { /* probably a logic error */ reg = tw_readl(TW68_DMAC) & TW68_FIFO_EN; tw_clearl(TW68_DMAC, TW68_FIFO_EN); dprintk(DBG_UNUSUAL, "FFOF interrupt\n"); tw_setl(TW68_DMAC, reg); } if (status & TW68_FFERR) dprintk(DBG_UNEXPECTED, "FFERR interrupt\n"); return; }