/* linux/drivers/media/video/samsung/jpeg_v2x/jpeg_dec.c
 *
 * Copyright (c) 2010 Samsung Electronics Co., Ltd.
 * http://www.samsung.com/
 *
 * Core file for Samsung Jpeg v2.x Interface driver
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/signal.h>
#include <linux/ioport.h>
#include <linux/kmod.h>
#include <linux/vmalloc.h>
#include <linux/time.h>
#include <linux/clk.h>
#include <linux/semaphore.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>

#include <asm/page.h>

#include <plat/regs_jpeg_v2_x.h>
#include <mach/irqs.h>

#include <media/v4l2-ioctl.h>

#include "jpeg_core.h"
#include "jpeg_dev.h"

#include "jpeg_mem.h"
#include "jpeg_regs.h"

static struct jpeg_fmt formats[] = {
	{
		.name		= "JPEG compressed format",
		.fourcc		= V4L2_PIX_FMT_JPEG_444,
		.depth		= {8},
		.color		= JPEG_444,
		.memplanes	= 1,
		.types		= M2M_OUTPUT,
	}, {
		.name		= "JPEG compressed format",
		.fourcc		= V4L2_PIX_FMT_JPEG_422,
		.depth		= {8},
		.color		= JPEG_422,
		.memplanes	= 1,
		.types		= M2M_OUTPUT,
	}, {
		.name		= "JPEG compressed format",
		.fourcc		= V4L2_PIX_FMT_JPEG_420,
		.depth		= {8},
		.color		= JPEG_420,
		.memplanes	= 1,
		.types		= M2M_OUTPUT,
	}, {
		.name		= "JPEG compressed format",
		.fourcc		= V4L2_PIX_FMT_JPEG_GRAY,
		.depth		= {8},
		.color		= JPEG_GRAY,
		.memplanes	= 1,
		.types		= M2M_OUTPUT,
	}, {
		.name		= "RGB565",
		.fourcc		= V4L2_PIX_FMT_RGB565X,
		.depth		= {16},
		.color		= RGB_565,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "XRGB-8-8-8-8, 32 bpp",
		.fourcc		= V4L2_PIX_FMT_RGB32,
		.depth		= {32},
		.color		= RGB_888,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:4:4 packed, Y/CbCr",
		.fourcc		= V4L2_PIX_FMT_YUV444_2P,
		.depth		= {8, 16},
		.color		= YCBCR_444_2P,
		.memplanes	= 2,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:4:4 packed, Y/CrCb",
		.fourcc		= V4L2_PIX_FMT_YVU444_2P,
		.depth		= {8, 16},
		.color		= YCRCB_444_2P,
		.memplanes	= 2,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:4:4 packed, Y/Cb/Cr",
		.fourcc		= V4L2_PIX_FMT_YUV444_3P,
		.depth		= {8, 8, 8},
		.color		= YCBCR_444_3P,
		.memplanes	= 3,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:2 packed, YCrYCb",
		.fourcc		= V4L2_PIX_FMT_YVYU,
		.depth		= {16},
		.color		= YCRYCB_422_1P,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:2 packed, CrYCbY",
		.fourcc		= V4L2_PIX_FMT_VYUY,
		.depth		= {16},
		.color		= CRYCBY_422_1P,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:2 packed, CbYCrY",
		.fourcc		= V4L2_PIX_FMT_UYVY,
		.depth		= {16},
		.color		= CRYCBY_422_1P,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:2 packed, YCbYCr",
		.fourcc		= V4L2_PIX_FMT_YUYV,
		.depth		= {16},
		.color		= YCBYCR_422_1P,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:2 planar, Y/CrCb",
		.fourcc		= V4L2_PIX_FMT_NV61,
		.depth		= {8, 8},
		.color		= YCRCB_422_2P,
		.memplanes	= 2,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:2 planar, Y/CbCr",
		.fourcc		= V4L2_PIX_FMT_NV16,
		.depth		= {8, 8},
		.color		= YCBCR_422_2P,
		.memplanes	= 2,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:0 planar, Y/CbCr",
		.fourcc		= V4L2_PIX_FMT_NV12,
		.depth		= {8, 4},
		.color		= YCBCR_420_2P,
		.memplanes	= 2,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:0 planar, Y/CrCb",
		.fourcc		= V4L2_PIX_FMT_NV21,
		.depth		= {8, 4},
		.color		= YCRCB_420_2P,
		.memplanes	= 2,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "YUV 4:2:0 contiguous 3-planar, Y/Cr/Cb",
		.fourcc		= V4L2_PIX_FMT_YUV420,
		.depth		= {8, 2, 2},
		.color		= YCBCR_420_3P,
		.memplanes	= 3,
		.types		= M2M_CAPTURE,
	}, {
		.name		= "Gray",
		.fourcc		= V4L2_PIX_FMT_GREY,
		.depth		= {8},
		.color		= GRAY,
		.memplanes	= 1,
		.types		= M2M_CAPTURE,
	},
};

static struct jpeg_fmt *find_format(struct v4l2_format *f)
{
	struct jpeg_fmt *fmt;
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(formats); ++i) {
		fmt = &formats[i];
		if (fmt->fourcc == f->fmt.pix_mp.pixelformat)
			break;
	}

	return (i == ARRAY_SIZE(formats)) ? NULL : fmt;
}

static int jpeg_dec_vidioc_querycap(struct file *file, void *priv,
			   struct v4l2_capability *cap)
{
	struct jpeg_ctx *ctx = file->private_data;
	struct jpeg_dev *dev = ctx->dev;

	strncpy(cap->driver, dev->plat_dev->name, sizeof(cap->driver) - 1);
	strncpy(cap->card, dev->plat_dev->name, sizeof(cap->card) - 1);
	cap->bus_info[0] = 0;
	cap->version = KERNEL_VERSION(1, 0, 0);
	cap->capabilities = V4L2_CAP_STREAMING |
		V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
		V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE;
	return 0;
}

int jpeg_dec_vidioc_enum_fmt(struct file *file, void *priv,
				struct v4l2_fmtdesc *f)
{
	struct jpeg_fmt *fmt;

	if (f->index >= ARRAY_SIZE(formats))
		return -EINVAL;

	fmt = &formats[f->index];
	strncpy(f->description, fmt->name, sizeof(f->description) - 1);
	f->pixelformat = fmt->color;

	return 0;
}

int jpeg_dec_vidioc_g_fmt(struct file *file, void *priv,
			     struct v4l2_format *f)
{
	struct jpeg_ctx *ctx = priv;
	struct v4l2_pix_format_mplane *pixm;
	struct jpeg_dec_param *dec_param = &ctx->param.dec_param;
	unsigned int width, height;

	pixm = &f->fmt.pix_mp;

	pixm->field	= V4L2_FIELD_NONE;

	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
		pixm->pixelformat = dec_param->in_fmt;
		pixm->num_planes = dec_param->in_plane;
		pixm->width = dec_param->in_width;
		pixm->height = dec_param->in_height;
	} else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
		jpeg_get_frame_size(ctx->dev->reg_base, &width, &height);
		pixm->pixelformat =
			dec_param->out_fmt;
		pixm->num_planes = dec_param->out_plane;
		pixm->width = width;
		pixm->height = height;
	} else {
		v4l2_err(&ctx->dev->v4l2_dev,
			"Wrong buffer/video queue type (%d)\n", f->type);
	}

	return 0;
}

static int jpeg_dec_vidioc_try_fmt(struct file *file, void *priv,
			       struct v4l2_format *f)
{
	struct jpeg_fmt *fmt;
	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
	struct jpeg_ctx *ctx = priv;
	int i;

	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE &&
		f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
			return -EINVAL;

	fmt = find_format(f);

	if (!fmt) {
		v4l2_err(&ctx->dev->v4l2_dev,
			 "Fourcc format (0x%08x) invalid.\n",
			 f->fmt.pix.pixelformat);
		return -EINVAL;
	}

	if (pix->field == V4L2_FIELD_ANY)
		pix->field = V4L2_FIELD_NONE;
	else if (V4L2_FIELD_NONE != pix->field)
		return -EINVAL;

	pix->num_planes = fmt->memplanes;

	for (i = 0; i < pix->num_planes; ++i) {
		int bpl = pix->plane_fmt[i].bytesperline;

		jpeg_dbg("[%d] bpl: %d, depth: %d, w: %d, h: %d",
		    i, bpl, fmt->depth[i], pix->width, pix->height);
		if (!bpl || (bpl * 8 / fmt->depth[i]) > pix->width)
			bpl = (pix->width * fmt->depth[i]) >> 3;

		if (!pix->plane_fmt[i].sizeimage)
			pix->plane_fmt[i].sizeimage = pix->height * bpl;

		pix->plane_fmt[i].bytesperline = bpl;

		jpeg_dbg("[%d]: bpl: %d, sizeimage: %d",
		    i, pix->plane_fmt[i].bytesperline,
		    pix->plane_fmt[i].sizeimage);
	}

	if (f->fmt.pix.height > MAX_JPEG_HEIGHT)
		f->fmt.pix.height = MAX_JPEG_HEIGHT;

	if (f->fmt.pix.width > MAX_JPEG_WIDTH)
		f->fmt.pix.width = MAX_JPEG_WIDTH;

	return 0;
}

static int jpeg_dec_vidioc_s_fmt_cap(struct file *file, void *priv,
				struct v4l2_format *f)
{
	struct jpeg_ctx *ctx = priv;
	struct vb2_queue *vq;
	struct v4l2_pix_format_mplane *pix;
	struct jpeg_fmt *fmt;
	int ret;
	int i;

	ret = jpeg_dec_vidioc_try_fmt(file, priv, f);
	if (ret)
		return ret;

	vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
	if (!vq)
		return -EINVAL;

	if (vb2_is_busy(vq)) {
		v4l2_err(&ctx->dev->v4l2_dev, "queue (%d) busy\n", f->type);
		return -EBUSY;
	}

	/* TODO: width & height has to be multiple of two */
	pix = &f->fmt.pix_mp;
	fmt = find_format(f);

	for (i = 0; i < fmt->memplanes; i++) {
		ctx->payload[i] =
			pix->plane_fmt[i].bytesperline * pix->height;
		ctx->param.dec_param.out_depth[i] = fmt->depth[i];
	}
	ctx->param.dec_param.out_width = pix->width;
	ctx->param.dec_param.out_height = pix->height;
	ctx->param.dec_param.out_plane = fmt->memplanes;
	ctx->param.dec_param.out_fmt = fmt->color;

	return 0;
}

