Linux-2.6.33.2/drivers/staging/dream/camera/msm_v4l2.c

/*
 *
 * Copyright (C) 2008-2009 QUALCOMM Incorporated.
 *
 */

#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/list.h>
#include <linux/ioctl.h>
#include <linux/spinlock.h>
#include <linux/videodev2.h>
#include <linux/proc_fs.h>
#include <media/v4l2-dev.h>
#include <media/msm_camera.h>
#include <mach/camera.h>
#include <media/v4l2-ioctl.h>
/*#include <linux/platform_device.h>*/

#define MSM_V4L2_START_SNAPSHOT _IOWR('V', BASE_VIDIOC_PRIVATE+1, \
      struct v4l2_buffer)

#define MSM_V4L2_GET_PICTURE    _IOWR('V', BASE_VIDIOC_PRIVATE+2, \
      struct v4l2_buffer)

#define MSM_V4L2_DEVICE_NAME       "msm_v4l2"

#define MSM_V4L2_PROC_NAME         "msm_v4l2"

#define MSM_V4L2_DEVNUM_MPEG2       0
#define MSM_V4L2_DEVNUM_YUV         20

/* HVGA-P (portrait) and HVGA-L (landscape) */
#define MSM_V4L2_WIDTH              480
#define MSM_V4L2_HEIGHT             320

#if 1
#define D(fmt, args...) printk(KERN_INFO "msm_v4l2: " fmt, ##args)
#else
#define D(fmt, args...) do {} while (0)
#endif

#define PREVIEW_FRAMES_NUM 4

struct msm_v4l2_device {
	struct list_head read_queue;
	struct v4l2_format current_cap_format;
	struct v4l2_format current_pix_format;
	struct video_device *pvdev;
	struct msm_v4l2_driver   *drv;
	uint8_t opencnt;

	spinlock_t read_queue_lock;
};

static struct msm_v4l2_device *g_pmsm_v4l2_dev;


static DEFINE_MUTEX(msm_v4l2_opencnt_lock);

static int msm_v4l2_open(struct file *f)
{
	int rc = 0;
	D("%s\n", __func__);
	mutex_lock(&msm_v4l2_opencnt_lock);
	if (!g_pmsm_v4l2_dev->opencnt) {
		rc = g_pmsm_v4l2_dev->drv->open(
				g_pmsm_v4l2_dev->drv->sync,
				MSM_APPS_ID_V4L2);
	}
	g_pmsm_v4l2_dev->opencnt++;
	mutex_unlock(&msm_v4l2_opencnt_lock);
	return rc;
}

static int msm_v4l2_release(struct file *f)
{
	int rc = 0;
	D("%s\n", __func__);
	mutex_lock(&msm_v4l2_opencnt_lock);
	if (!g_pmsm_v4l2_dev->opencnt) {
		g_pmsm_v4l2_dev->opencnt--;
		if (!g_pmsm_v4l2_dev->opencnt) {
			rc = g_pmsm_v4l2_dev->drv->release(
					g_pmsm_v4l2_dev->drv->sync);
		}
	}
	mutex_unlock(&msm_v4l2_opencnt_lock);
	return rc;
}

static unsigned int msm_v4l2_poll(struct file *f, struct poll_table_struct *w)
{
	return g_pmsm_v4l2_dev->drv->drv_poll(g_pmsm_v4l2_dev->drv->sync, f, w);
}

