/* * PWCBSD - Philips USB webcam driver for FreeBSD 5.4 and higher * * Copyright (C) 2006 Raaf * * Based on the Linux pwc driver. * * Copyright (C) 1999-2003 Nemosoft Unv. * Copyright (C) 2004-2006 Luc Saillard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include "pwc.h" #include "pwc-ioctl.h" #ifdef USB_DEBUG static const char *v4l1_ioctls[] = { "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", "SMICROCODE", "GVBIFMT", "SVBIFMT" }; #define V4L1_IOCTLS (sizeof(v4l1_ioctls)/ sizeof(v4l1_ioctls[0])) #define _IOC_TYPEBITS 8 #define _IOC_NRBITS 8 #define _IOC_NRSHIFT 0 #define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) #define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) #endif int pwc_video_do_ioctl(struct pwc_softc *pdev, unsigned int cmd, void *arg, int unit) { #ifdef USB_DEBUG if (pwcdebug & TRACE_IOCTL) { switch (_IOC_TYPE(cmd)) { case 'v': printf(PWC_NAME": ioctl 0x%x (v4l1, VIDIOC%s)\n", cmd, (_IOC_NR(cmd) < V4L1_IOCTLS) ? v4l1_ioctls[_IOC_NR(cmd)] : "?"); break; default: printf(PWC_NAME": ioctl 0x%x (?)\n", cmd); } } #endif switch (cmd) { /* Query cabapilities */ case VIDIOCGCAP: { struct video_capability *caps = arg; strncpy(caps->name, pdev->name, sizeof(caps->name) - 1); caps->name[sizeof(caps->name) - 1] = '\0'; caps->type = VID_TYPE_CAPTURE; caps->channels = 1; caps->audios = 1; caps->minwidth = pdev->view_min.x; caps->minheight = pdev->view_min.y; caps->maxwidth = pdev->view_max.x; caps->maxheight = pdev->view_max.y; break; } /* Channel functions (simulate 1 channel) */ case VIDIOCGCHAN: { struct video_channel *v = arg; if (v->channel != 0) return -EINVAL; v->flags = 0; v->tuners = 0; v->type = VIDEO_TYPE_CAMERA; strcpy(v->name, "Webcam"); return 0; } case VIDIOCSCHAN: { /* The spec says the argument is an integer, but the bttv driver uses a video_channel arg, which makes sense becasue it also has the norm flag. */ struct video_channel *v = arg; if (v->channel != 0) return -EINVAL; return 0; } /* Picture functions; contrast etc. */ case VIDIOCGPICT: { struct video_picture *p = arg; int val; val = pwc_get_brightness(pdev); if (val >= 0) p->brightness = (val<<9); else p->brightness = 0xffff; val = pwc_get_contrast(pdev); if (val >= 0) p->contrast = (val<<10); else p->contrast = 0xffff; /* Gamma, Whiteness, what's the difference? :) */ val = pwc_get_gamma(pdev); if (val >= 0) p->whiteness = (val<<11); else p->whiteness = 0xffff; val = pwc_get_saturation(pdev); if (val >= 0) p->colour = 32768 + (val-256) * 327; else p->colour = 0xffff; p->depth = 24; p->palette = pdev->vpalette; p->hue = 0xFFFF; /* N/A */ break; } case VIDIOCSPICT: { struct video_picture *p = arg; /* * FIXME: Suppose we are mid read ANSWER: No problem: the firmware of the camera can handle brightness/contrast/etc changes at _any_ time, and the palette is used exactly once in the uncompress routine. */ pwc_set_brightness(pdev, p->brightness); pwc_set_contrast(pdev, p->contrast); pwc_set_gamma(pdev, p->whiteness); pwc_set_saturation(pdev, p->colour); if (p->palette && p->palette != pdev->vpalette) { switch (p->palette) { case VIDEO_PALETTE_YUV420P: case VIDEO_PALETTE_RAW: pdev->vpalette = p->palette; return pwc_try_video_mode(pdev, pdev->image.x, pdev->image.y, pdev->vframes, pdev->vcompression, pdev->vsnapshot); break; default: return -EINVAL; break; } } break; } /* Window/size parameters */ case VIDIOCGWIN: { struct video_window *vw = arg; vw->x = 0; vw->y = 0; vw->width = pdev->view.x; vw->height = pdev->view.y; vw->chromakey = 0; vw->flags = (pdev->vframes << PWC_FPS_SHIFT) | (pdev->vsnapshot ? PWC_FPS_SNAPSHOT : 0); break; } case VIDIOCSWIN: { struct video_window *vw = arg; int fps, snapshot, ret; fps = (vw->flags & PWC_FPS_FRMASK) >> PWC_FPS_SHIFT; snapshot = vw->flags & PWC_FPS_SNAPSHOT; if (fps == 0) fps = pdev->vframes; if (pdev->view.x == vw->width && pdev->view.y && fps == pdev->vframes && snapshot == pdev->vsnapshot) return 0; ret = pwc_try_video_mode(pdev, vw->width, vw->height, fps, pdev->vcompression, snapshot); if (ret) return ret; break; } /* We don't have overlay support (yet) */ case VIDIOCGFBUF: { struct video_buffer *vb = arg; memset(vb,0,sizeof(*vb)); break; } #ifdef USE_MMAP /* mmap() functions */ case VIDIOCGMBUF: { /* Tell the user program how much memory is needed for a mmap() */ struct video_mbuf *vm = arg; int i; memset(vm, 0, sizeof(*vm)); vm->size = pdev->pwc_mbufs * round_page(pdev->len_per_image); vm->frames = pdev->pwc_mbufs; /* double buffering should be enough for most applications */ for (i = 0; i < pdev->pwc_mbufs; i++) vm->offsets[i] = i * round_page(pdev->len_per_image); break; } case VIDIOCMCAPTURE: { /* Start capture into a given image buffer (called 'frame' in video_mmap structure) */ struct video_mmap *vm = arg; Trace(TRACE_READ, "VIDIOCMCAPTURE: %dx%d, frame %d, format %d\n", vm->width, vm->height, vm->frame, vm->format); if (vm->frame < 0 || vm->frame >= pdev->pwc_mbufs) return -EINVAL; /* xawtv is nasty. It probes the available palettes by setting a very small image size and trying various palettes... The driver doesn't support such small images, so I'm working around it. */ if (vm->format) { switch (vm->format) { case VIDEO_PALETTE_YUV420P: case VIDEO_PALETTE_RAW: break; default: return -EINVAL; break; } } if ((vm->width != pdev->view.x || vm->height != pdev->view.y) && (vm->width >= pdev->view_min.x && vm->height >= pdev->view_min.y)) { int ret; Trace(TRACE_OPEN, "VIDIOCMCAPTURE: changing size to please xawtv :-(.\n"); ret = pwc_try_video_mode(pdev, vm->width, vm->height, pdev->vframes, pdev->vcompression, pdev->vsnapshot); if (ret) return ret; } /* ... size mismatch */ /* FIXME: should we lock here? */ if (pdev->image_used[vm->frame]) return -EBUSY; /* buffer wasn't available. Bummer */ pdev->image_used[vm->frame] = 1; /* Okay, we're done here. In the SYNC call we wait until a frame comes available, then expand image into the given buffer. In contrast to the CPiA cam the Philips cams deliver a constant stream, almost like a grabber card. Also, we have separate buffers for the rawdata and the image, meaning we can nearly always expand into the requested buffer. */ Trace(TRACE_READ, "VIDIOCMCAPTURE done.\n"); break; } case VIDIOCSYNC: { /* The doc says: "Whenever a buffer is used it should call VIDIOCSYNC to free this frame up and continue." The only odd thing about this whole procedure is that MCAPTURE flags the buffer as "in use", and SYNC immediately unmarks it, while it isn't after SYNC that you know that the buffer actually got filled! So you better not start a CAPTURE in the same frame immediately (use double buffering). This is not a problem for this cam, since it has extra intermediate buffers, but a hardware grabber card will then overwrite the buffer you're working on. */ int *mbuf = arg; int ret; Trace(TRACE_READ, "VIDIOCSYNC called (%d).\n", *mbuf); /* bounds check */ if (*mbuf < 0 || *mbuf >= pdev->pwc_mbufs) return -EINVAL; /* check if this buffer was requested anyway */ if (pdev->image_used[*mbuf] == 0) return -EINVAL; pdev->fill_image = *mbuf; /* tell in which buffer we want the image to be expanded */ while ((ret = pwc_handle_frame(pdev)) == -EBUSY) { pdev->state |= PWC_ASLEEP; ret = tsleep(pdev, PZERO | PCATCH, "pwcsyn", 0); pdev->state &= ~PWC_ASLEEP; if(pdev->error_status) { pdev->image_used[*mbuf] = 0; return -pdev->error_status; } else if(ret) return -ret; } pdev->image_used[*mbuf] = 0; if(ret < 0) return ret; Trace(TRACE_READ, "VIDIOCSYNC: frame ready.\n"); break; } #endif case VIDIOCGAUDIO: { struct video_audio *v = arg; strcpy(v->name, "Microphone"); v->audio = -1; /* unknown audio minor */ v->flags = 0; v->mode = VIDEO_SOUND_MONO; v->volume = 0; v->bass = 0; v->treble = 0; v->balance = 0x8000; v->step = 1; break; } case VIDIOCSAUDIO: { /* Dummy: nothing can be set */ break; } case VIDIOCGUNIT: { struct video_unit *vu = arg; vu->video = unit; vu->audio = -1; /* not known yet */ vu->vbi = -1; vu->radio = -1; vu->teletext = -1; break; } default: return pwc_do_ioctl(pdev, cmd, arg); } /* ..switch */ return 0; }