/*
 *	Video for Linux Two
 *
 *	A generic video device interface for the LINUX operating system
 *	using a set of device structures/vectors for low level operations.
 *
 *	This file replaces the videodev.c file that comes with the
 *	regular kernel distribution.
 *
 *	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.
 *
 * Author:	Bill Dirks <dirks@rendition.com>
 *		based on code by Alan Cox, <alan@cymru.net>
 *
 *	gcc -c -O2 -Wall -DMODULE videodev.c
 */

#ifndef __KERNEL__
#define __KERNEL__
#endif

#include <linux/config.h>
#ifndef EXPORT_SYMTAB
#define EXPORT_SYMTAB
#endif
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/string.h>
#include <linux/errno.h>
#include "videodev2.h"
#include <linux/proc_fs.h>

#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/pgtable.h>
#include <asm/io.h>

#ifdef CONFIG_KMOD
#include <linux/kmod.h>
#endif

#ifndef min
#define min(a,b) (((a)<(b))?(a):(b))
#endif

static int v4l2_major		= 81;
MODULE_PARM(v4l2_major, "i");

#define V4L2_NUM_DEVICES	256 

/*
 *	Active devices 
 */
static struct v4l2_device *v4l2_device[V4L2_NUM_DEVICES];

static struct v4l2_clock *masterclock;


/*
 *	D E V I C E   O P E R A T I O N S
 *
 */

/*
 *	Open a video device.
 */
static int
video_open(struct inode *inode, struct file *file)
{
	unsigned int		minor = MINOR(inode->i_rdev);
	struct v4l2_device	*vfl;
	int			err;

	if (minor >= V4L2_NUM_DEVICES)
		return -ENODEV;
	vfl = v4l2_device[minor];
	if (vfl == NULL)
	{
#ifdef CONFIG_KMOD	/*  KMOD code by Erik Walthinsen  */
		char modname[20];

		sprintf(modname, "char-major-%d-%d", v4l2_major, minor);
		request_module(modname);
		vfl = v4l2_device[minor];
		if (vfl == NULL)
#endif
			return -ENODEV;
	}
	if (vfl->open == NULL)
		return -ENODEV;
	err = vfl->open(vfl, file->f_flags, &file->private_data);
	if (err == 0 && file->private_data == NULL)
	{
		printk(KERN_ERR"V4L2: Device returned NULL open id\n");
		err = -ENODEV;
	}
	if (err == 0)
		++vfl->busy;
	return err;
}

/*
 *	Last close of a struct file
 */	
static int
video_release(struct inode *inode, struct file *file)
{
	struct v4l2_device *vfl = v4l2_device[MINOR(inode->i_rdev)];
	if (vfl->close)
		vfl->close(file->private_data);
	file->private_data = NULL;
	if (vfl->busy)
		--vfl->busy;
	return 0;
}

/*
 *	Read from a video device
 */
static ssize_t
video_read(struct file *file,
	   char *buf, size_t count, loff_t *ppos)
{
	struct v4l2_device *vfl =
		v4l2_device[MINOR(file->f_dentry->d_inode->i_rdev)];
	if (vfl->read)
		return vfl->read(file->private_data, buf, count,
				 file->f_flags & O_NONBLOCK);
	return -EINVAL;
}

/*
 *	Write to a video device
 */
static ssize_t
video_write(struct file *file,
	    const char *buf, size_t count, loff_t *ppos)
{
	struct v4l2_device *vfl =
		v4l2_device[MINOR(file->f_dentry->d_inode->i_rdev)];
	if (vfl->write)
		return vfl->write(file->private_data, buf, count,
				  file->f_flags & O_NONBLOCK);
	return 0;
}

/*
 *	IO Control
 */
static void
fill_ctrl_category(struct v4l2_queryctrl *qc);

static int
translate_ioctl(struct v4l2_device	*vfl,
		void			*per_open_data,
		int			cmd,
		void			*arg);

static int
video_ioctl(struct inode *inode, struct file *file,
	    unsigned int cmd, unsigned long arg)
{
	struct v4l2_device *vfl = v4l2_device[MINOR(inode->i_rdev)];
	char	targ[V4L2_MAX_IOCTL_SIZE];
	void	*parg	= (void *)arg;
	int	err	= -EINVAL;

	if (vfl->ioctl == NULL)
		return -EINVAL;

	/*  Copy arguments into temp kernel buffer  */
	switch (_IOC_DIR(cmd))
	{
	case _IOC_NONE:
		parg = (void *)arg;
		break;
	case _IOC_WRITE:
	case (_IOC_WRITE | _IOC_READ):
		if (_IOC_SIZE(cmd) > sizeof(targ))
			break;/*  Arguments are too big.  */
		if (copy_from_user(targ, (void *)arg, _IOC_SIZE(cmd)))
			return -EFAULT;
		parg = targ;
		break;
	case _IOC_READ:
		parg = targ;
		break;
	}

	/*  Fill in the category for pre-defined controls  */
	if (cmd == VIDIOC_QUERYCTRL)
		fill_ctrl_category((struct v4l2_queryctrl *)parg);

	/*  Try passing it to the driver first  */
	err = vfl->ioctl(file->private_data, cmd, parg);

	/*  If the driver doesn't recognize it and it's an old ioctl,
	    pass it through the translation layer.  */
	if (err == -ENOIOCTLCMD && _IOC_TYPE(cmd) == 'v')
	{
		err = translate_ioctl(vfl, file->private_data, 
				      cmd, parg);
	}

	/*  Copy results into user buffer  */
	switch (_IOC_DIR(cmd))
	{
	case _IOC_READ:
	case (_IOC_WRITE | _IOC_READ):
		if (parg == targ &&
		    copy_to_user((void *)arg, parg, _IOC_SIZE(cmd)))
			return -EFAULT;
		break;
	}

	if (err != -ENOIOCTLCMD)
		return err;

	/*  Handle ioctls not recognized by the driver  */
	return -EINVAL;
}

/*
 *	Memory mapping
 */ 
static int
video_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct v4l2_device *vfl =
		v4l2_device[MINOR(file->f_dentry->d_inode->i_rdev)];
	int	err;
	if (vfl->mmap)
	{
		vma->vm_file = file;
		err = vfl->mmap(file->private_data, vma);
		if (err == 0 && vma->vm_file != NULL)
			++vma->vm_file->f_count;
		return err;
	}
	return -ENODEV;
}

/*
 *	Poll (select()) support
 */
static unsigned int
video_poll(struct file *file, poll_table *table)
{
	struct v4l2_device *vfl =
		v4l2_device[MINOR(file->f_dentry->d_inode->i_rdev)];
	if (vfl->poll)
		return vfl->poll(file->private_data, file, table);
	return POLLERR;
}

/*
 *	Not used.
 */ 
static long long
video_lseek(struct file *file,
	    long long offset, int origin)
{
	return -ESPIPE;
}

/*
 *	CONTROL CATEGORIES
 */