static int jpeg_dec_vidioc_s_fmt_out(struct file *file, void *priv,
			struct v4l2_format *f)
{
	struct jpeg_ctx *ctx = priv;
	struct vb2_queue *vq;
	struct v4l2_pix_format_mplane *pix;
	struct jpeg_fmt *fmt;
	int ret;
	int i;

	ret = jpeg_dec_vidioc_try_fmt(file, priv, f);
	if (ret)
		return ret;

	vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
	if (!vq)
		return -EINVAL;

	if (vb2_is_busy(vq)) {
		v4l2_err(&ctx->dev->v4l2_dev, "queue (%d) busy\n", f->type);
		return -EBUSY;
	}

	/* TODO: width & height has to be multiple of two */
	pix = &f->fmt.pix_mp;
	fmt = find_format(f);

	for (i = 0; i < fmt->memplanes; i++)
		ctx->payload[i] =
			pix->plane_fmt[i].bytesperline * pix->height;

	ctx->param.dec_param.in_width = pix->width;
	ctx->param.dec_param.in_height = pix->height;
	ctx->param.dec_param.in_plane = fmt->memplanes;
	ctx->param.dec_param.in_depth = fmt->depth[0];
	ctx->param.dec_param.in_fmt = fmt->color;
	if((pix->plane_fmt[0].sizeimage % 32) == 0)
		ctx->param.dec_param.size = (pix->plane_fmt[0].sizeimage / 32);
	else
		ctx->param.dec_param.size = (pix->plane_fmt[0].sizeimage / 32) + 1;
	ctx->param.dec_param.mem_size = pix->plane_fmt[0].sizeimage;

