/*
 *	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

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 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)];
	int	err = -EINVAL;

	if (vfl->ioctl)
		err = vfl->ioctl(file->private_data, cmd, (void *)arg);

	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;
}


/*
 *	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;

	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", 
	"?",		"?",		"?",		"?",
};
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;
		t = (vfl->type < 0 ||
		     vfl->type >= sizeof(device_types)/sizeof(char*))
		  ? "?" : device_types[vfl->type];
		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;
}

/*  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;

	if (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 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;

	if (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);
