V4L/DVB (4228a): pvrusb2 to kernel 2.6.18

Implement V4L2 driver for the Hauppauge PVR USB2 TV tuner.

The Hauppauge PVR USB2 is a USB connected TV tuner with an embedded
cx23416 hardware MPEG2 encoder.  There are two major variants of this
device; this driver handles both.  Any V4L2 application which
understands MPEG2 video stream data should be able to work with this
device.

Signed-off-by: Mike Isely <isely@pobox.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c
new file mode 100644
index 0000000..74b681a
--- /dev/null
+++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c
@@ -0,0 +1,1056 @@
+/*
+ *
+ *  $Id$
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include "pvrusb2-context.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#include "pvrusb2-ioread.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+
+struct pvr2_v4l2_dev;
+struct pvr2_v4l2_fh;
+struct pvr2_v4l2;
+
+/* V4L no longer provide the ability to set / get a private context pointer
+   (i.e. video_get_drvdata / video_set_drvdata), which means we have to
+   concoct our own context locating mechanism.  Supposedly this is intended
+   to simplify driver implementation.  It's not clear to me how that can
+   possibly be true.  Our solution here is to maintain a lookup table of
+   our context instances, indexed by the minor device number of the V4L
+   device.  See pvr2_v4l2_open() for some implications of this approach. */
+static struct pvr2_v4l2_dev *devices[256];
+static DEFINE_MUTEX(device_lock);
+
+struct pvr2_v4l2_dev {
+	struct pvr2_v4l2 *v4lp;
+	struct video_device *vdev;
+	struct pvr2_context_stream *stream;
+	int ctxt_idx;
+	enum pvr2_config config;
+};
+
+struct pvr2_v4l2_fh {
+	struct pvr2_channel channel;
+	struct pvr2_v4l2_dev *dev_info;
+	enum v4l2_priority prio;
+	struct pvr2_ioread *rhp;
+	struct file *file;
+	struct pvr2_v4l2 *vhead;
+	struct pvr2_v4l2_fh *vnext;
+	struct pvr2_v4l2_fh *vprev;
+	wait_queue_head_t wait_data;
+	int fw_mode_flag;
+};
+
+struct pvr2_v4l2 {
+	struct pvr2_channel channel;
+	struct pvr2_v4l2_fh *vfirst;
+	struct pvr2_v4l2_fh *vlast;
+
+	struct v4l2_prio_state prio;
+
+	/* streams */
+	struct pvr2_v4l2_dev video_dev;
+};
+
+static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(video_nr, int, NULL, 0444);
+MODULE_PARM_DESC(video_nr, "Offset for device's minor");
+
+struct v4l2_capability pvr_capability ={
+	.driver         = "pvrusb2",
+	.card           = "Hauppauge WinTV pvr-usb2",
+	.bus_info       = "usb",
+	.version        = KERNEL_VERSION(0,8,0),
+	.capabilities   = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE |
+			   V4L2_CAP_TUNER | V4L2_CAP_AUDIO |
+			   V4L2_CAP_READWRITE),
+	.reserved       = {0,0,0,0}
+};
+
+static struct v4l2_tuner pvr_v4l2_tuners[]= {
+	{
+		.index      = 0,
+		.name       = "TV Tuner",
+		.type           = V4L2_TUNER_ANALOG_TV,
+		.capability     = (V4L2_TUNER_CAP_NORM |
+				   V4L2_TUNER_CAP_STEREO |
+				   V4L2_TUNER_CAP_LANG1 |
+				   V4L2_TUNER_CAP_LANG2),
+		.rangelow   = 0,
+		.rangehigh  = 0,
+		.rxsubchans     = V4L2_TUNER_SUB_STEREO,
+		.audmode        = V4L2_TUNER_MODE_STEREO,
+		.signal         = 0,
+		.afc            = 0,
+		.reserved       = {0,0,0,0}
+	}
+};
+
+struct v4l2_fmtdesc pvr_fmtdesc [] = {
+	{
+		.index          = 0,
+		.type           = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+		.flags          = V4L2_FMT_FLAG_COMPRESSED,
+		.description    = "MPEG1/2",
+		// This should really be V4L2_PIX_FMT_MPEG, but xawtv
+		// breaks when I do that.
+		.pixelformat    = 0, // V4L2_PIX_FMT_MPEG,
+		.reserved       = { 0, 0, 0, 0 }
+	}
+};
+
+#define PVR_FORMAT_PIX  0
+#define PVR_FORMAT_VBI  1
+
+struct v4l2_format pvr_format [] = {
+	[PVR_FORMAT_PIX] = {
+		.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+		.fmt    = {
+			.pix        = {
+				.width          = 720,
+				.height             = 576,
+				// This should really be V4L2_PIX_FMT_MPEG,
+				// but xawtv breaks when I do that.
+				.pixelformat    = 0, // V4L2_PIX_FMT_MPEG,
+				.field          = V4L2_FIELD_INTERLACED,
+				.bytesperline   = 0,  // doesn't make sense
+						      // here
+				//FIXME : Don't know what to put here...
+				.sizeimage          = (32*1024),
+				.colorspace     = 0, // doesn't make sense here
+				.priv           = 0
+			}
+		}
+	},
+	[PVR_FORMAT_VBI] = {
+		.type   = V4L2_BUF_TYPE_VBI_CAPTURE,
+		.fmt    = {
+			.vbi        = {
+				.sampling_rate = 27000000,
+				.offset = 248,
+				.samples_per_line = 1443,
+				.sample_format = V4L2_PIX_FMT_GREY,
+				.start = { 0, 0 },
+				.count = { 0, 0 },
+				.flags = 0,
+				.reserved = { 0, 0 }
+			}
+		}
+	}
+};
+
+/*
+ * pvr_ioctl()
+ *
+ * This is part of Video 4 Linux API. The procedure handles ioctl() calls.
+ *
+ */
+static int pvr2_v4l2_do_ioctl(struct inode *inode, struct file *file,
+			      unsigned int cmd, void *arg)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_v4l2 *vp = fh->vhead;
+	struct pvr2_v4l2_dev *dev_info = fh->dev_info;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret = -EINVAL;
+
+	if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) {
+		v4l_print_ioctl(pvr2_hdw_get_driver_name(hdw),cmd);
+	}
+
+	if (!pvr2_hdw_dev_ok(hdw)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "ioctl failed - bad or no context");
+		return -EFAULT;
+	}
+
+	/* check priority */
+	switch (cmd) {
+	case VIDIOC_S_CTRL:
+	case VIDIOC_S_STD:
+	case VIDIOC_S_INPUT:
+	case VIDIOC_S_TUNER:
+	case VIDIOC_S_FREQUENCY:
+		ret = v4l2_prio_check(&vp->prio, &fh->prio);
+		if (ret)
+			return ret;
+	}
+
+	switch (cmd) {
+	case VIDIOC_QUERYCAP:
+	{
+		struct v4l2_capability *cap = arg;
+
+		memcpy(cap, &pvr_capability, sizeof(struct v4l2_capability));
+
+		ret = 0;
+		break;
+	}
+
+	case VIDIOC_G_PRIORITY:
+	{
+		enum v4l2_priority *p = arg;
+
+		*p = v4l2_prio_max(&vp->prio);
+		ret = 0;
+		break;
+	}
+
+	case VIDIOC_S_PRIORITY:
+	{
+		enum v4l2_priority *prio = arg;
+
+		ret = v4l2_prio_change(&vp->prio, &fh->prio, *prio);
+		break;
+	}
+
+	case VIDIOC_ENUMSTD:
+	{
+		struct v4l2_standard *vs = (struct v4l2_standard *)arg;
+		int idx = vs->index;
+		ret = pvr2_hdw_get_stdenum_value(hdw,vs,idx+1);
+		break;
+	}
+
+	case VIDIOC_G_STD:
+	{
+		int val = 0;
+		ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),&val);
+		*(v4l2_std_id *)arg = val;
+		break;
+	}
+
+	case VIDIOC_S_STD:
+	{
+		ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),
+			*(v4l2_std_id *)arg);
+		break;
+	}
+
+	case VIDIOC_ENUMINPUT:
+	{
+		struct pvr2_ctrl *cptr;
+		struct v4l2_input *vi = (struct v4l2_input *)arg;
+		struct v4l2_input tmp;
+		unsigned int cnt;
+
+		cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT);
+
+		memset(&tmp,0,sizeof(tmp));
+		tmp.index = vi->index;
+		ret = 0;
+		switch (vi->index) {
+		case PVR2_CVAL_INPUT_TV:
+		case PVR2_CVAL_INPUT_RADIO:
+			tmp.type = V4L2_INPUT_TYPE_TUNER;
+			break;
+		case PVR2_CVAL_INPUT_SVIDEO:
+		case PVR2_CVAL_INPUT_COMPOSITE:
+			tmp.type = V4L2_INPUT_TYPE_CAMERA;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		if (ret < 0) break;
+
+		cnt = 0;
+		pvr2_ctrl_get_valname(cptr,vi->index,
+				      tmp.name,sizeof(tmp.name)-1,&cnt);
+		tmp.name[cnt] = 0;
+
+		/* Don't bother with audioset, since this driver currently
+		   always switches the audio whenever the video is
+		   switched. */
+
+		/* Handling std is a tougher problem.  It doesn't make
+		   sense in cases where a device might be multi-standard.
+		   We could just copy out the current value for the
+		   standard, but it can change over time.  For now just
+		   leave it zero. */
+
+		memcpy(vi, &tmp, sizeof(tmp));
+
+		ret = 0;
+		break;
+	}
+
+	case VIDIOC_G_INPUT:
+	{
+		struct pvr2_ctrl *cptr;
+		struct v4l2_input *vi = (struct v4l2_input *)arg;
+		int val;
+		cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT);
+		val = 0;
+		ret = pvr2_ctrl_get_value(cptr,&val);
+		vi->index = val;
+		break;
+	}
+
+	case VIDIOC_S_INPUT:
+	{
+		struct v4l2_input *vi = (struct v4l2_input *)arg;
+		ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT),
+			vi->index);
+		break;
+	}
+
+	case VIDIOC_ENUMAUDIO:
+	{
+		ret = -EINVAL;
+		break;
+	}
+
+	case VIDIOC_G_AUDIO:
+	{
+		ret = -EINVAL;
+		break;
+	}
+
+	case VIDIOC_S_AUDIO:
+	{
+		ret = -EINVAL;
+		break;
+	}
+	case VIDIOC_G_TUNER:
+	{
+		struct v4l2_tuner *vt = (struct v4l2_tuner *)arg;
+		unsigned int status_mask;
+		int val;
+		if (vt->index !=0) break;
+
+		status_mask = pvr2_hdw_get_signal_status(hdw);
+
+		memcpy(vt, &pvr_v4l2_tuners[vt->index],
+		       sizeof(struct v4l2_tuner));
+
+		vt->signal = 0;
+		if (status_mask & PVR2_SIGNAL_OK) {
+			if (status_mask & PVR2_SIGNAL_STEREO) {
+				vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
+			} else {
+				vt->rxsubchans = V4L2_TUNER_SUB_MONO;
+			}
+			if (status_mask & PVR2_SIGNAL_SAP) {
+				vt->rxsubchans |= (V4L2_TUNER_SUB_LANG1 |
+						   V4L2_TUNER_SUB_LANG2);
+			}
+			vt->signal = 65535;
+		}
+
+		val = 0;
+		ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_AUDIOMODE),
+			&val);
+		vt->audmode = val;
+		break;
+	}
+
+	case VIDIOC_S_TUNER:
+	{
+		struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+
+		if (vt->index != 0)
+			break;
+
+		ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_AUDIOMODE),
+			vt->audmode);
+	}
+
+	case VIDIOC_S_FREQUENCY:
+	{
+		const struct v4l2_frequency *vf = (struct v4l2_frequency *)arg;
+		ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),
+			vf->frequency * 62500);
+		break;
+	}
+
+	case VIDIOC_G_FREQUENCY:
+	{
+		struct v4l2_frequency *vf = (struct v4l2_frequency *)arg;
+		int val = 0;
+		ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),
+			&val);
+		val /= 62500;
+		vf->frequency = val;
+		break;
+	}
+
+	case VIDIOC_ENUM_FMT:
+	{
+		struct v4l2_fmtdesc *fd = (struct v4l2_fmtdesc *)arg;
+
+		/* Only one format is supported : mpeg.*/
+		if (fd->index != 0)
+			break;
+
+		memcpy(fd, pvr_fmtdesc, sizeof(struct v4l2_fmtdesc));
+		ret = 0;
+		break;
+	}
+
+	case VIDIOC_G_FMT:
+	{
+		struct v4l2_format *vf = (struct v4l2_format *)arg;
+		int val;
+		switch(vf->type) {
+		case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+			memcpy(vf, &pvr_format[PVR_FORMAT_PIX],
+			       sizeof(struct v4l2_format));
+			val = 0;
+			pvr2_ctrl_get_value(
+				pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_HRES),
+				&val);
+			vf->fmt.pix.width = val;
+			val = 0;
+			pvr2_ctrl_get_value(
+				pvr2_hdw_get_ctrl_by_id(hdw,
+							PVR2_CID_INTERLACE),
+				&val);
+			if (val) vf->fmt.pix.width /= 2;
+			val = 0;
+			pvr2_ctrl_get_value(
+				pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_VRES),
+				&val);
+			vf->fmt.pix.height = val;
+			ret = 0;
+			break;
+		case V4L2_BUF_TYPE_VBI_CAPTURE:
+			// ????? Still need to figure out to do VBI correctly
+			ret = -EINVAL;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	}
+
+	case VIDIOC_TRY_FMT:
+	case VIDIOC_S_FMT:
+	{
+		struct v4l2_format *vf = (struct v4l2_format *)arg;
+
+		ret = 0;
+		switch(vf->type) {
+		case V4L2_BUF_TYPE_VIDEO_CAPTURE: {
+			int h = vf->fmt.pix.height;
+			int w = vf->fmt.pix.width;
+			int vd_std, hf, hh;
+
+			vd_std = 0;
+			pvr2_ctrl_get_value(
+				pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),
+				&vd_std);
+			if (vd_std & V4L2_STD_525_60) {
+				hf=480;
+			} else {
+				hf=576;
+			}
+			hh = (int) (hf / 2);
+
+			memcpy(vf, &pvr_format[PVR_FORMAT_PIX],
+			       sizeof(struct v4l2_format));
+			if (w > 720)
+				vf->fmt.pix.width = 720;
+			vf->fmt.pix.width &= 0xff0;
+			vf->fmt.pix.height = (h > hh) ? hf : hh;
+
+			if (cmd == VIDIOC_S_FMT) {
+				pvr2_ctrl_set_value(
+					pvr2_hdw_get_ctrl_by_id(hdw,
+								PVR2_CID_HRES),
+					vf->fmt.pix.width);
+				pvr2_ctrl_set_value(
+					pvr2_hdw_get_ctrl_by_id(hdw,
+								PVR2_CID_VRES),
+					vf->fmt.pix.height);
+				pvr2_ctrl_set_value(
+					pvr2_hdw_get_ctrl_by_id(
+						hdw,PVR2_CID_INTERLACE),
+					vf->fmt.pix.height != hf);
+			}
+		} break;
+		case V4L2_BUF_TYPE_VBI_CAPTURE:
+			// ????? Still need to figure out to do VBI correctly
+			ret = -EINVAL;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	}
+
+	case VIDIOC_STREAMON:
+	{
+		ret = pvr2_hdw_set_stream_type(hdw,dev_info->config);
+		if (ret < 0) return ret;
+		ret = pvr2_hdw_set_streaming(hdw,!0);
+		break;
+	}
+
+	case VIDIOC_STREAMOFF:
+	{
+		ret = pvr2_hdw_set_streaming(hdw,0);
+		break;
+	}
+
+	case VIDIOC_QUERYCTRL:
+	{
+		struct pvr2_ctrl *cptr;
+		struct v4l2_queryctrl *vc = (struct v4l2_queryctrl *)arg;
+		ret = 0;
+		cptr = pvr2_hdw_get_ctrl_v4l(hdw,vc->id);
+		if (!cptr) {
+			ret = -EINVAL;
+			break;
+		}
+
+		strlcpy(vc->name,pvr2_ctrl_get_name(cptr),sizeof(vc->name));
+		vc->default_value = pvr2_ctrl_get_def(cptr);
+		switch (pvr2_ctrl_get_type(cptr)) {
+		case pvr2_ctl_enum:
+			vc->type = V4L2_CTRL_TYPE_MENU;
+			vc->minimum = 0;
+			vc->maximum = pvr2_ctrl_get_cnt(cptr) - 1;
+			vc->step = 1;
+			break;
+		case pvr2_ctl_int:
+			vc->type = V4L2_CTRL_TYPE_INTEGER;
+			vc->minimum = pvr2_ctrl_get_min(cptr);
+			vc->maximum = pvr2_ctrl_get_max(cptr);
+			vc->step = 1;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	}
+
+	case VIDIOC_QUERYMENU:
+	{
+		struct v4l2_querymenu *vm = (struct v4l2_querymenu *)arg;
+		unsigned int cnt = 0;
+		ret = pvr2_ctrl_get_valname(pvr2_hdw_get_ctrl_v4l(hdw,vm->id),
+					    vm->index,
+					    vm->name,sizeof(vm->name)-1,
+					    &cnt);
+		vm->name[cnt] = 0;
+		break;
+	}
+
+	case VIDIOC_G_CTRL:
+	{
+		struct v4l2_control *vc = (struct v4l2_control *)arg;
+		int val = 0;
+		ret = pvr2_ctrl_get_value(pvr2_hdw_get_ctrl_v4l(hdw,vc->id),
+					  &val);
+		vc->value = val;
+		break;
+	}
+
+	case VIDIOC_S_CTRL:
+	{
+		struct v4l2_control *vc = (struct v4l2_control *)arg;
+		ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_v4l(hdw,vc->id),
+					  vc->value);
+		break;
+	}
+
+	case VIDIOC_LOG_STATUS:
+	{
+		int nr = pvr2_hdw_get_unit_number(hdw);
+
+		printk(KERN_INFO "pvrusb2: =================  START STATUS CARD #%d  =================\n", nr);
+		pvr2_hdw_trigger_module_log(hdw);
+		printk(KERN_INFO "pvrusb2: ==================  END STATUS CARD #%d  ==================\n", nr);
+		ret = 0;
+		break;
+	}
+
+	default :
+		ret = v4l_compat_translate_ioctl(inode,file,cmd,
+						 arg,pvr2_v4l2_do_ioctl);
+	}
+
+	pvr2_hdw_commit_ctl(hdw);
+
+	if (ret < 0) {
+		if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "pvr2_v4l2_do_ioctl failure, ret=%d",ret);
+		} else {
+			if (pvrusb2_debug & PVR2_TRACE_ERROR_LEGS) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "pvr2_v4l2_do_ioctl failure, ret=%d"
+					   " command was:",ret);
+				v4l_print_ioctl(pvr2_hdw_get_driver_name(hdw),
+						cmd);
+			}
+		}
+	} else {
+		pvr2_trace(PVR2_TRACE_V4LIOCTL,
+			   "pvr2_v4l2_do_ioctl complete, ret=%d (0x%x)",
+			   ret,ret);
+	}
+	return ret;
+}
+
+
+static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip)
+{
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "unregistering device video%d [%s]",
+		   dip->vdev->minor,pvr2_config_get_name(dip->config));
+	if (dip->ctxt_idx >= 0) {
+		mutex_lock(&device_lock);
+		devices[dip->ctxt_idx] = NULL;
+		dip->ctxt_idx = -1;
+		mutex_unlock(&device_lock);
+	}
+	video_unregister_device(dip->vdev);
+}
+
+
+static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp)
+{
+	pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,-1);
+	pvr2_v4l2_dev_destroy(&vp->video_dev);
+
+	pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp);
+	pvr2_channel_done(&vp->channel);
+	kfree(vp);
+}
+
+
+void pvr2_v4l2_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_v4l2 *vp;
+	vp = container_of(chp,struct pvr2_v4l2,channel);
+	if (!vp->channel.mc_head->disconnect_flag) return;
+	if (vp->vfirst) return;
+	pvr2_v4l2_destroy_no_lock(vp);
+}
+
+
+int pvr2_v4l2_ioctl(struct inode *inode, struct file *file,
+		    unsigned int cmd, unsigned long arg)
+{
+
+/* Temporary hack : use ivtv api until a v4l2 one is available. */
+#define IVTV_IOC_G_CODEC        0xFFEE7703
+#define IVTV_IOC_S_CODEC        0xFFEE7704
+	if (cmd == IVTV_IOC_G_CODEC || cmd == IVTV_IOC_S_CODEC) return 0;
+	return video_usercopy(inode, file, cmd, arg, pvr2_v4l2_do_ioctl);
+}
+
+
+int pvr2_v4l2_release(struct inode *inode, struct file *file)
+{
+	struct pvr2_v4l2_fh *fhp = file->private_data;
+	struct pvr2_v4l2 *vp = fhp->vhead;
+	struct pvr2_context *mp = fhp->vhead->channel.mc_head;
+
+	pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_release");
+
+	if (fhp->rhp) {
+		struct pvr2_stream *sp;
+		struct pvr2_hdw *hdw;
+		hdw = fhp->channel.mc_head->hdw;
+		pvr2_hdw_set_streaming(hdw,0);
+		sp = pvr2_ioread_get_stream(fhp->rhp);
+		if (sp) pvr2_stream_set_callback(sp,0,0);
+		pvr2_ioread_destroy(fhp->rhp);
+		fhp->rhp = 0;
+	}
+	v4l2_prio_close(&vp->prio, &fhp->prio);
+	file->private_data = NULL;
+
+	pvr2_context_enter(mp); do {
+		if (fhp->vnext) {
+			fhp->vnext->vprev = fhp->vprev;
+		} else {
+			vp->vlast = fhp->vprev;
+		}
+		if (fhp->vprev) {
+			fhp->vprev->vnext = fhp->vnext;
+		} else {
+			vp->vfirst = fhp->vnext;
+		}
+		fhp->vnext = 0;
+		fhp->vprev = 0;
+		fhp->vhead = 0;
+		pvr2_channel_done(&fhp->channel);
+		pvr2_trace(PVR2_TRACE_STRUCT,
+			   "Destroying pvr_v4l2_fh id=%p",fhp);
+		kfree(fhp);
+		if (vp->channel.mc_head->disconnect_flag && !vp->vfirst) {
+			pvr2_v4l2_destroy_no_lock(vp);
+		}
+	} while (0); pvr2_context_exit(mp);
+	return 0;
+}
+
+
+int pvr2_v4l2_open(struct inode *inode, struct file *file)
+{
+	struct pvr2_v4l2_dev *dip = 0; /* Our own context pointer */
+	struct pvr2_v4l2_fh *fhp;
+	struct pvr2_v4l2 *vp;
+	struct pvr2_hdw *hdw;
+
+	mutex_lock(&device_lock);
+	/* MCI 7-Jun-2006 Even though we're just doing what amounts to an
+	   atomic read of the device mapping array here, we still need the
+	   mutex.  The problem is that there is a tiny race possible when
+	   we register the device.  We can't update the device mapping
+	   array until after the device has been registered, owing to the
+	   fact that we can't know the minor device number until after the
+	   registration succeeds.  And if another thread tries to open the
+	   device in the window of time after registration but before the
+	   map is updated, then it will get back an erroneous null pointer
+	   and the open will result in a spurious failure.  The only way to
+	   prevent that is to (a) be inside the mutex here before we access
+	   the array, and (b) cover the entire registration process later
+	   on with this same mutex.  Thus if we get inside the mutex here,
+	   then we can be assured that the registration process actually
+	   completed correctly.  This is an unhappy complication from the
+	   use of global data in a driver that lives in a preemptible
+	   environment.  It sure would be nice if the video device itself
+	   had a means for storing and retrieving a local context pointer.
+	   Oh wait.  It did.  But now it's gone.  Silly me. */
+	{
+		unsigned int midx = iminor(file->f_dentry->d_inode);
+		if (midx < sizeof(devices)/sizeof(devices[0])) {
+			dip = devices[midx];
+		}
+	}
+	mutex_unlock(&device_lock);
+
+	if (!dip) return -ENODEV; /* Should be impossible but I'm paranoid */
+
+	vp = dip->v4lp;
+	hdw = vp->channel.hdw;
+
+	pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open");
+
+	if (!pvr2_hdw_dev_ok(hdw)) {
+		pvr2_trace(PVR2_TRACE_OPEN_CLOSE,
+			   "pvr2_v4l2_open: hardware not ready");
+		return -EIO;
+	}
+
+	fhp = kmalloc(sizeof(*fhp),GFP_KERNEL);
+	if (!fhp) {
+		return -ENOMEM;
+	}
+	memset(fhp,0,sizeof(*fhp));
+
+	init_waitqueue_head(&fhp->wait_data);
+	fhp->dev_info = dip;
+
+	pvr2_context_enter(vp->channel.mc_head); do {
+		pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp);
+		pvr2_channel_init(&fhp->channel,vp->channel.mc_head);
+		fhp->vnext = 0;
+		fhp->vprev = vp->vlast;
+		if (vp->vlast) {
+			vp->vlast->vnext = fhp;
+		} else {
+			vp->vfirst = fhp;
+		}
+		vp->vlast = fhp;
+		fhp->vhead = vp;
+	} while (0); pvr2_context_exit(vp->channel.mc_head);
+
+	fhp->file = file;
+	file->private_data = fhp;
+	v4l2_prio_open(&vp->prio,&fhp->prio);
+
+	fhp->fw_mode_flag = pvr2_hdw_cpufw_get_enabled(hdw);
+
+	return 0;
+}
+
+
+static void pvr2_v4l2_notify(struct pvr2_v4l2_fh *fhp)
+{
+	wake_up(&fhp->wait_data);
+}
+
+static int pvr2_v4l2_iosetup(struct pvr2_v4l2_fh *fh)
+{
+	int ret;
+	struct pvr2_stream *sp;
+	struct pvr2_hdw *hdw;
+	if (fh->rhp) return 0;
+
+	/* First read() attempt.  Try to claim the stream and start
+	   it... */
+	if ((ret = pvr2_channel_claim_stream(&fh->channel,
+					     fh->dev_info->stream)) != 0) {
+		/* Someone else must already have it */
+		return ret;
+	}
+
+	fh->rhp = pvr2_channel_create_mpeg_stream(fh->dev_info->stream);
+	if (!fh->rhp) {
+		pvr2_channel_claim_stream(&fh->channel,0);
+		return -ENOMEM;
+	}
+
+	hdw = fh->channel.mc_head->hdw;
+	sp = fh->dev_info->stream->stream;
+	pvr2_stream_set_callback(sp,(pvr2_stream_callback)pvr2_v4l2_notify,fh);
+	pvr2_hdw_set_stream_type(hdw,fh->dev_info->config);
+	pvr2_hdw_set_streaming(hdw,!0);
+	ret = pvr2_ioread_set_enabled(fh->rhp,!0);
+
+	return ret;
+}
+
+
+static ssize_t pvr2_v4l2_read(struct file *file,
+			      char __user *buff, size_t count, loff_t *ppos)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	int ret;
+
+	if (fh->fw_mode_flag) {
+		struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+		char *tbuf;
+		int c1,c2;
+		int tcnt = 0;
+		unsigned int offs = *ppos;
+
+		tbuf = kmalloc(PAGE_SIZE,GFP_KERNEL);
+		if (!tbuf) return -ENOMEM;
+
+		while (count) {
+			c1 = count;
+			if (c1 > PAGE_SIZE) c1 = PAGE_SIZE;
+			c2 = pvr2_hdw_cpufw_get(hdw,offs,tbuf,c1);
+			if (c2 < 0) {
+				tcnt = c2;
+				break;
+			}
+			if (!c2) break;
+			if (copy_to_user(buff,tbuf,c2)) {
+				tcnt = -EFAULT;
+				break;
+			}
+			offs += c2;
+			tcnt += c2;
+			buff += c2;
+			count -= c2;
+			*ppos += c2;
+		}
+		kfree(tbuf);
+		return tcnt;
+	}
+
+	if (!fh->rhp) {
+		ret = pvr2_v4l2_iosetup(fh);
+		if (ret) {
+			return ret;
+		}
+	}
+
+	for (;;) {
+		ret = pvr2_ioread_read(fh->rhp,buff,count);
+		if (ret >= 0) break;
+		if (ret != -EAGAIN) break;
+		if (file->f_flags & O_NONBLOCK) break;
+		/* Doing blocking I/O.  Wait here. */
+		ret = wait_event_interruptible(
+			fh->wait_data,
+			pvr2_ioread_avail(fh->rhp) >= 0);
+		if (ret < 0) break;
+	}
+
+	return ret;
+}
+
+
+static unsigned int pvr2_v4l2_poll(struct file *file, poll_table *wait)
+{
+	unsigned int mask = 0;
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	int ret;
+
+	if (fh->fw_mode_flag) {
+		mask |= POLLIN | POLLRDNORM;
+		return mask;
+	}
+
+	if (!fh->rhp) {
+		ret = pvr2_v4l2_iosetup(fh);
+		if (ret) return POLLERR;
+	}
+
+	poll_wait(file,&fh->wait_data,wait);
+
+	if (pvr2_ioread_avail(fh->rhp) >= 0) {
+		mask |= POLLIN | POLLRDNORM;
+	}
+
+	return mask;
+}
+
+
+static struct file_operations vdev_fops = {
+	.owner      = THIS_MODULE,
+	.open       = pvr2_v4l2_open,
+	.release    = pvr2_v4l2_release,
+	.read       = pvr2_v4l2_read,
+	.ioctl      = pvr2_v4l2_ioctl,
+	.llseek     = no_llseek,
+	.poll       = pvr2_v4l2_poll,
+};
+
+
+#define VID_HARDWARE_PVRUSB2    38  /* FIXME : need a good value */
+
+static struct video_device vdev_template = {
+	.owner      = THIS_MODULE,
+	.type       = VID_TYPE_CAPTURE | VID_TYPE_TUNER,
+	.type2      = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE
+		       | V4L2_CAP_TUNER | V4L2_CAP_AUDIO
+		       | V4L2_CAP_READWRITE),
+	.hardware   = VID_HARDWARE_PVRUSB2,
+	.fops       = &vdev_fops,
+};
+
+
+static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip,
+			       struct pvr2_v4l2 *vp,
+			       enum pvr2_config cfg)
+{
+	int mindevnum;
+	int unit_number;
+	int v4l_type;
+	dip->v4lp = vp;
+	dip->config = cfg;
+
+
+	switch (cfg) {
+	case pvr2_config_mpeg:
+		v4l_type = VFL_TYPE_GRABBER;
+		dip->stream = &vp->channel.mc_head->video_stream;
+		break;
+	case pvr2_config_vbi:
+		v4l_type = VFL_TYPE_VBI;
+		break;
+	case pvr2_config_radio:
+		v4l_type = VFL_TYPE_RADIO;
+		break;
+	default:
+		/* Bail out (this should be impossible) */
+		err("Failed to set up pvrusb2 v4l dev"
+		    " due to unrecognized config");
+		return;
+	}
+
+	if (!dip->stream) {
+		err("Failed to set up pvrusb2 v4l dev"
+		    " due to missing stream instance");
+		return;
+	}
+
+	dip->vdev = video_device_alloc();
+	if (!dip->vdev) {
+		err("Alloc of pvrusb2 v4l video device failed");
+		return;
+	}
+
+	memcpy(dip->vdev,&vdev_template,sizeof(vdev_template));
+	dip->vdev->release = video_device_release;
+	mutex_lock(&device_lock);
+
+	mindevnum = -1;
+	unit_number = pvr2_hdw_get_unit_number(vp->channel.mc_head->hdw);
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		mindevnum = video_nr[unit_number];
+	}
+	if ((video_register_device(dip->vdev, v4l_type, mindevnum) < 0) &&
+	    (video_register_device(dip->vdev, v4l_type, -1) < 0)) {
+		err("Failed to register pvrusb2 v4l video device");
+	} else {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "registered device video%d [%s]",
+			   dip->vdev->minor,pvr2_config_get_name(dip->config));
+	}
+
+	if ((dip->vdev->minor < sizeof(devices)/sizeof(devices[0])) &&
+	    (devices[dip->vdev->minor] == NULL)) {
+		dip->ctxt_idx = dip->vdev->minor;
+		devices[dip->ctxt_idx] = dip;
+	}
+	mutex_unlock(&device_lock);
+
+	pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,
+					dip->vdev->minor);
+}
+
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp)
+{
+	struct pvr2_v4l2 *vp;
+
+	vp = kmalloc(sizeof(*vp),GFP_KERNEL);
+	if (!vp) return vp;
+	memset(vp,0,sizeof(*vp));
+	vp->video_dev.ctxt_idx = -1;
+	pvr2_channel_init(&vp->channel,mnp);
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp);
+
+	vp->channel.check_func = pvr2_v4l2_internal_check;
+
+	/* register streams */
+	pvr2_v4l2_dev_init(&vp->video_dev,vp,pvr2_config_mpeg);
+
+
+	return vp;
+}
+
+/*
+  Stuff for Emacs to see, in order to encourage consistent editing style:
+  *** Local Variables: ***
+  *** mode: c ***
+  *** fill-column: 75 ***
+  *** tab-width: 8 ***
+  *** c-basic-offset: 8 ***
+  *** End: ***
+  */