static void
fill_ctrl_category(struct v4l2_queryctrl *qc)
{
	if ((qc->id >= V4L2_CID_BRIGHTNESS &&
	     qc->id <= V4L2_CID_WHITENESS) ||
	    (qc->id >= V4L2_CID_BLACK_LEVEL &&
	     qc->id <= V4L2_CID_AUTOGAIN))
	{
		qc->category = V4L2_CTRL_CAT_VIDEO;
		strcpy(qc->catname, "Video");
	}
	if ((qc->id >= V4L2_CID_AUDIO_VOLUME &&
	     qc->id <= V4L2_CID_AUDIO_LOUDNESS))
	{
		qc->category = V4L2_CTRL_CAT_AUDIO;
		strcpy(qc->catname, "Audio");
	}
}

/*
 *	B A C K W A R D   C O M P A T I B I L I T Y
 *
 *	This code allows applications written for the original API to
 *	work with drivers that only understand the new API.
 */

static int
get_v4l_control(struct v4l2_device	*vfl,
		void			*context,
		int			cid)
{
	struct v4l2_queryctrl	qctrl2;
	struct v4l2_control	ctrl2;
	int			err;

	qctrl2.id = cid;
	err = vfl->ioctl(context, VIDIOC_QUERYCTRL, &qctrl2);
	if (err == 0)
	{
		ctrl2.id = qctrl2.id;
		vfl->ioctl(context, VIDIOC_G_CTRL, &ctrl2);
		return ((ctrl2.value - qctrl2.minimum) * 65535
			 + (qctrl2.maximum - qctrl2.minimum) / 2)
			/ (qctrl2.maximum - qctrl2.minimum);
	}
	return 0;
}

static int
set_v4l_control(struct v4l2_device	*vfl,
		void			*context,
		int			cid,
		int			value)
{
	struct v4l2_queryctrl	qctrl2;
	struct v4l2_control	ctrl2;
	int			err;

	qctrl2.id = cid;
	err = vfl->ioctl(context, VIDIOC_QUERYCTRL, &qctrl2);
	if (err == 0)
	{
		if (value < 0)
			value = 0;
		if (value > 65535)
			value = 65535;
		ctrl2.id = qctrl2.id;
		ctrl2.value = 
			(value * (qctrl2.maximum - qctrl2.minimum)
			 + 32767)
			/ 65535;
		vfl->ioctl(context, VIDIOC_S_CTRL, &ctrl2);
	}
	return 0;
}

static int
find_tuner(struct v4l2_device	*vfl,
	   void			*context,
	   int			n)
{
	struct v4l2_input	inp2;
	int	i;
	int	err;

	/*  Find the input number of the n'th tuner  */
	for (i = 0; i < 100/*arbitrary*/; ++i)
	{
		inp2.index = i;
		err = vfl->ioctl(context, VIDIOC_ENUMINPUT, &inp2);
		if (err < 0)
			break;
		if (inp2.type != V4L2_INPUT_TYPE_TUNER)
			continue;
		if (n == 0)
			break;
		--n;
	}
	if (err < 0)
		return err;
	return i;
}