static long msm_v4l2_ioctl(struct file *filep,
			   unsigned int cmd, unsigned long arg)
{
	struct msm_ctrl_cmd *ctrlcmd;

	D("msm_v4l2_ioctl, cmd = %d, %d\n", cmd, __LINE__);

	switch (cmd) {
	case MSM_V4L2_START_SNAPSHOT:

		ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
		if (!ctrlcmd) {
			CDBG("msm_v4l2_ioctl: cannot allocate buffer\n");
			return -ENOMEM;
		}

		ctrlcmd->length     = 0;
		ctrlcmd->value      = NULL;
		ctrlcmd->timeout_ms = 10000;

		D("msm_v4l2_ioctl,  MSM_V4L2_START_SNAPSHOT v4l2 ioctl %d\n",
		cmd);
		ctrlcmd->type = MSM_V4L2_SNAPSHOT;
		return g_pmsm_v4l2_dev->drv->ctrl(g_pmsm_v4l2_dev->drv->sync,
							ctrlcmd);

	case MSM_V4L2_GET_PICTURE:
		D("msm_v4l2_ioctl,  MSM_V4L2_GET_PICTURE v4l2 ioctl %d\n", cmd);
		ctrlcmd = (struct msm_ctrl_cmd *)arg;
		return g_pmsm_v4l2_dev->drv->get_pict(
				g_pmsm_v4l2_dev->drv->sync, ctrlcmd);

	default:
		D("msm_v4l2_ioctl, standard v4l2 ioctl %d\n", cmd);
		return video_ioctl2(filep, cmd, arg);
	}
}

static void msm_v4l2_release_dev(struct video_device *d)
{
	D("%s\n", __func__);
}

static int msm_v4l2_querycap(struct file *f,
			     void *pctx, struct v4l2_capability *pcaps)
{
	D("%s\n", __func__);
	strncpy(pcaps->driver, MSM_APPS_ID_V4L2, sizeof(pcaps->driver));
	strncpy(pcaps->card,
		MSM_V4L2_DEVICE_NAME, sizeof(pcaps->card));
	pcaps->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	return 0;
}

static int msm_v4l2_s_std(struct file *f, void *pctx, v4l2_std_id *pnorm)
{
	D("%s\n", __func__);
	return 0;
}

static int msm_v4l2_queryctrl(struct file *f,
				void *pctx, struct v4l2_queryctrl *pqctrl)
{
  int rc = 0;
  struct msm_ctrl_cmd *ctrlcmd;

	D("%s\n", __func__);

	ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
	if (!ctrlcmd) {
		CDBG("msm_v4l2_queryctrl: cannot allocate buffer\n");
		return -ENOMEM;
	}

	ctrlcmd->type       = MSM_V4L2_QUERY_CTRL;
	ctrlcmd->length     = sizeof(struct v4l2_queryctrl);
	ctrlcmd->value      = pqctrl;
	ctrlcmd->timeout_ms = 10000;

	rc = g_pmsm_v4l2_dev->drv->ctrl(g_pmsm_v4l2_dev->drv->sync, ctrlcmd);
	if (rc < 0)
		return -1;

	return ctrlcmd->status;
}

static int msm_v4l2_g_ctrl(struct file *f, void *pctx, struct v4l2_control *c)
{
	int rc = 0;
	struct msm_ctrl_cmd *ctrlcmd;

	D("%s\n", __func__);

	ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
	if (!ctrlcmd) {
		CDBG("msm_v4l2_g_ctrl: cannot allocate buffer\n");
		return -ENOMEM;
	}

	ctrlcmd->type       = MSM_V4L2_GET_CTRL;
	ctrlcmd->length     = sizeof(struct v4l2_control);
	ctrlcmd->value      = c;
	ctrlcmd->timeout_ms = 10000;

	rc = g_pmsm_v4l2_dev->drv->ctrl(g_pmsm_v4l2_dev->drv->sync, ctrlcmd);
	if (rc < 0)
		return -1;

	return ctrlcmd->status;
}

static int msm_v4l2_s_ctrl(struct file *f, void *pctx, struct v4l2_control *c)
{
	int rc = 0;
	struct msm_ctrl_cmd *ctrlcmd;

	ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
	if (!ctrlcmd) {
		CDBG("msm_v4l2_s_ctrl: cannot allocate buffer\n");
		return -ENOMEM;
	}

	ctrlcmd->type       = MSM_V4L2_SET_CTRL;
	ctrlcmd->length     = sizeof(struct v4l2_control);
	ctrlcmd->value      = c;
	ctrlcmd->timeout_ms = 10000;

	D("%s\n", __func__);

	rc = g_pmsm_v4l2_dev->drv->ctrl(g_pmsm_v4l2_dev->drv->sync, ctrlcmd);
	if (rc < 0)
		return -1;

	return ctrlcmd->status;
}

