| /* linux/drivers/media/video/exynos/jpeg/jpeg_dev.c |
| * |
| * Copyright (c) 2012 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 <mach/irqs.h> |
| |
| #include <media/v4l2-ioctl.h> |
| |
| #include "jpeg_core.h" |
| #include "jpeg_dev.h" |
| |
| #include "jpeg_mem.h" |
| #include "jpeg_regs.h" |
| #include "regs_jpeg_v2_x.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_CAPTURE, |
| }, { |
| .name = "JPEG compressed format", |
| .fourcc = V4L2_PIX_FMT_JPEG_422, |
| .depth = {8}, |
| .color = JPEG_422, |
| .memplanes = 1, |
| .types = M2M_CAPTURE, |
| }, { |
| .name = "JPEG compressed format", |
| .fourcc = V4L2_PIX_FMT_JPEG_420, |
| .depth = {8}, |
| .color = JPEG_420, |
| .memplanes = 1, |
| .types = M2M_CAPTURE, |
| }, { |
| .name = "JPEG compressed format", |
| .fourcc = V4L2_PIX_FMT_JPEG_GRAY, |
| .depth = {8}, |
| .color = JPEG_GRAY, |
| .memplanes = 1, |
| .types = M2M_CAPTURE, |
| }, { |
| .name = "RGB565", |
| .fourcc = V4L2_PIX_FMT_RGB565X, |
| .depth = {16}, |
| .color = RGB_565, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:4:4 packed, Y/CbCr", |
| .fourcc = V4L2_PIX_FMT_YUV444_2P, |
| .depth = {24}, |
| .color = YCBCR_444_2P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:4:4 packed, Y/CrCb", |
| .fourcc = V4L2_PIX_FMT_YVU444_2P, |
| .depth = {24}, |
| .color = YCRCB_444_2P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:4:4 packed, Y/Cb/Cr", |
| .fourcc = V4L2_PIX_FMT_YUV444_3P, |
| .depth = {24}, |
| .color = YCBCR_444_3P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "XRGB-8-8-8-8, 32 bpp", |
| .fourcc = V4L2_PIX_FMT_RGB32, |
| .depth = {32}, |
| .color = RGB_888, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 packed, YCrYCb", |
| .fourcc = V4L2_PIX_FMT_YVYU, |
| .depth = {16}, |
| .color = YCRYCB_422_1P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 packed, CrYCbY", |
| .fourcc = V4L2_PIX_FMT_VYUY, |
| .depth = {16}, |
| .color = CRYCBY_422_1P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 packed, CbYCrY", |
| .fourcc = V4L2_PIX_FMT_UYVY, |
| .depth = {16}, |
| .color = CBYCRY_422_1P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 packed, YCbYCr", |
| .fourcc = V4L2_PIX_FMT_YUYV, |
| .depth = {16}, |
| .color = YCBYCR_422_1P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 planar, Y/CrCb", |
| .fourcc = V4L2_PIX_FMT_NV61, |
| .depth = {16}, |
| .color = YCRCB_422_2P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 planar, Y/CbCr", |
| .fourcc = V4L2_PIX_FMT_NV16, |
| .depth = {16}, |
| .color = YCBCR_422_2P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:0 planar, Y/CbCr", |
| .fourcc = V4L2_PIX_FMT_NV12, |
| .depth = {12}, |
| .color = YCBCR_420_2P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:0 planar, Y/CrCb", |
| .fourcc = V4L2_PIX_FMT_NV21, |
| .depth = {12}, |
| .color = YCRCB_420_2P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:0 contiguous 3-planar, Y/Cb/Cr", |
| .fourcc = V4L2_PIX_FMT_YUV420, |
| .depth = {12}, |
| .color = YCBCR_420_3P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:0 contiguous 3-planar, Y/Cr/Cb", |
| .fourcc = V4L2_PIX_FMT_YVU420, |
| .depth = {12}, |
| .color = YCRCB_420_3P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "Gray", |
| .fourcc = V4L2_PIX_FMT_GREY, |
| .depth = {8}, |
| .color = GRAY, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 packed, CrYCbY", |
| .fourcc = V4L2_PIX_FMT_VYUY, |
| .depth = {16}, |
| .color = CRYCBY_422_1P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "YUV 4:2:2 packed, CbYCrY", |
| .fourcc = V4L2_PIX_FMT_UYVY, |
| .depth = {16}, |
| .color = CRYCBY_422_1P, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, { |
| .name = "XBGR-8-8-8-8, 32 bpp", |
| .fourcc = V4L2_PIX_FMT_BGR32, |
| .depth = {32}, |
| .color = BGR_888, |
| .memplanes = 1, |
| .types = M2M_OUTPUT, |
| }, |
| }; |
| |
| 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_enc_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_enc_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->fourcc; |
| |
| return 0; |
| } |
| |
| int jpeg_enc_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_enc_param *enc_param = &ctx->param.enc_param; |
| |
| pixm = &f->fmt.pix_mp; |
| |
| pixm->field = V4L2_FIELD_NONE; |
| |
| if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { |
| pixm->pixelformat = |
| enc_param->in_fmt; |
| pixm->num_planes = |
| enc_param->in_plane; |
| pixm->width = |
| enc_param->in_width; |
| pixm->height = |
| enc_param->in_height; |
| } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { |
| pixm->pixelformat = |
| enc_param->out_fmt; |
| pixm->num_planes = |
| enc_param->out_plane; |
| pixm->width = |
| enc_param->out_width; |
| pixm->height = |
| enc_param->out_height; |
| } else { |
| v4l2_err(&ctx->dev->v4l2_dev, |
| "Wrong buffer/video queue type (%d)\n", f->type); |
| } |
| |
| return 0; |
| } |
| |
| static int jpeg_enc_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_enc_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_enc_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; |
| } |
| |
| 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.enc_param.out_width = pix->height; |
| ctx->param.enc_param.out_height = pix->width; |
| ctx->param.enc_param.out_plane = fmt->memplanes; |
| ctx->param.enc_param.out_depth = fmt->depth[0]; |
| ctx->param.enc_param.out_fmt = fmt->color; |
| |
| return 0; |
| } |
| |
| static int jpeg_enc_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_enc_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.enc_param.in_depth[i] = fmt->depth[i]; |
| } |
| ctx->param.enc_param.in_width = pix->width; |
| ctx->param.enc_param.in_height = pix->height; |
| ctx->param.enc_param.in_plane = fmt->memplanes; |
| ctx->param.enc_param.in_fmt = fmt->color; |
| |
| return 0; |
| } |
| |
| static int jpeg_enc_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_enc_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_enc_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_enc_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_enc_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_enc_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_enc_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 vidioc_enc_g_jpegcomp(struct file *file, void *priv, |
| struct v4l2_jpegcompression *jpegcomp) |
| { |
| struct jpeg_ctx *ctx = priv; |
| |
| jpegcomp->quality = ctx->param.enc_param.quality; |
| return 0; |
| } |
| |
| static int jpeg_enc_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_enc_ioctl_ops = { |
| .vidioc_querycap = jpeg_enc_vidioc_querycap, |
| |
| .vidioc_enum_fmt_vid_cap_mplane = jpeg_enc_vidioc_enum_fmt, |
| .vidioc_enum_fmt_vid_out_mplane = jpeg_enc_vidioc_enum_fmt, |
| |
| .vidioc_g_fmt_vid_cap_mplane = jpeg_enc_vidioc_g_fmt, |
| .vidioc_g_fmt_vid_out_mplane = jpeg_enc_vidioc_g_fmt, |
| |
| .vidioc_try_fmt_vid_cap_mplane = jpeg_enc_vidioc_try_fmt, |
| .vidioc_try_fmt_vid_out_mplane = jpeg_enc_vidioc_try_fmt, |
| .vidioc_s_fmt_vid_cap_mplane = jpeg_enc_vidioc_s_fmt_cap, |
| .vidioc_s_fmt_vid_out_mplane = jpeg_enc_vidioc_s_fmt_out, |
| |
| .vidioc_reqbufs = jpeg_enc_m2m_reqbufs, |
| .vidioc_querybuf = jpeg_enc_m2m_querybuf, |
| .vidioc_qbuf = jpeg_enc_m2m_qbuf, |
| .vidioc_dqbuf = jpeg_enc_m2m_dqbuf, |
| .vidioc_streamon = jpeg_enc_m2m_streamon, |
| .vidioc_streamoff = jpeg_enc_m2m_streamoff, |
| .vidioc_g_jpegcomp = vidioc_enc_g_jpegcomp, |
| .vidioc_s_jpegcomp = vidioc_enc_s_jpegcomp, |
| .vidioc_s_ctrl = jpeg_enc_vidioc_s_ctrl, |
| }; |
| const struct v4l2_ioctl_ops *get_jpeg_enc_v4l2_ioctl_ops(void) |
| { |
| return &jpeg_enc_ioctl_ops; |
| } |