/* * 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 * based on code by Alan Cox, * * gcc -c -O2 -Wall -DMODULE videodev.c */ #ifndef __KERNEL__ #define __KERNEL__ #endif #include #ifndef EXPORT_SYMTAB #define EXPORT_SYMTAB #endif #include #include #include #include #include #include #include #include #include "videodev2.h" #include #include #include #include #include #ifdef CONFIG_KMOD #include #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);