static int msm_v4l2_reqbufs(struct file *f,
			    void *pctx, struct v4l2_requestbuffers *b)
{
	D("%s\n", __func__);
	return 0;
}

static int msm_v4l2_querybuf(struct file *f, void *pctx, struct v4l2_buffer *pb)
{
	struct msm_pmem_info pmem_buf;
#if 0
	__u32 width = 0;
	__u32 height = 0;
	__u32 y_size = 0;
	__u32 y_pad = 0;

	/* FIXME: g_pmsm_v4l2_dev->current_pix_format.fmt.pix.width; */
	width = 640;
	/* FIXME: g_pmsm_v4l2_dev->current_pix_format.fmt.pix.height; */
	height = 480;

	D("%s: width = %d, height = %d\n", __func__, width, height);

	y_size = width * height;
	y_pad = y_size % 4;
#endif

    __u32 y_pad = pb->bytesused % 4;

	/* V4L2 videodev will do the copy_from_user. */

	memset(&pmem_buf, 0, sizeof(struct msm_pmem_info));
	pmem_buf.type = MSM_PMEM_OUTPUT2;
	pmem_buf.vaddr = (void *)pb->m.userptr;
	pmem_buf.y_off = 0;
	pmem_buf.fd = (int)pb->reserved;
	/* pmem_buf.cbcr_off = (y_size + y_pad); */
    pmem_buf.cbcr_off = (pb->bytesused + y_pad);

	g_pmsm_v4l2_dev->drv->reg_pmem(g_pmsm_v4l2_dev->drv->sync, &pmem_buf);

	return 0;
}

static int msm_v4l2_qbuf(struct file *f, void *pctx, struct v4l2_buffer *pb)
{
    /*
	__u32 y_size = 0;
	__u32 y_pad = 0;
	__u32 width = 0;
	__u32 height = 0;
    */

	__u32 y_pad = 0;

	struct msm_pmem_info meminfo;
	struct msm_frame frame;
	static int cnt;

	if ((pb->flags >> 16) & 0x0001) {
		/* this is for previwe */
#if 0
		width = 640;
		height = 480;

		/* V4L2 videodev will do the copy_from_user. */
		D("%s: width = %d, height = %d\n", __func__, width, height);
		y_size = width * height;
		y_pad = y_size % 4;
#endif

		y_pad = pb->bytesused % 4;

		if (pb->type == V4L2_BUF_TYPE_PRIVATE) {
			/* this qbuf is actually for releasing */

			frame.buffer           = pb->m.userptr;
			frame.y_off            = 0;
			/* frame.cbcr_off = (y_size + y_pad); */
			frame.cbcr_off         = (pb->bytesused + y_pad);
			frame.fd               = pb->reserved;

			D("V4L2_BUF_TYPE_PRIVATE: pb->bytesused = %d \n",
			pb->bytesused);

			g_pmsm_v4l2_dev->drv->put_frame(
				g_pmsm_v4l2_dev->drv->sync,
				&frame);

			return 0;
		}

		D("V4L2_BUF_TYPE_VIDEO_CAPTURE: pb->bytesused = %d \n",
		pb->bytesused);

		meminfo.type             = MSM_PMEM_OUTPUT2;
		meminfo.fd               = (int)pb->reserved;
		meminfo.vaddr            = (void *)pb->m.userptr;
		meminfo.y_off            = 0;
		/* meminfo.cbcr_off = (y_size + y_pad); */
		meminfo.cbcr_off         = (pb->bytesused + y_pad);
		if (cnt == PREVIEW_FRAMES_NUM - 1)
			meminfo.active = 0;
		else
			meminfo.active = 1;
		cnt++;
		g_pmsm_v4l2_dev->drv->reg_pmem(g_pmsm_v4l2_dev->drv->sync,
				&meminfo);
	} else if ((pb->flags) & 0x0001) {
		/* this is for snapshot */

	__u32 y_size = 0;

	if ((pb->flags >> 8) & 0x01) {

		y_size = pb->bytesused;

		meminfo.type = MSM_PMEM_THUMBAIL;
	} else if ((pb->flags >> 9) & 0x01) {

		y_size = pb->bytesused;

		meminfo.type = MSM_PMEM_MAINIMG;
	}

	y_pad = y_size % 4;

	meminfo.fd         = (int)pb->reserved;
	meminfo.vaddr      = (void *)pb->m.userptr;
	meminfo.y_off      = 0;
	/* meminfo.cbcr_off = (y_size + y_pad); */
	meminfo.cbcr_off   = (y_size + y_pad);
	meminfo.active 	   = 1;
	g_pmsm_v4l2_dev->drv->reg_pmem(g_pmsm_v4l2_dev->drv->sync,
					&meminfo);
	}

	return 0;
}