static int
translate_ioctl(struct v4l2_device	*vfl,
		void			*context,
		int			cmd,
		void			*arg)
{
	int	err	= -ENOIOCTLCMD;

	switch (cmd)
	{
	case VIDIOCGCAP:	/* capability */
	{
		struct video_capability *cap = arg;
		struct v4l2_capability cap2;
		struct v4l2_framebuffer fbuf2;

		err = vfl->ioctl(context, VIDIOC_QUERYCAP, &cap2);
		if (err < 0)
			break;
		if (cap2.flags & V4L2_FLAG_PREVIEW)
		{
			err = vfl->ioctl(context, VIDIOC_G_FBUF, &fbuf2);
			if (err < 0)
				memset(&fbuf2, 0, sizeof(fbuf2));
			err = 0;
		}
		memset(cap, 0, sizeof(cap));
		memcpy(cap->name, cap2.name, 
		       min(sizeof(cap->name), sizeof(cap2.name)));
		cap->name[sizeof(cap->name) - 1] = 0;
		if (cap2.type == V4L2_TYPE_CAPTURE)
			cap->type = VID_TYPE_CAPTURE;
		if (cap2.flags & V4L2_FLAG_TUNER)
			cap->type |= VID_TYPE_TUNER;
		if (cap2.flags & V4L2_FLAG_DATA_SERVICE)
			cap->type |= VID_TYPE_TELETEXT;
		if (cap2.flags & V4L2_FLAG_PREVIEW)
			cap->type |= VID_TYPE_OVERLAY;
		if (cap2.flags & V4L2_FLAG_MONOCHROME)
			cap->type |= VID_TYPE_MONOCHROME;
		if (fbuf2.flags & V4L2_FBUF_FLAG_PRIMARY)
			cap->type |= VID_TYPE_FRAMERAM;
		if (fbuf2.capability & V4L2_FBUF_CAP_CHROMAKEY)
			cap->type |= VID_TYPE_CHROMAKEY;
		if (fbuf2.capability & V4L2_FBUF_CAP_CLIPPING)
			cap->type |= VID_TYPE_CLIPPING;
		if (fbuf2.capability & V4L2_FBUF_CAP_SCALEUP ||
		    fbuf2.capability & V4L2_FBUF_CAP_SCALEDOWN)
			cap->type |= VID_TYPE_SCALES;
		cap->channels  = cap2.inputs;
		cap->audios    = cap2.audios;
		cap->maxwidth  = cap2.maxwidth;
		cap->maxheight = cap2.maxheight;
		cap->minwidth  = cap2.minwidth;
		cap->minheight = cap2.minheight;
		break;
	}
	case VIDIOCGFBUF: /*  get frame buffer  */
	{
		struct video_buffer	*buffer = arg;
		struct v4l2_framebuffer	fbuf2;

		err = vfl->ioctl(context, VIDIOC_G_FBUF, &fbuf2);
		if (err < 0)
			break;
		buffer->base = fbuf2.base[0];
		buffer->height = fbuf2.fmt.height;
		buffer->width = fbuf2.fmt.width;
		buffer->depth = fbuf2.fmt.depth;
		if (fbuf2.fmt.flags & V4L2_FMT_FLAG_BYTESPERLINE)
			buffer->bytesperline = fbuf2.fmt.bytesperline;
		else
		{
			buffer->bytesperline = 
				(fbuf2.fmt.width * fbuf2.fmt.depth + 7) & 7;
			buffer->bytesperline >>= 3;
		}
		if (fbuf2.fmt.pixelformat == V4L2_PIX_FMT_RGB555)
			buffer->depth = 15;
		break;
	}
	case VIDIOCSFBUF: /*  set frame buffer  */
	{
		struct video_buffer	*buffer = arg;
		struct v4l2_framebuffer	fbuf2;

		memset(&fbuf2, 0, sizeof(fbuf2));
		fbuf2.base[0] = buffer->base;
		fbuf2.fmt.height = buffer->height;
		fbuf2.fmt.width = buffer->width;
		fbuf2.fmt.depth = buffer->depth;
		switch (fbuf2.fmt.depth)
		{
		case 8:
			fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB332;
			break;
		case 15:
			fbuf2.fmt.depth = 16;
			fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB555;
			break;
		case 16:
			fbuf2.fmt.pixelformat = V4L2_PIX_FMT_RGB565;
			break;
		case 24:
			fbuf2.fmt.pixelformat = V4L2_PIX_FMT_BGR24;
			break;
		case 32:
			fbuf2.fmt.pixelformat = V4L2_PIX_FMT_BGR32;
			break;
		}
		fbuf2.fmt.flags |= V4L2_FMT_FLAG_BYTESPERLINE;
		fbuf2.fmt.bytesperline = buffer->bytesperline;
		fbuf2.flags = V4L2_FBUF_FLAG_PRIMARY;
		err = vfl->ioctl(context, VIDIOC_S_FBUF, &fbuf2);
		break;
	}
	case VIDIOCGWIN: /*  get window or capture dimensions  */
	{
		struct video_window	*win = arg;
		struct v4l2_window	win2;
		struct v4l2_format	fmt2;

		err = vfl->ioctl(context, VIDIOC_G_WIN, &win2);
		if (err == 0)
		{
			win->x = win2.x;
			win->y = win2.y;
			win->width = win2.width;
			win->height = win2.height;
			win->chromakey = win2.chromakey;
			win->clips = NULL;
			win->clipcount = 0;
			break;
		}
		err = vfl->ioctl(context, VIDIOC_G_FMT, &fmt2);
		if (err < 0)
			break;
		win->x = 0;
		win->y = 0;
		win->width = fmt2.width;
		win->height = fmt2.height;
		win->chromakey = 0;
		win->clips = NULL;
		win->clipcount = 0;
		break;
	}
	case VIDIOCSWIN: /*  set window and/or capture dimensions  */
	{
		struct video_window	*win = arg;
		struct v4l2_window	win2;
		struct v4l2_format	fmt2;

		err = vfl->ioctl(context, VIDIOC_G_FMT, &fmt2);
		if (err == 0)
		{
			fmt2.width = win->width;
			fmt2.height = win->height;
			err = vfl->ioctl(context, VIDIOC_S_FMT, &fmt2);
			win->width = fmt2.width;
			win->height = fmt2.height;
		}
		win2.x = win->x;
		win2.y = win->y;
		win2.width = win->width;
		win2.height = win->height;
		win2.chromakey = win->chromakey;
		win2.clips = (void *)win->clips;
		win2.clipcount = win->clipcount;
		vfl->ioctl(context, VIDIOC_S_WIN, &win2);
		break;
	}
	case VIDIOCCAPTURE: /*  turn on/off preview  */
	{
		err = vfl->ioctl(context, VIDIOC_PREVIEW, arg);
		break;
	}
	case VIDIOCGCHAN: /*  get input information  */
	{
		struct video_channel	*chan = arg;
		struct v4l2_input	input2;
		struct v4l2_standard	std2;
		int			sid;

		input2.index = chan->channel;
		err = vfl->ioctl(context, VIDIOC_ENUMINPUT, &input2);
		if (err < 0)
			break;
		chan->channel = input2.index;
		memcpy(chan->name, input2.name,
		       min(sizeof(chan->name), sizeof(input2.name)));
		chan->name[sizeof(chan->name) - 1] = 0;
		chan->tuners = (input2.type == V4L2_INPUT_TYPE_TUNER) ? 1 : 0;
		chan->flags = (chan->tuners) ? VIDEO_VC_TUNER : 0;
		if (input2.capability & V4L2_INPUT_CAP_AUDIO)
			chan->flags |= VIDEO_VC_AUDIO;
		switch (input2.type)
		{
		case V4L2_INPUT_TYPE_TUNER:
			chan->type = VIDEO_TYPE_TV;
			break;
		default:
		case V4L2_INPUT_TYPE_CAMERA:
			chan->type = VIDEO_TYPE_CAMERA;
			break;
		}
		chan->norm = 0;
		err = vfl->ioctl(context, VIDIOC_G_STD, &std2);
		if (err == 0)
		{
			sid = v4l2_video_std_confirm(&std2);
			switch (sid)
			{
			case V4L2_STD_NTSC:
				chan->norm = VIDEO_MODE_NTSC;
				break;
			case V4L2_STD_PAL:
				chan->norm = VIDEO_MODE_PAL;
				break;
			case V4L2_STD_SECAM:
				chan->norm = VIDEO_MODE_SECAM;
				break;
			}
		}
		break;
	}
	case VIDIOCSCHAN: /*  set input  */
	{
		err = vfl->ioctl(context, VIDIOC_S_INPUT, arg);
		break;
	}
	case VIDIOCGPICT: /*  get tone controls & partial capture format  */
	{
		struct video_picture	*pict = arg;
		struct v4l2_format	fmt2;

		pict->brightness = get_v4l_control(vfl, context, 
						   V4L2_CID_BRIGHTNESS);
		pict->hue = get_v4l_control(vfl, context, 
					    V4L2_CID_HUE);
		pict->contrast = get_v4l_control(vfl, context, 
						 V4L2_CID_CONTRAST);
		pict->colour = get_v4l_control(vfl, context, 
					       V4L2_CID_SATURATION);
		pict->whiteness = get_v4l_control(vfl, context, 
						  V4L2_CID_WHITENESS);
		err = vfl->ioctl(context, VIDIOC_G_FMT, &fmt2);
		if (err < 0)
			break;
		pict->depth = fmt2.depth;
		switch (fmt2.pixelformat)
		{
		case V4L2_PIX_FMT_GREY:
			pict->palette = VIDEO_PALETTE_GREY;
			break;
		case V4L2_PIX_FMT_RGB555:
			pict->palette = VIDEO_PALETTE_RGB555;
			pict->depth = 15;
			break;
		case V4L2_PIX_FMT_RGB565:
			pict->palette = VIDEO_PALETTE_RGB565;
			pict->depth = 16;
			break;
		case V4L2_PIX_FMT_BGR24:
			pict->palette = VIDEO_PALETTE_RGB24;
			break;
		case V4L2_PIX_FMT_BGR32:
			pict->palette = VIDEO_PALETTE_RGB32;
			break;
		case V4L2_PIX_FMT_YUYV:
			pict->palette = VIDEO_PALETTE_YUYV;
			break;
		case V4L2_PIX_FMT_UYVY:
			pict->palette = VIDEO_PALETTE_UYVY;
			break;
		case V4L2_PIX_FMT_YUV420:
			pict->palette = VIDEO_PALETTE_YUV420;
			break;
		case V4L2_PIX_FMT_YUV422P:
			pict->palette = VIDEO_PALETTE_YUV422P;
			break;
		case V4L2_PIX_FMT_YUV411P:
			pict->palette = VIDEO_PALETTE_YUV411P;
			break;
		}
		break;
	}
	case VIDIOCSPICT: /*  set tone controls & partial capture format  */
	{
		struct video_picture	*pict = arg;
		struct v4l2_format	fmt2;

		set_v4l_control(vfl, context,
				V4L2_CID_BRIGHTNESS, pict->brightness);
		set_v4l_control(vfl, context,
				V4L2_CID_HUE, pict->hue);
		set_v4l_control(vfl, context,
				V4L2_CID_CONTRAST, pict->contrast);
		set_v4l_control(vfl, context,
				V4L2_CID_SATURATION, pict->colour);
		set_v4l_control(vfl, context,
				V4L2_CID_WHITENESS, pict->whiteness);
		vfl->ioctl(context, VIDIOC_G_FMT, &fmt2);
		switch (pict->palette)
		{
		case VIDEO_PALETTE_GREY:
			fmt2.pixelformat = V4L2_PIX_FMT_GREY;
			break;
		case VIDEO_PALETTE_RGB555:
			fmt2.pixelformat = V4L2_PIX_FMT_RGB555;
			break;
		case VIDEO_PALETTE_RGB565:
			fmt2.pixelformat = V4L2_PIX_FMT_RGB565;
			break;
		case VIDEO_PALETTE_RGB24:
			fmt2.pixelformat = V4L2_PIX_FMT_RGB24;
			break;
		case VIDEO_PALETTE_RGB32:
			fmt2.pixelformat = V4L2_PIX_FMT_RGB32;
			break;
		case VIDEO_PALETTE_YUYV:
			fmt2.pixelformat = V4L2_PIX_FMT_YUYV;
			break;
		case VIDEO_PALETTE_UYVY:
			fmt2.pixelformat = V4L2_PIX_FMT_UYVY;
			break;
		case VIDEO_PALETTE_YUV420:
			fmt2.pixelformat = V4L2_PIX_FMT_YUV420;
			break;
		case VIDEO_PALETTE_YUV422P:
			fmt2.pixelformat = V4L2_PIX_FMT_YUV422P;
			break;
		case VIDEO_PALETTE_YUV411P:
			fmt2.pixelformat = V4L2_PIX_FMT_YUV411P;
			break;
		}
		err = vfl->ioctl(context, VIDIOC_S_FMT, &fmt2);
		break;
	}
	case VIDIOCGTUNER: /*  get tuner information  */
	{
		struct video_tuner	*tun = arg;
		struct v4l2_tuner	tun2;
		int			i;
		int			sid;

		i = find_tuner(vfl, context, tun->tuner);
		if (i < 0)
		{
			err = i;
			break;
		}
		tun2.input = i;
		err = vfl->ioctl(context, VIDIOC_G_TUNER, &tun2);
		if (err < 0)
			break;
		memcpy(tun->name, tun2.name,
		       min(sizeof(tun->name), sizeof(tun2.name)));
		tun->name[sizeof(tun->name) - 1] = 0;
		tun->rangelow = tun2.rangelow;
		tun->rangehigh = tun2.rangehigh;
		tun->flags = 0;
		tun->mode = VIDEO_MODE_AUTO;
		sid = v4l2_video_std_confirm(&tun2.std);
		switch (sid)
		{
		case V4L2_STD_NTSC:
			tun->flags = VIDEO_TUNER_NTSC;
			tun->mode = VIDEO_MODE_NTSC;
			break;
		case V4L2_STD_PAL:
			tun->flags = VIDEO_TUNER_PAL;
			tun->mode = VIDEO_MODE_PAL;
			break;
		case V4L2_STD_SECAM:
			tun->flags = VIDEO_TUNER_SECAM;
			tun->mode = VIDEO_MODE_SECAM;
			break;
		}
		if (tun2.capability & V4L2_TUNER_CAP_LOW)
			tun->flags |= VIDEO_TUNER_LOW;
		if (tun2.rxsubchans & V4L2_TUNER_SUB_STEREO)
			tun->flags |= VIDEO_TUNER_STEREO_ON;
		tun->signal = tun2.signal;
		break;
	}
	case VIDIOCSTUNER: /*  select a tuner input  */
	{
		int	i;

		i = find_tuner(vfl, context, (int)arg);
		if (i < 0)
		{
			err = i;
			break;
		}
		err = vfl->ioctl(context, VIDIOC_S_INPUT, (void *)i);
		break;
	}
	case VIDIOCGFREQ: /*  get frequency  */
	{
		err = vfl->ioctl(context, VIDIOC_G_FREQ, arg);
		break;
	}
	case VIDIOCSFREQ: /*  set frequency  */
	{
		err = vfl->ioctl(context, VIDIOC_S_FREQ, arg);
		break;
	}
	case VIDIOCGAUDIO: /*  get audio properties/controls  */
	{
		struct video_audio	*aud = arg;
		struct v4l2_audio	aud2;
		struct v4l2_queryctrl	qctrl2;
		struct v4l2_tuner	tun2;
		int			v;

		err = vfl->ioctl(context, VIDIOC_G_AUDIO, &aud2);
		if (err < 0)
			break;
		memcpy(aud->name, aud2.name,
		       min(sizeof(aud->name), sizeof(aud2.name)));
		aud->name[sizeof(aud->name) - 1] = 0;
		aud->audio = aud2.audio;
		aud->flags = 0;
		v = get_v4l_control(vfl, context, V4L2_CID_AUDIO_VOLUME);
		if (v >= 0)
		{
			aud->volume = v;
			aud->flags |= VIDEO_AUDIO_VOLUME;
		}
		v = get_v4l_control(vfl, context, V4L2_CID_AUDIO_BASS);
		if (v >= 0)
		{
			aud->bass = v;
			aud->flags |= VIDEO_AUDIO_BASS;
		}
		v = get_v4l_control(vfl, context, V4L2_CID_AUDIO_TREBLE);
		if (v >= 0)
		{
			aud->treble = v;
			aud->flags |= VIDEO_AUDIO_TREBLE;
		}
		v = get_v4l_control(vfl, context, V4L2_CID_AUDIO_BALANCE);
		if (v >= 0)
		{
			aud->balance = v;
			aud->flags |= VIDEO_AUDIO_BALANCE;
		}
		v = get_v4l_control(vfl, context, V4L2_CID_AUDIO_MUTE);
		if (v >= 0)
		{
			if (v)
				aud->flags |= VIDEO_AUDIO_MUTE;
			aud->flags |= VIDEO_AUDIO_MUTABLE;
		}
		aud->step = 1;
		qctrl2.id = V4L2_CID_AUDIO_VOLUME;
		if (vfl->ioctl(context, VIDIOC_QUERYCTRL, &qctrl2) == 0)
			aud->step = qctrl2.step;
		aud->mode = 0;
		err = vfl->ioctl(context, VIDIOC_G_TUNER, &tun2);
		if (err < 0)
		{
			err = 0;
			break;
		}
		switch (tun2.audmode)
		{
		case V4L2_TUNER_MODE_MONO:
			aud->mode = VIDEO_SOUND_MONO;
			break;
		case V4L2_TUNER_MODE_STEREO:
			aud->mode = VIDEO_SOUND_STEREO;
			break;
		case V4L2_TUNER_MODE_LANG2:
			aud->mode = VIDEO_SOUND_LANG2;
			break;
		}
		break;
	}
	case VIDIOCSAUDIO: /*  set audio controls  */
	{
		struct video_audio	*aud = arg;
		struct v4l2_audio	aud2;
		struct v4l2_tuner	tun2;
		int			i;

		aud2.audio = aud2.audio;
		err = vfl->ioctl(context, VIDIOC_S_AUDIO, &aud2);
		if (err < 0)
			break;

		set_v4l_control(vfl, context, V4L2_CID_AUDIO_VOLUME, 
				aud->volume);
		set_v4l_control(vfl, context, V4L2_CID_AUDIO_BASS,
				aud->bass);
		set_v4l_control(vfl, context, V4L2_CID_AUDIO_TREBLE,
				aud->treble);
		set_v4l_control(vfl, context, V4L2_CID_AUDIO_BALANCE,
				aud->balance);
		set_v4l_control(vfl, context, V4L2_CID_AUDIO_MUTE,
				!!(aud->flags & VIDEO_AUDIO_MUTE));

		err = vfl->ioctl(context, VIDIOC_G_INPUT, &i);
		if (err < 0)
		{
			err = 0;
			break;
		}
		tun2.input = i;
		err = vfl->ioctl(context, VIDIOC_G_TUNER, &tun2);
		if (err == 0)
		{
			switch (aud->mode)
			{
			default:
			case VIDEO_SOUND_MONO:
				tun2.audmode = V4L2_TUNER_MODE_MONO;
				break;
			case VIDEO_SOUND_STEREO:
				tun2.audmode = V4L2_TUNER_MODE_STEREO;
				break;
			case VIDEO_SOUND_LANG2:
				tun2.audmode = V4L2_TUNER_MODE_LANG2;
				break;
			}
			vfl->ioctl(context, VIDIOC_S_TUNER, &tun2);
		}
		err = 0;
		break;
	}
	case VIDIOCGMBUF: /*  get mmap parameters  */
	{
		//struct video_mbuf	*mbuf = arg;
		break;
	}
	case VIDIOCMCAPTURE: /*  capture a frame  */
	{
		//struct video_mmap	*mm = arg;
		break;
	}
	case VIDIOCSYNC: /*  wait for a frame  */
	{
		break;
	}
	case VIDIOCGUNIT: /*  get related device minors  */
		/*  No translation  */
		break;
	case VIDIOCGCAPTURE: /*    */
		/*  No translation, yet...  */
		break;
	case VIDIOCSCAPTURE: /*    */
		/*  No translation, yet...  */
		break;
	}
	return err;
}