	return 0;
}

static int jpeg_dec_m2m_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *reqbufs)
{
	struct jpeg_ctx *ctx = priv;
	struct vb2_queue *vq;

	vq = v4l2_m2m_get_vq(ctx->m2m_ctx, reqbufs->type);
	if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
		ctx->dev->vb2->set_cacheable(ctx->dev->alloc_ctx, ctx->input_cacheable);
	else if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
		ctx->dev->vb2->set_cacheable(ctx->dev->alloc_ctx, ctx->output_cacheable);

	return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs);
}

static int jpeg_dec_m2m_querybuf(struct file *file, void *priv,
			   struct v4l2_buffer *buf)
{
	struct jpeg_ctx *ctx = priv;
	return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf);
}

static int jpeg_dec_m2m_qbuf(struct file *file, void *priv,
			  struct v4l2_buffer *buf)
{
	struct jpeg_ctx *ctx = priv;
	return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf);
}

static int jpeg_dec_m2m_dqbuf(struct file *file, void *priv,
			   struct v4l2_buffer *buf)
{
	struct jpeg_ctx *ctx = priv;
	return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf);
}

static int jpeg_dec_m2m_streamon(struct file *file, void *priv,
			   enum v4l2_buf_type type)
{
	struct jpeg_ctx *ctx = priv;
	return v4l2_m2m_streamon(file, ctx->m2m_ctx, type);
}

