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