/*
 *	D E V I C E   R E G I S T R A T I O N
 *
 *	Video for Linux Two device drivers request registration here.
 */
int
v4l2_register_device(struct v4l2_device *vfl)
{
	int	i	= 0;
	int	err;

	if (vfl == NULL)
	{
		printk(KERN_ERR"V4L2: v4l2_register_device() passed"
		       " a NULL pointer!\n");
		return -1;
	}
	i = vfl->minor;
	if (vfl->open == NULL)
	{
		printk(KERN_ERR "V4L2: Device %d has no open method\n", i);
		return -1;
	}
	if (i < 0 || i >= V4L2_NUM_DEVICES)
	{
		printk(KERN_ERR"V4L2: Minor value %d is out of range\n",
		       i);
		return -1;
	}
	if (v4l2_device[i] != NULL)
	{
		printk(KERN_ERR"V4L2: %s and %s have both been assigned"
		       " minor %d\n", v4l2_device[i]->name,
		       vfl->name, i);
		return 1;
	}

	v4l2_device[i] = vfl;
	/* The init call may sleep so we book the slot out then call */
	MOD_INC_USE_COUNT;
	err = 0;
	if (vfl->initialize)
		err = vfl->initialize(vfl);
	if (err < 0)
	{
		printk(KERN_ERR "V4L2: %s initialize method failed\n",
		       vfl->name);
		v4l2_device[i] = NULL;
		MOD_DEC_USE_COUNT;
		return err;
	}

	vfl->busy = 0;

	vfl->name[sizeof(vfl->name) - 1] = 0;
	printk(KERN_INFO"V4L2: Registered \"%s\" as char device %d, %d\n",
		 vfl->name, v4l2_major, vfl->minor);
	return 0;
}