static int msm_v4l2_dqbuf(struct file *f, void *pctx, struct v4l2_buffer *pb)
{
	struct msm_frame frame;
	D("%s\n", __func__);

	/* V4L2 videodev will do the copy_to_user. */
	if (pb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {

		D("%s, %d\n", __func__, __LINE__);

		g_pmsm_v4l2_dev->drv->get_frame(
			g_pmsm_v4l2_dev->drv->sync,
			&frame);

		pb->type       = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		pb->m.userptr  = (unsigned long)frame.buffer;  /* FIXME */
		pb->reserved   = (int)frame.fd;
		/* pb->length     = (int)frame.cbcr_off; */

		pb->bytesused  = frame.cbcr_off;

	} else if (pb->type == V4L2_BUF_TYPE_PRIVATE) {
		__u32 y_pad     = pb->bytesused % 4;

		frame.buffer   = pb->m.userptr;
		frame.y_off    = 0;
		/* frame.cbcr_off = (y_size + y_pad); */
		frame.cbcr_off = (pb->bytesused + y_pad);
		frame.fd       = pb->reserved;

		g_pmsm_v4l2_dev->drv->put_frame(
			g_pmsm_v4l2_dev->drv->sync,
			&frame);
	}

	return 0;
}

static int msm_v4l2_streamon(struct file *f, void *pctx, enum v4l2_buf_type i)
{
  struct msm_ctrl_cmd *ctrlcmd;

	ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
	if (!ctrlcmd) {
		CDBG("msm_v4l2_s_fmt_cap: cannot allocate buffer\n");
		return -ENOMEM;
	}

	ctrlcmd->type       = MSM_V4L2_STREAM_ON;
	ctrlcmd->timeout_ms = 10000;
	ctrlcmd->length     = 0;
	ctrlcmd->value      = NULL;

	D("%s\n", __func__);

	g_pmsm_v4l2_dev->drv->ctrl(
		g_pmsm_v4l2_dev->drv->sync,
		ctrlcmd);

	D("%s after drv->ctrl \n", __func__);

	return 0;
}

static int msm_v4l2_streamoff(struct file *f, void *pctx, enum v4l2_buf_type i)
{
  struct msm_ctrl_cmd *ctrlcmd;

	ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
	if (!ctrlcmd) {
		CDBG("msm_v4l2_s_fmt_cap: cannot allocate buffer\n");
		return -ENOMEM;
	}

	ctrlcmd->type       = MSM_V4L2_STREAM_OFF;
	ctrlcmd->timeout_ms = 10000;
	ctrlcmd->length     = 0;
	ctrlcmd->value      = NULL;


	D("%s\n", __func__);

	g_pmsm_v4l2_dev->drv->ctrl(
		g_pmsm_v4l2_dev->drv->sync,
		ctrlcmd);

	return 0;
}

static int msm_v4l2_enum_fmt_overlay(struct file *f,
				     void *pctx, struct v4l2_fmtdesc *pfmtdesc)
{
	D("%s\n", __func__);
	return 0;
}

static int msm_v4l2_enum_fmt_cap(struct file *f,
				 void *pctx, struct v4l2_fmtdesc *pfmtdesc)
{
	D("%s\n", __func__);

	switch (pfmtdesc->index) {
	case 0:
		pfmtdesc->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		pfmtdesc->flags = 0;
		strncpy(pfmtdesc->description, "YUV 4:2:0",
			sizeof(pfmtdesc->description));
		pfmtdesc->pixelformat = V4L2_PIX_FMT_YVU420;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int msm_v4l2_g_fmt_cap(struct file *f,
			      void *pctx, struct v4l2_format *pfmt)
{
	D("%s\n", __func__);
	pfmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	pfmt->fmt.pix.width = MSM_V4L2_WIDTH;
	pfmt->fmt.pix.height = MSM_V4L2_HEIGHT;
	pfmt->fmt.pix.pixelformat = V4L2_PIX_FMT_YVU420;
	pfmt->fmt.pix.field = V4L2_FIELD_ANY;
	pfmt->fmt.pix.bytesperline = 0;
	pfmt->fmt.pix.sizeimage = 0;
	pfmt->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
	pfmt->fmt.pix.priv = 0;
	return 0;
}

static int msm_v4l2_s_fmt_cap(struct file *f,
			      void *pctx, struct v4l2_format *pfmt)
{
  struct msm_ctrl_cmd *ctrlcmd;

	D("%s\n", __func__);

	ctrlcmd = kmalloc(sizeof(struct msm_ctrl_cmd), GFP_ATOMIC);
	if (!ctrlcmd) {
		CDBG("msm_v4l2_s_fmt_cap: cannot allocate buffer\n");
		return -ENOMEM;
	}

  ctrlcmd->type       = MSM_V4L2_VID_CAP_TYPE;
  ctrlcmd->length     = sizeof(struct v4l2_format);
  ctrlcmd->value      = pfmt;
  ctrlcmd->timeout_ms = 10000;

	if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
		kfree(ctrlcmd);
		return -1;
	}

#if 0
	/* FIXEME */
	if (pfmt->fmt.pix.pixelformat != V4L2_PIX_FMT_YVU420) {
		kfree(ctrlcmd);
		return -EINVAL;
	}
#endif

	/* Ok, but check other params, too. */

#if 0
	memcpy(&g_pmsm_v4l2_dev->current_pix_format.fmt.pix, pfmt,
	       sizeof(struct v4l2_format));
#endif

	g_pmsm_v4l2_dev->drv->ctrl(g_pmsm_v4l2_dev->drv->sync, ctrlcmd);

	return 0;
}

static int msm_v4l2_g_fmt_overlay(struct file *f,
				  void *pctx, struct v4l2_format *pfmt)
{
	D("%s\n", __func__);
	pfmt->type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	pfmt->fmt.pix.width = MSM_V4L2_WIDTH;
	pfmt->fmt.pix.height = MSM_V4L2_HEIGHT;
	pfmt->fmt.pix.pixelformat = V4L2_PIX_FMT_YVU420;
	pfmt->fmt.pix.field = V4L2_FIELD_ANY;
	pfmt->fmt.pix.bytesperline = 0;
	pfmt->fmt.pix.sizeimage = 0;
	pfmt->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
	pfmt->fmt.pix.priv = 0;
	return 0;
}

static int msm_v4l2_s_fmt_overlay(struct file *f,
				  void *pctx, struct v4l2_format *pfmt)
{
	D("%s\n", __func__);
	return 0;
}

static int msm_v4l2_overlay(struct file *f, void *pctx, unsigned int i)
{
	D("%s\n", __func__);
	return 0;
}

static int msm_v4l2_g_jpegcomp(struct file *f,
			       void *pctx, struct v4l2_jpegcompression *pcomp)
{
	D("%s\n", __func__);
	return 0;
}

static int msm_v4l2_s_jpegcomp(struct file *f,
			       void *pctx, struct v4l2_jpegcompression *pcomp)
{
	D("%s\n", __func__);
	return 0;
}

#ifdef CONFIG_PROC_FS
int msm_v4l2_read_proc(char *pbuf, char **start, off_t offset,
		       int count, int *eof, void *data)
{
	int len = 0;
	len += snprintf(pbuf, strlen("stats\n") + 1, "stats\n");

	if (g_pmsm_v4l2_dev) {
		len += snprintf(pbuf, strlen("mode: ") + 1, "mode: ");

		if (g_pmsm_v4l2_dev->current_cap_format.type
		    == V4L2_BUF_TYPE_VIDEO_CAPTURE)
			len += snprintf(pbuf, strlen("capture\n") + 1,
					"capture\n");
		else
			len += snprintf(pbuf, strlen("unknown\n") + 1,
					"unknown\n");

		len += snprintf(pbuf, 21, "resolution: %dx%d\n",
				g_pmsm_v4l2_dev->current_cap_format.fmt.pix.
				width,
				g_pmsm_v4l2_dev->current_cap_format.fmt.pix.
				height);

		len += snprintf(pbuf,
				strlen("pixel format: ") + 1, "pixel format: ");
		if (g_pmsm_v4l2_dev->current_cap_format.fmt.pix.pixelformat
		    == V4L2_PIX_FMT_YVU420)
			len += snprintf(pbuf, strlen("yvu420\n") + 1,
					"yvu420\n");
		else
			len += snprintf(pbuf, strlen("unknown\n") + 1,
					"unknown\n");

		len += snprintf(pbuf, strlen("colorspace: ") + 1,
				"colorspace: ");
		if (g_pmsm_v4l2_dev->current_cap_format.fmt.pix.colorspace
		    == V4L2_COLORSPACE_JPEG)
			len += snprintf(pbuf, strlen("jpeg\n") + 1, "jpeg\n");
		else
			len += snprintf(pbuf, strlen("unknown\n") + 1,
					"unknown\n");
	}

	*eof = 1;
	return len;
}
#endif

static const struct v4l2_file_operations msm_v4l2_fops = {
	.owner = THIS_MODULE,
	.open = msm_v4l2_open,
	.poll = msm_v4l2_poll,
	.release = msm_v4l2_release,
	.ioctl = msm_v4l2_ioctl,
};

static void msm_v4l2_dev_init(struct msm_v4l2_device *pmsm_v4l2_dev)
{
	pmsm_v4l2_dev->read_queue_lock =
	    __SPIN_LOCK_UNLOCKED(pmsm_v4l2_dev->read_queue_lock);
	INIT_LIST_HEAD(&pmsm_v4l2_dev->read_queue);
}

static int msm_v4l2_try_fmt_cap(struct file *file,
				 void *fh, struct v4l2_format *f)
{
	/* FIXME */
	return 0;
}

static int mm_v4l2_try_fmt_type_private(struct file *file,
					 void *fh, struct v4l2_format *f)
{
	/* FIXME */
	return 0;
}

/*
 * should the following structure be used instead of the code in the function?
 * static const struct v4l2_ioctl_ops msm_v4l2_ioctl_ops = {
 *     .vidioc_querycap = ....
 * }
 */
static const struct v4l2_ioctl_ops msm_ioctl_ops = {
	.vidioc_querycap = msm_v4l2_querycap,
	.vidioc_s_std = msm_v4l2_s_std,

	.vidioc_queryctrl = msm_v4l2_queryctrl,
	.vidioc_g_ctrl = msm_v4l2_g_ctrl,
	.vidioc_s_ctrl = msm_v4l2_s_ctrl,

	.vidioc_reqbufs = msm_v4l2_reqbufs,
	.vidioc_querybuf = msm_v4l2_querybuf,
	.vidioc_qbuf = msm_v4l2_qbuf,
	.vidioc_dqbuf = msm_v4l2_dqbuf,

	.vidioc_streamon = msm_v4l2_streamon,
	.vidioc_streamoff = msm_v4l2_streamoff,

	.vidioc_enum_fmt_vid_overlay = msm_v4l2_enum_fmt_overlay,
	.vidioc_enum_fmt_vid_cap = msm_v4l2_enum_fmt_cap,

	.vidioc_try_fmt_vid_cap = msm_v4l2_try_fmt_cap,
	.vidioc_try_fmt_type_private = mm_v4l2_try_fmt_type_private,

	.vidioc_g_fmt_vid_cap = msm_v4l2_g_fmt_cap,
	.vidioc_s_fmt_vid_cap = msm_v4l2_s_fmt_cap,
	.vidioc_g_fmt_vid_overlay = msm_v4l2_g_fmt_overlay,
	.vidioc_s_fmt_vid_overlay = msm_v4l2_s_fmt_overlay,
	.vidioc_overlay = msm_v4l2_overlay,

	.vidioc_g_jpegcomp = msm_v4l2_g_jpegcomp,
	.vidioc_s_jpegcomp = msm_v4l2_s_jpegcomp,
};

static int msm_v4l2_video_dev_init(struct video_device *pvd)
{
	strncpy(pvd->name, MSM_APPS_ID_V4L2, sizeof(pvd->name));
	pvd->vfl_type = 1;
	pvd->fops = &msm_v4l2_fops;
	pvd->release = msm_v4l2_release_dev;
	pvd->minor = -1;
	pvd->ioctl_ops = &msm_ioctl_ops;
	return msm_v4l2_register(g_pmsm_v4l2_dev->drv);
}

static int __init msm_v4l2_init(void)
{
	int rc = -ENOMEM;
	struct video_device *pvdev = NULL;
	struct msm_v4l2_device *pmsm_v4l2_dev = NULL;
	D("%s\n", __func__);

	pvdev = video_device_alloc();
	if (pvdev == NULL)
		return rc;

	pmsm_v4l2_dev =
		kzalloc(sizeof(struct msm_v4l2_device), GFP_KERNEL);
	if (pmsm_v4l2_dev == NULL) {
		video_device_release(pvdev);
		return rc;
	}

	msm_v4l2_dev_init(pmsm_v4l2_dev);

	g_pmsm_v4l2_dev = pmsm_v4l2_dev;
	g_pmsm_v4l2_dev->pvdev = pvdev;

	g_pmsm_v4l2_dev->drv =
		kzalloc(sizeof(struct msm_v4l2_driver), GFP_KERNEL);
	if (!g_pmsm_v4l2_dev->drv) {
		video_device_release(pvdev);
		kfree(pmsm_v4l2_dev);
		return rc;
	}

	rc = msm_v4l2_video_dev_init(pvdev);
	if (rc < 0) {
		video_device_release(pvdev);
		kfree(g_pmsm_v4l2_dev->drv);
		kfree(pmsm_v4l2_dev);
		return rc;
	}

	if (video_register_device(pvdev, VFL_TYPE_GRABBER,
	    MSM_V4L2_DEVNUM_YUV)) {
		D("failed to register device\n");
		video_device_release(pvdev);
		kfree(g_pmsm_v4l2_dev);
		g_pmsm_v4l2_dev = NULL;
		return -ENOENT;
	}
#ifdef CONFIG_PROC_FS
	create_proc_read_entry(MSM_V4L2_PROC_NAME,
			       0, NULL, msm_v4l2_read_proc, NULL);
#endif

	return 0;
}

static void __exit msm_v4l2_exit(void)
{
	struct video_device *pvdev = g_pmsm_v4l2_dev->pvdev;
	D("%s\n", __func__);
#ifdef CONFIG_PROC_FS
	remove_proc_entry(MSM_V4L2_PROC_NAME, NULL);
#endif
	video_unregister_device(pvdev);
	video_device_release(pvdev);

	msm_v4l2_unregister(g_pmsm_v4l2_dev->drv);

	kfree(g_pmsm_v4l2_dev->drv);
	g_pmsm_v4l2_dev->drv = NULL;

	kfree(g_pmsm_v4l2_dev);
	g_pmsm_v4l2_dev = NULL;
}

module_init(msm_v4l2_init);
module_exit(msm_v4l2_exit);

MODULE_DESCRIPTION("MSM V4L2 driver");
MODULE_LICENSE("GPL v2");