diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2014-09-03 03:31:07 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <m.chehab@samsung.com> | 2014-09-04 11:25:01 -0300 |
commit | 5740f4e75f713015067e2667a52bd3b35ef91e07 (patch) | |
tree | 9b58a95433a0e3db4fa4f152e8ef2e76e036efc3 /drivers/media/pci/tw68/tw68-video.c | |
parent | 89fffac802c18caebdf4e91c0785b522c9f6399a (diff) |
[media] tw68: add original tw68 code
This tw68 driver has been out-of-tree for many years on gitorious:
https://gitorious.org/tw68/tw68-v2.
This copies that code to the kernel as a record of that original code.
Note that William Brack's email address in these sources is no longer
valid and I have not been able to contact him. However, all the code is
standard GPL.
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
Diffstat (limited to 'drivers/media/pci/tw68/tw68-video.c')
-rw-r--r-- | drivers/media/pci/tw68/tw68-video.c | 2230 |
1 files changed, 2230 insertions, 0 deletions
diff --git a/drivers/media/pci/tw68/tw68-video.c b/drivers/media/pci/tw68/tw68-video.c new file mode 100644 index 000000000000..ca08ca38d3bd --- /dev/null +++ b/drivers/media/pci/tw68/tw68-video.c @@ -0,0 +1,2230 @@ +/* + * 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 <wbrack@mmm.com.hk> + * + * 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 <linux/module.h> +#include <media/v4l2-common.h> +#include <linux/sort.h> + +#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; +} |