/*
 *	Unregister an unused video for linux device
 */
void
v4l2_unregister_device(struct v4l2_device *vfl)
{
	if (vfl->minor < 0 || vfl->minor >= V4L2_NUM_DEVICES ||
	    v4l2_device[vfl->minor] != vfl)
	{
		printk(KERN_ERR"V4L2: bad unregister\n");
		return;
	}
	v4l2_device[vfl->minor] = NULL;
	MOD_DEC_USE_COUNT;
}


/*
 *	/ p r o c / v i d e o d e v   H A N D L E R
 */
/*  Original /proc file code from Erik Walthinsen  */

static char *device_types[] =
{
	"capture",	"codec",	"output",	"effects", 
	"vbi",		"vtr",		"teletext",	"radio", 
	"undef",	"undef",	"undef",	"undef",
};
static int
video_read_proc(char *buf, char **start, off_t offset, int len, int unused)
{
	struct v4l2_device *vfl;
	int	i;
	char	*t;

	len = 0;
	len += sprintf(buf, "Video for Linux Two: V%d.%d alpha."
		       " Major device: %d\n",
		       V4L2_MAJOR_VERSION, V4L2_MINOR_VERSION,
		       v4l2_major);
	//len += sprintf(buf+len,"minor: type      busy name\n");
	for (i = 0; i < V4L2_NUM_DEVICES; i++)
	{
		vfl = v4l2_device[i];
		if (vfl == NULL)
			continue;
		if  (len > (PAGE_SIZE - 80))
			return len;
		if (vfl->type >= 0 &&
		    vfl->type < sizeof(device_types)/sizeof(char*))
			t = device_types[vfl->type];
		else if (vfl->type >= V4L2_TYPE_PRIVATE)
			t = "private";
		else 
			t = "undef";
		len += sprintf(buf+len, "%5d: %-9s %3d  %s\n",
			       vfl->minor, t, vfl->busy, vfl->name);
	}
	return len;
}

/* proc file for /proc/videodev */
static struct proc_dir_entry video_proc_entry =
{
	0, 8, "videodev", S_IFREG | S_IRUGO, 1, 0, 0, 0, NULL,
	&video_read_proc
};

/*
 *	V I D E O   F O R   L I N U X   T W O   I N I T I A L I Z A T I O N
 */

static struct file_operations video_fops =
{
	video_lseek,
	video_read,
	video_write,
	NULL,	/* readdir */
	video_poll,
	video_ioctl,
	video_mmap,
	video_open,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,118)
	NULL,
#endif
	video_release
};

/*
 *	Initialize Video for Linux Two
 */
int videodev_init(void)
{
	int	i;

	printk(KERN_INFO"Video for Linux Two: V%d.%d alpha."
	       " Major device: %d\n",
	       V4L2_MAJOR_VERSION, V4L2_MINOR_VERSION, v4l2_major);
	if (register_chrdev(v4l2_major, "v4l2", &video_fops))
	{
		printk("V4L2: Unable to get major %d\n", v4l2_major);
		return -EIO;
	}

	/* make sure there's a way to tell if a device is not there */
	for (i = 0; i < V4L2_NUM_DEVICES; i++)
		v4l2_device[i] = NULL;
	proc_register(&proc_root, &video_proc_entry);

	masterclock = NULL;

	return 0;
}

#ifdef MODULE		
int init_module(void)
{
	return videodev_init();
}

void cleanup_module(void)
{
	proc_unregister(&proc_root, video_proc_entry.low_ino);
	unregister_chrdev(v4l2_major, "v4l2");
}
#endif


/*
 *
 *	V 4 L 2   D R I V E R   H E L P E R   A P I
 *
 */