static int jpeg_dec_m2m_streamoff(struct file *file, void *priv,
			    enum v4l2_buf_type type)
{
	struct jpeg_ctx *ctx = priv;
	return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type);
}

static int vidioc_dec_s_jpegcomp(struct file *file, void *priv,
			struct v4l2_jpegcompression *jpegcomp)
{
	struct jpeg_ctx *ctx = priv;
	ctx->param.enc_param.quality = jpegcomp->quality;
	return 0;
}

static int jpeg_dec_vidioc_s_ctrl(struct file *file, void *priv,
			struct v4l2_control *ctrl)
{
	struct jpeg_ctx *ctx = priv;
/*
*	0 : input/output noncacheable
*	1 : input/output cacheable
*	2 : input cacheable / output noncacheable
*	3 : input noncacheable / output cacheable
*/
	switch (ctrl->id) {
	case V4L2_CID_CACHEABLE:
		if (ctrl->value == 0) {
			ctx->input_cacheable = 0;
			ctx->output_cacheable = 0;
		} else if (ctrl->value == 1) {
			ctx->input_cacheable = 1;
			ctx->output_cacheable = 1;
		} else if (ctrl->value == 2) {
			ctx->input_cacheable = 1;
			ctx->output_cacheable = 0;
		} else if (ctrl->value == 3) {
			ctx->input_cacheable = 0;
			ctx->output_cacheable = 1;
		} else {
			ctx->input_cacheable = 0;
			ctx->output_cacheable = 0;
		}
		break;
	default:
		v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n");
		break;
	}

	return 0;
}

static const struct v4l2_ioctl_ops jpeg_dec_ioctl_ops = {
	.vidioc_querycap		= jpeg_dec_vidioc_querycap,

	.vidioc_enum_fmt_vid_cap_mplane	= jpeg_dec_vidioc_enum_fmt,
	.vidioc_enum_fmt_vid_out_mplane	= jpeg_dec_vidioc_enum_fmt,

	.vidioc_g_fmt_vid_cap_mplane	= jpeg_dec_vidioc_g_fmt,
	.vidioc_g_fmt_vid_out_mplane	= jpeg_dec_vidioc_g_fmt,

	.vidioc_try_fmt_vid_cap_mplane		= jpeg_dec_vidioc_try_fmt,
	.vidioc_try_fmt_vid_out_mplane		= jpeg_dec_vidioc_try_fmt,
	.vidioc_s_fmt_vid_cap_mplane		= jpeg_dec_vidioc_s_fmt_cap,
	.vidioc_s_fmt_vid_out_mplane		= jpeg_dec_vidioc_s_fmt_out,

	.vidioc_reqbufs		= jpeg_dec_m2m_reqbufs,
	.vidioc_querybuf		= jpeg_dec_m2m_querybuf,
	.vidioc_qbuf			= jpeg_dec_m2m_qbuf,
	.vidioc_dqbuf			= jpeg_dec_m2m_dqbuf,
	.vidioc_streamon		= jpeg_dec_m2m_streamon,
	.vidioc_streamoff		= jpeg_dec_m2m_streamoff,
	.vidioc_s_jpegcomp		= vidioc_dec_s_jpegcomp,
	.vidioc_s_ctrl			= jpeg_dec_vidioc_s_ctrl,
};
const struct v4l2_ioctl_ops *get_jpeg_dec_v4l2_ioctl_ops(void)
{
	return &jpeg_dec_ioctl_ops;
}