void
v4l2_version(int *major, int *minor)
{
	*major = V4L2_MAJOR_VERSION;
	*minor = V4L2_MINOR_VERSION;
}

struct v4l2_device *
v4l2_device_from_minor(int minor)
{
	if (minor < 0 || minor >= V4L2_NUM_DEVICES)
		return NULL;
	return v4l2_device[minor];
}

struct v4l2_device *
v4l2_device_from_file(struct file *file)
{
	if (file == NULL)
		return NULL;
	return v4l2_device_from_minor(MINOR(file->f_dentry->d_inode->i_rdev));
}

void *
v4l2_openid_from_file(struct file *file)
{
	if (file == NULL)
		return NULL;
	return file->private_data;
}

static struct mm_struct *
find_init_mm(void)
{
	static struct mm_struct	*mm;
	struct task_struct	*p;
	if (mm)
		return mm;
	for (p = current; p && (p = p->next_task) != current; )
		if (p->pid == 0)
			break;
	mm = (p) ? p->mm : NULL;
	return mm;
}

/*  Useful for using vmalloc()ed memory as DMA target  */
unsigned long
v4l2_vmalloc_to_bus(void *virt)
{
	pgd_t		*pgd;
	pmd_t		*pmd;
	pte_t		*pte;
	unsigned long	a = (unsigned long)virt;
	struct mm_struct *mm = find_init_mm();

	if (mm == NULL ||
	    pgd_none(*(pgd = pgd_offset(mm,  a))) ||
	    pmd_none(*(pmd = pmd_offset(pgd, a))) ||
	    pte_none(*(pte = pte_offset(pmd, a))))
		return 0;
	return virt_to_bus((void *)pte_page(*pte))
		+ (a & (PAGE_SIZE - 1));
}

/*  Useful for a nopage handler when mmap()ing vmalloc()ed memory  */
unsigned long
v4l2_vmalloc_to_page(void *virt)
{
	pgd_t		*pgd;
	pmd_t		*pmd;
	pte_t		*pte;
	unsigned long	a = (unsigned long)virt;
	struct mm_struct *mm = find_init_mm();

	if (mm == NULL ||
	    pgd_none(*(pgd = pgd_offset(current->mm, a))) ||
	    pmd_none(*(pmd = pmd_offset(pgd,         a))) ||
	    pte_none(*(pte = pte_offset(pmd,         a))))
		return 0;
	return pte_page(*pte);
}

/*
 *  Simple queue management
 */
static rwlock_t rw_lock_unlocked = RW_LOCK_UNLOCKED;
void
v4l2_q_init(struct v4l2_queue *q)
{
	if (q == NULL)
		return;
	q->qlock = rw_lock_unlocked;
	q->forw = (struct v4l2_q_node *)q;
	q->back = (struct v4l2_q_node *)q;
}
void
v4l2_q_add_head(struct v4l2_queue *q, struct v4l2_q_node *node)
{
	unsigned long flags;
	if (q == NULL || node == NULL)
		return;
	if (q->forw == NULL || q->back == NULL)
		v4l2_q_init(q);
	write_lock_irqsave(&(q->qlock), flags);
	node->forw = q->forw;
	node->back = (struct v4l2_q_node *)q;
	q->forw->back = node;
	q->forw = node;
	write_unlock_irqrestore(&(q->qlock), flags);
}
void
v4l2_q_add_tail(struct v4l2_queue *q, struct v4l2_q_node *node)
{
	unsigned long flags;
	if (q == NULL || node == NULL)
		return;
	if (q->forw == NULL || q->back == NULL)
		v4l2_q_init(q);
	write_lock_irqsave(&(q->qlock), flags);
	node->forw = (struct v4l2_q_node *)q;
	node->back = q->back;
	q->back->forw = node;
	q->back = node;
	write_unlock_irqrestore(&(q->qlock), flags);
}
void *
v4l2_q_del_head(struct v4l2_queue *q)
{
	unsigned long flags;
	struct v4l2_q_node *node;
	if (q == NULL)
		return NULL;
	write_lock_irqsave(&(q->qlock), flags);
	if (q->forw == NULL || q->back == NULL ||
	    q->forw == (struct v4l2_q_node *)q ||
	    q->back == (struct v4l2_q_node *)q)
	{
		write_unlock_irqrestore(&(q->qlock), flags);
		return NULL;
	}
	node = q->forw;
	node->forw->back = (struct v4l2_q_node *)q;
	q->forw = node->forw;
	node->forw = NULL;
	node->back = NULL;
	write_unlock_irqrestore(&(q->qlock), flags);
	return node;
}
void *
v4l2_q_del_tail(struct v4l2_queue *q)
{
	unsigned long flags;
	struct v4l2_q_node *node;
	if (q == NULL)
		return NULL;
	write_lock_irqsave(&(q->qlock), flags);
	if (q->forw == NULL || q->back == NULL ||
	    q->forw == (struct v4l2_q_node *)q ||
	    q->back == (struct v4l2_q_node *)q)
	{
		write_unlock_irqrestore(&(q->qlock), flags);
		return NULL;
	}
	node = q->back;
	node->back->forw = (struct v4l2_q_node *)q;
	q->back = node->back;
	node->forw = NULL;
	node->back = NULL;
	write_unlock_irqrestore(&(q->qlock), flags);
	return node;
}
void *
v4l2_q_peek_head(struct v4l2_queue *q)
{
	unsigned long flags;
	struct v4l2_q_node *node;
	read_lock_irqsave(&(q->qlock), flags);
	if (q == NULL || q->forw == NULL || q->forw == (struct v4l2_q_node *)q)
	{
		read_unlock_irqrestore(&(q->qlock), flags);
		return NULL;
	}
	node = q->forw;
	read_unlock_irqrestore(&(q->qlock), flags);
	return node;
}
void *
v4l2_q_peek_tail(struct v4l2_queue *q)
{
	unsigned long flags;
	struct v4l2_q_node *node;
	read_lock_irqsave(&(q->qlock), flags);
	if (q == NULL || q->back == NULL || q->back == (struct v4l2_q_node *)q)
	{
		read_unlock_irqrestore(&(q->qlock), flags);
		return NULL;
	}
	node = q->back;
	read_unlock_irqrestore(&(q->qlock), flags);
	return node;
}
void *
v4l2_q_yank_node(struct v4l2_queue *q, struct v4l2_q_node *node)
{
	unsigned long flags;
	struct v4l2_q_node *t;
	if (v4l2_q_peek_head(q) == NULL || node == NULL)
		return NULL;
	write_lock_irqsave(&(q->qlock), flags);
	for (t = q->forw; t != (struct v4l2_q_node *)q; t = t->forw)
		if (t == node)
		{
			node->back->forw = node->forw;
			node->forw->back = node->back;
			node->forw = NULL;
			node->back = NULL;
			write_unlock_irqrestore(&(q->qlock), flags);
			return node;
		}
	write_unlock_irqrestore(&(q->qlock), flags);
	return NULL;
}

/*
 *  Math functions
 */

u32
v4l2_math_div6432(u64 a, u32 d, u32 *r)
{
	u32 q, m;
#ifdef __i386__
	__asm__ __volatile__ (
		"	movl %2,%%eax\n"
		"	movl %3,%%edx\n"
		"	divl %4\n"
		"	movl %%eax,%0\n"
		"	movl %%edx,%1\n"
		: "=g" (q), "=g" (m)
		: "g" ((u32)a), "g" ((u32)(a >> 32)), "g" (d)
		: "eax", "edx"
		);
#else
	q = a / d;
	m = a % d;
#endif
	if (r) *r = m;
	return q;
}

void
v4l2_timeval_delta(struct timeval *d,
		   struct timeval *a,
		   struct timeval *b)
{
	if (a->tv_sec < b->tv_sec ||
	    (a->tv_sec == b->tv_sec && a->tv_usec < b->tv_usec))
	{
		d->tv_sec = b->tv_sec - a->tv_sec;
		d->tv_usec = b->tv_usec - a->tv_usec;
	}
	else
	{
		d->tv_sec = a->tv_sec - b->tv_sec;
		d->tv_usec = a->tv_usec - b->tv_usec;
	}
	if (d->tv_usec < 0)
	{
		d->tv_usec += 1000000;
		d->tv_sec -= 1;
	}
}

unsigned long
v4l2_timeval_divide(struct timeval *t, unsigned long p_100ns)
{
	/*  Note: 'p_100ns' is in 100ns units, and the quotient is rounded  */
	u64	t_100ns;

	t_100ns = (u64)t->tv_sec * 10000000 + t->tv_usec * 10;
	return v4l2_math_div6432(t_100ns + (p_100ns >> 1), p_100ns, NULL);
}

/*  Force the timeval to be an integer multiple of p_100ns  */
unsigned long
v4l2_timeval_correct(struct timeval *t, unsigned long p_100ns)
{
	unsigned long	n;
	u32		m;
	n = v4l2_timeval_divide(t, p_100ns);
	t->tv_sec = v4l2_math_div6432((u64)p_100ns * n, 10000000, &m);
	t->tv_usec = m / 10;
	return n;
}

/*
 *	Master clock and Timeval operations
 */

int
v4l2_masterclock_register(struct v4l2_clock *clock)
{
	if (clock == NULL || clock->gettime == NULL)
		return -1;
	if (masterclock != NULL)
		return -1;
	masterclock = clock;
	MOD_INC_USE_COUNT;
	return 0;
}
void
v4l2_masterclock_unregister(struct v4l2_clock *clock)
{
	if (clock != masterclock)
		return;
	masterclock = NULL;
	MOD_DEC_USE_COUNT;
}
void
v4l2_masterclock_gettime(struct timeval *curr)
{
	if (masterclock)
		masterclock->gettime(curr);
	else
		do_gettimeofday(curr);
}

/*
 *  Video Standard Operations (contributed by Michael Schimek)
 */

/* This is the recommended method to deal with the framerate fields. More 
   sophisticated drivers will access the fields directly. */
unsigned int
v4l2_video_std_fps(struct v4l2_standard *vs)
{ 
	if (vs->framerate.numerator > 0)
		return (((vs->framerate.denominator << 8) / 
			 vs->framerate.numerator) + 
			(1 << 7)) / (1 << 8);
	return 0;
}

/*  Compute the time per frame in 100ns units  */
unsigned long
v4l2_video_std_tpf(struct v4l2_standard *vs)
{
	return v4l2_math_div6432(
		(u64)vs->framerate.numerator * 10000000
		+ vs->framerate.denominator / 2,
		vs->framerate.denominator,
		NULL);
}

/*  Used only in v4l2_video_std_confirm()  */
static void
catc1p2e6(__u8 *s, char c, int n)
{
	n /= 10000;
	sprintf(s + strlen(s), "%c%d.%02d", c, n / 100, n % 100);
}

/* Verify the validity of the parameters of a v4l2_standard structure and
   create the name and id from the other fields. It does not relieve a 
   driver from examining if it can fulfill the request.  Returns an 
   errno < 0 if inconsistent, 0 if an unknown but maybe usable format, 
   or the V4L2_STD_XXX_X value if a known standard. */
int
v4l2_video_std_confirm(struct v4l2_standard *vs)
{
	unsigned int	rate  = 0;
	unsigned int	lines = vs->framelines;
	int		std   = 0;

	strcpy(vs->name, "Unknown");
	if (vs->reserved1 || vs->reserved2)
		return -EINVAL;

	if (vs->framerate.numerator > 0 &&	
	    vs->framerate.denominator > 0)
		rate = v4l2_video_std_fps(vs);

	if (vs->framelines >= 624 && vs->framelines <= 626)
		lines = 625;
	else if (vs->framelines >= 524 && vs->framelines <= 526)
		lines = 525;

	if (rate == 0 || lines == 0 || rate > 200)
		return -EINVAL;

	switch (vs->colorstandard)
	{
	case V4L2_COLOR_STD_PAL:
		strcpy(vs->name, "PAL");
		if (rate == 25 && lines == 625)
			switch (vs->colorstandard_data.pal.colorsubcarrier)
			{
			case V4L2_COLOR_SUBC_PAL_N:
				strcpy(vs->name, "PAL-N");
				if (vs->transmission & ~V4L2_TRANSM_STD_N)
					return -EINVAL;
				return V4L2_STD_PAL_N;
			case V4L2_COLOR_SUBC_PAL:
				if (vs->transmission & 
				    ~(V4L2_TRANSM_STD_B | V4L2_TRANSM_STD_G |
				      V4L2_TRANSM_STD_H | V4L2_TRANSM_STD_I |
				      V4L2_TRANSM_STD_D))
					return -EINVAL;
				std = V4L2_STD_PAL;
				goto addtransm;
			}
		else if (rate == 30 && lines == 525)
			switch (vs->colorstandard_data.pal.colorsubcarrier)
			{
			case V4L2_COLOR_SUBC_PAL_M:
				strcpy(vs->name, "PAL-M");
				if (vs->transmission & ~V4L2_TRANSM_STD_M)
					return -EINVAL;
				return V4L2_STD_PAL_M;
			case V4L2_COLOR_SUBC_PAL:
				strcpy(vs->name, "PAL-60");
				if (vs->transmission)
					return -EINVAL;
				return V4L2_STD_PAL_60;
			}
		if (vs->transmission)
			return -EINVAL;
		catc1p2e6(vs->name, ' ', 
			  vs->colorstandard_data.pal.colorsubcarrier);
		break;

	case V4L2_COLOR_STD_NTSC:
		strcpy(vs->name, "NTSC");
		if (rate == 25 && lines == 625)
			switch (vs->colorstandard_data.ntsc.colorsubcarrier)
			{
			case V4L2_COLOR_SUBC_NTSC:
				strcpy(vs->name, "NTSC-N");
				if (vs->transmission & ~V4L2_TRANSM_STD_N)
					return -EINVAL;
				return V4L2_STD_NTSC_N;
			}
		else if (rate == 30 && lines == 525)
			switch (vs->colorstandard_data.ntsc.colorsubcarrier)
			{
			case V4L2_COLOR_SUBC_NTSC:
				if (vs->transmission & ~V4L2_TRANSM_STD_M)
					return -EINVAL;
				std = V4L2_STD_NTSC;
				goto addtransm;
			case V4L2_COLOR_SUBC_PAL:
				strcpy(vs->name, "NTSC-44");
				if (vs->transmission)
					return -EINVAL;
				return V4L2_STD_NTSC_44;
			}
		if (vs->transmission)
			return -EINVAL;
		catc1p2e6(vs->name, ' ', 
			  vs->colorstandard_data.ntsc.colorsubcarrier);
		break;

	case V4L2_COLOR_STD_SECAM:
		strcpy(vs->name, "SECAM");
		if (rate == 25 && lines == 625)
			if (vs->colorstandard_data.secam.f0b == 
			    V4L2_COLOR_SUBC_SECAMB &&
			    vs->colorstandard_data.secam.f0r == 
			    V4L2_COLOR_SUBC_SECAMR)
			{
				if (vs->transmission &
				    ~(V4L2_TRANSM_STD_B | V4L2_TRANSM_STD_D |
			              V4L2_TRANSM_STD_G | V4L2_TRANSM_STD_K |
			              V4L2_TRANSM_STD_K1 | V4L2_TRANSM_STD_L))
					return -EINVAL;
				std = V4L2_STD_SECAM;
				goto addtransm;
			}
		if (vs->transmission)
			return -EINVAL;
		catc1p2e6(vs->name, ' ', vs->colorstandard_data.secam.f0b);
		catc1p2e6(vs->name, '/', vs->colorstandard_data.secam.f0r);
		break;

	default:
		return -EINVAL;
	}

        sprintf(vs->name + strlen(vs->name), " %d/%d",
	        vs->framelines, rate);

	return std;

 addtransm:
	if (vs->transmission) strcat(vs->name, "-");

        if (vs->transmission & V4L2_TRANSM_STD_B) strcat(vs->name, "B/");
        if (vs->transmission & V4L2_TRANSM_STD_G) strcat(vs->name, "G/");
        if (vs->transmission & V4L2_TRANSM_STD_H) strcat(vs->name, "H/");
	if (vs->transmission & V4L2_TRANSM_STD_I) strcat(vs->name, "I/");
	if (vs->transmission & V4L2_TRANSM_STD_D) strcat(vs->name, "D/");
	if (vs->transmission & V4L2_TRANSM_STD_K) strcat(vs->name, "K/");
	if (vs->transmission & V4L2_TRANSM_STD_K1) strcat(vs->name, "K1/");
	if (vs->transmission & V4L2_TRANSM_STD_L) strcat(vs->name, "L/");
	if (vs->transmission & V4L2_TRANSM_STD_M) strcat(vs->name, "M/");
	if (vs->transmission & V4L2_TRANSM_STD_N) strcat(vs->name, "N/");

	if (vs->name[strlen(vs->name) - 1] == '/')
		vs->name[strlen(vs->name) - 1] = 0;    

	return std;
}

/* Fill in the fields of a v4l2_standard structure according to the
   'id' and 'transmission' parameters.  Returns negative on error.  */
int
v4l2_video_std_construct(struct v4l2_standard *vs,
			 int id, __u32 transmission)
{
	memset(vs, 0, sizeof(struct v4l2_standard));

	vs->framerate.numerator = 1;
	vs->framerate.denominator = 25;
	vs->framelines = 625;

	switch (id)
	{
	case V4L2_STD_PAL_60:
		vs->framerate.numerator = 1001;
		vs->framerate.denominator = 30000;
		vs->framelines = 525;
		/* fall thru */
	case V4L2_STD_PAL:
		vs->colorstandard = V4L2_COLOR_STD_PAL;
		vs->colorstandard_data.pal.colorsubcarrier =
			V4L2_COLOR_SUBC_PAL;
		break;
	case V4L2_STD_PAL_M:
		vs->framerate.numerator = 1001;
		vs->framerate.denominator = 30000;
		vs->framelines = 525;
		vs->colorstandard = V4L2_COLOR_STD_PAL;
		vs->colorstandard_data.pal.colorsubcarrier = 
			V4L2_COLOR_SUBC_PAL_M;
		break;
	case V4L2_STD_PAL_N:
		vs->colorstandard = V4L2_COLOR_STD_PAL;
		vs->colorstandard_data.pal.colorsubcarrier = 
			V4L2_COLOR_SUBC_PAL_N;
		break;

	case V4L2_STD_NTSC:
		vs->framerate.numerator = 1001;
		vs->framerate.denominator = 30000;
		vs->framelines = 525;
		/* fall thru */
	case V4L2_STD_NTSC_N:
		vs->colorstandard = V4L2_COLOR_STD_NTSC;
		vs->colorstandard_data.ntsc.colorsubcarrier = 
			V4L2_COLOR_SUBC_NTSC;
		break;
	case V4L2_STD_NTSC_44:
		vs->framerate.numerator = 1001;
		vs->framerate.denominator = 30000;
		vs->framelines = 525;
		vs->colorstandard = V4L2_COLOR_STD_NTSC;
		vs->colorstandard_data.ntsc.colorsubcarrier = 
			V4L2_COLOR_SUBC_PAL;
		break;

	case V4L2_STD_SECAM:
		vs->colorstandard = V4L2_COLOR_STD_SECAM;
		vs->colorstandard_data.secam.f0b = V4L2_COLOR_SUBC_SECAMB;
		vs->colorstandard_data.secam.f0r = V4L2_COLOR_SUBC_SECAMR;
		break;

	default:
		return -EINVAL;
	}

	vs->transmission = transmission;

	return v4l2_video_std_confirm(vs);
}


/*---------------------------------------*/

EXPORT_SYMBOL(v4l2_register_device);
EXPORT_SYMBOL(v4l2_unregister_device);

EXPORT_SYMBOL(v4l2_version);
EXPORT_SYMBOL(v4l2_device_from_minor);
EXPORT_SYMBOL(v4l2_device_from_file);
EXPORT_SYMBOL(v4l2_openid_from_file);
EXPORT_SYMBOL(v4l2_vmalloc_to_bus);
EXPORT_SYMBOL(v4l2_vmalloc_to_page);
EXPORT_SYMBOL(v4l2_q_init);
EXPORT_SYMBOL(v4l2_q_add_head);
EXPORT_SYMBOL(v4l2_q_add_tail);
EXPORT_SYMBOL(v4l2_q_del_head);
EXPORT_SYMBOL(v4l2_q_del_tail);
EXPORT_SYMBOL(v4l2_q_peek_head);
EXPORT_SYMBOL(v4l2_q_peek_tail);
EXPORT_SYMBOL(v4l2_q_yank_node);
EXPORT_SYMBOL(v4l2_math_div6432);
EXPORT_SYMBOL(v4l2_timeval_delta);
EXPORT_SYMBOL(v4l2_timeval_divide);
EXPORT_SYMBOL(v4l2_timeval_correct);
EXPORT_SYMBOL(v4l2_masterclock_register);
EXPORT_SYMBOL(v4l2_masterclock_unregister);
EXPORT_SYMBOL(v4l2_masterclock_gettime);
EXPORT_SYMBOL(v4l2_video_std_fps);
EXPORT_SYMBOL(v4l2_video_std_tpf);
EXPORT_SYMBOL(v4l2_video_std_confirm);
EXPORT_SYMBOL(v4l2_video_std_construct);
