blob: 55e26467d72b6f56f7992b27cde59accfbcda48f [file] [log] [blame]
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
#include <linux/vmalloc.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ion.h>
#include <linux/msm_ion.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/compat.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-core.h>
#include <media/v4l2-mem2mem.h>
#include <media/msm_jpeg_dma.h>
#include <linux/clk/msm-clk.h>
#include "msm_jpeg_dma_dev.h"
#include "msm_jpeg_dma_hw.h"
#include "cam_hw_ops.h"
#define MSM_JPEGDMA_DRV_NAME "msm_jpegdma"
/* Jpeg dma stream off timeout */
#define MSM_JPEGDMA_STREAM_OFF_TIMEOUT_MS 500
/* Jpeg dma formats lookup table */
static struct msm_jpegdma_format formats[] = {
{
.name = "Greyscale",
.fourcc = V4L2_PIX_FMT_GREY,
.depth = 8,
.num_planes = 1,
.colplane_h = 1,
.colplane_v = 1,
.h_align = 1,
.v_align = 1,
.planes[0] = JPEGDMA_PLANE_TYPE_Y,
},
{
.name = "Y/CbCr 4:2:0",
.fourcc = V4L2_PIX_FMT_NV12,
.depth = 12,
.num_planes = 2,
.colplane_h = 1,
.colplane_v = 2,
.h_align = 2,
.v_align = 2,
.planes[0] = JPEGDMA_PLANE_TYPE_Y,
.planes[1] = JPEGDMA_PLANE_TYPE_CBCR,
},
{
.name = "Y/CrCb 4:2:0",
.fourcc = V4L2_PIX_FMT_NV21,
.depth = 12,
.num_planes = 2,
.colplane_h = 1,
.colplane_v = 2,
.h_align = 2,
.v_align = 2,
.planes[0] = JPEGDMA_PLANE_TYPE_Y,
.planes[1] = JPEGDMA_PLANE_TYPE_CBCR,
},
{
.name = "YVU 4:2:0 planar, YCrCb",
.fourcc = V4L2_PIX_FMT_YVU420,
.depth = 12,
.num_planes = 3,
.colplane_h = 1,
.colplane_v = 4,
.h_align = 2,
.v_align = 2,
.planes[0] = JPEGDMA_PLANE_TYPE_Y,
.planes[1] = JPEGDMA_PLANE_TYPE_CR,
.planes[2] = JPEGDMA_PLANE_TYPE_CB,
},
{
.name = "YUV 4:2:0 planar, YCbCr",
.fourcc = V4L2_PIX_FMT_YUV420,
.depth = 12,
.num_planes = 3,
.colplane_h = 1,
.colplane_v = 4,
.h_align = 2,
.v_align = 2,
.planes[0] = JPEGDMA_PLANE_TYPE_Y,
.planes[1] = JPEGDMA_PLANE_TYPE_CB,
.planes[2] = JPEGDMA_PLANE_TYPE_CR,
},
};
/*
* msm_jpegdma_ctx_from_fh - Get dma context from v4l2 fh.
* @fh: Pointer to v4l2 fh.
*/
static inline struct jpegdma_ctx *msm_jpegdma_ctx_from_fh(struct v4l2_fh *fh)
{
return container_of(fh, struct jpegdma_ctx, fh);
}
/*
* msm_jpegdma_get_next_config_idx - get next configuration index.
* @ctx: Pointer to jpegdma context.
*/
static inline int msm_jpegdma_get_next_config_idx(struct jpegdma_ctx *ctx)
{
return (ctx->config_idx + 1) % MSM_JPEGDMA_MAX_CONFIGS;
}
/*
* msm_jpegdma_schedule_next_config - Schedule next configuration.
* @ctx: Pointer to jpegdma context.
*/
static inline void msm_jpegdma_schedule_next_config(struct jpegdma_ctx *ctx)
{
ctx->config_idx = (ctx->config_idx + 1) % MSM_JPEGDMA_MAX_CONFIGS;
}
/*
* msm_jpegdma_cast_long_to_buff_ptr - Cast long to buffer pointer.
* @vaddr: vaddr as long
* @buff_ptr_head: buffer pointer head
*/
static inline void msm_jpegdma_cast_long_to_buff_ptr(unsigned long vaddr,
struct msm_jpeg_dma_buff __user **buff_ptr_head)
{
#ifdef CONFIG_COMPAT
*buff_ptr_head = compat_ptr(vaddr);
#else
*buff_ptr_head = (struct msm_jpeg_dma_buff *) vaddr;
#endif
}
/*
* msm_jpegdma_get_format_idx - Get jpeg dma format lookup index.
* @ctx: Pointer to dma ctx.
* @f: v4l2 format.
*/
static int msm_jpegdma_get_format_idx(struct jpegdma_ctx *ctx,
struct v4l2_format *f)
{
int i;
for (i = 0; i < ARRAY_SIZE(formats); i++)
if (formats[i].fourcc == f->fmt.pix.pixelformat)
break;
if (i == ARRAY_SIZE(formats))
return -EINVAL;
return i;
}
/*
* msm_jpegdma_fill_size_from_ctx - Fill jpeg dma format lookup index.
* @ctx: Pointer to dma ctx.
* @size: Size config.
*/
static void msm_jpegdma_fill_size_from_ctx(struct jpegdma_ctx *ctx,
struct msm_jpegdma_size_config *size)
{
size->in_size.top = ctx->crop.top;
size->in_size.left = ctx->crop.left;
size->in_size.width = ctx->crop.width;
size->in_size.height = ctx->crop.height;
size->in_size.scanline = ctx->format_out.fmt.pix.height;
size->in_size.stride = ctx->format_out.fmt.pix.bytesperline;
size->out_size.top = 0;
size->out_size.left = 0;
size->out_size.width = ctx->format_cap.fmt.pix.width;
size->out_size.height = ctx->format_cap.fmt.pix.height;
size->out_size.scanline = ctx->format_cap.fmt.pix.height;
size->out_size.stride = ctx->format_cap.fmt.pix.bytesperline;
}
/*
* msm_jpegdma_align_format - Align jpeg dma format.
* @f: v4l2 format.
* @format_idx: format lookup index.
*/
static void msm_jpegdma_align_format(struct v4l2_format *f, int format_idx)
{
unsigned int size_image;
int i;
if (f->fmt.pix.width > MSM_JPEGDMA_MAX_WIDTH)
f->fmt.pix.width = MSM_JPEGDMA_MAX_WIDTH;
if (f->fmt.pix.width < MSM_JPEGDMA_MIN_WIDTH)
f->fmt.pix.width = MSM_JPEGDMA_MIN_WIDTH;
if (f->fmt.pix.height > MSM_JPEGDMA_MAX_HEIGHT)
f->fmt.pix.height = MSM_JPEGDMA_MAX_HEIGHT;
if (f->fmt.pix.height < MSM_JPEGDMA_MIN_HEIGHT)
f->fmt.pix.height = MSM_JPEGDMA_MIN_HEIGHT;
if (formats[format_idx].h_align > 1)
f->fmt.pix.width &= ~(formats[format_idx].h_align - 1);
if (formats[format_idx].v_align > 1)
f->fmt.pix.height &= ~(formats[format_idx].v_align - 1);
if (f->fmt.pix.bytesperline < f->fmt.pix.width)
f->fmt.pix.bytesperline = f->fmt.pix.width;
f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
MSM_JPEGDMA_STRIDE_ALIGN);
f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
size_image = f->fmt.pix.bytesperline * f->fmt.pix.height;
if (formats[format_idx].num_planes > 1)
for (i = 1; i < formats[format_idx].num_planes; i++)
size_image += (f->fmt.pix.bytesperline *
(f->fmt.pix.height /
formats[format_idx].colplane_v));
f->fmt.pix.sizeimage = size_image;
f->fmt.pix.field = V4L2_FIELD_NONE;
}
/*
* msm_jpegdma_config_ok - Check if jpeg dma format is ok for processing.
* @ctx: Pointer to dma ctx.
*/
static int msm_jpegdma_config_ok(struct jpegdma_ctx *ctx)
{
int ret;
int cap_idx;
int out_idx;
struct msm_jpegdma_size_config size;
cap_idx = msm_jpegdma_get_format_idx(ctx, &ctx->format_cap);
if (cap_idx < 0)
return 0;
out_idx = msm_jpegdma_get_format_idx(ctx, &ctx->format_out);
if (out_idx < 0)
return 0;
/* jpeg dma can not convert formats */
if (cap_idx != out_idx)
return 0;
msm_jpegdma_fill_size_from_ctx(ctx, &size);
size.format = formats[ctx->format_idx];
ret = msm_jpegdma_hw_check_config(ctx->jdma_device, &size);
if (ret < 0)
return 0;
return 1;
}
/*
* msm_jpegdma_update_hw_config - Update dma hw configuration/
* @ctx: Pointer to dma ctx.
*/
static int msm_jpegdma_update_hw_config(struct jpegdma_ctx *ctx)
{
struct msm_jpegdma_size_config size;
int idx;
int ret = 0;
if (msm_jpegdma_config_ok(ctx)) {
size.fps = ctx->timeperframe.denominator /
ctx->timeperframe.numerator;
size.in_offset = ctx->in_offset;
size.out_offset = ctx->out_offset;
size.format = formats[ctx->format_idx];
msm_jpegdma_fill_size_from_ctx(ctx, &size);
idx = msm_jpegdma_get_next_config_idx(ctx);
ret = msm_jpegdma_hw_set_config(ctx->jdma_device,
&size, &ctx->plane_config[idx]);
if (ret < 0)
dev_err(ctx->jdma_device->dev, "Can not get hw cfg\n");
else
ctx->pending_config = 1;
}
return ret;
}
/*
* msm_jpegdma_queue_setup - vb2_ops queue_setup callback.
* @q: Pointer to vb2 queue struct.
* @fmt: Pointer to v4l2 format struct (NULL is valid argument).
* @num_buffers: Pointer of number of buffers requested.
* @num_planes: Pointer to number of planes requested.
* @sizes: Array containing sizes of planes.
* @alloc_ctxs: Array of allocated contexts for each plane.
*/
static int msm_jpegdma_queue_setup(struct vb2_queue *q,
// const void *parg,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_ctxs[])
{
struct jpegdma_ctx *ctx = vb2_get_drv_priv(q);
//struct v4l2_format *fmt = (struct v4l2_format *)parg;
struct v4l2_format *fmt = NULL;
if (fmt == NULL) {
switch (q->type) {
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
sizes[0] = ctx->format_out.fmt.pix.sizeimage;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
sizes[0] = ctx->format_cap.fmt.pix.sizeimage;
break;
default:
return -EINVAL;
}
} else {
sizes[0] = fmt->fmt.pix.sizeimage;
}
*num_planes = 1;
alloc_ctxs[0] = (struct device *)ctx->jdma_device;
return 0;
}
/*
* msm_jpegdma_buf_queue - vb2_ops buf_queue callback.
* @vb: Pointer to vb2 buffer struct.
*/
static void msm_jpegdma_buf_queue(struct vb2_buffer *vb)
{
struct jpegdma_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vb2_v4l2_buf = to_vb2_v4l2_buffer(vb);
v4l2_m2m_buf_queue(ctx->m2m_ctx, vb2_v4l2_buf);
}
/*
* msm_jpegdma_start_streaming - vb2_ops start_streaming callback.
* @q: Pointer to vb2 queue struct.
* @count: Number of buffer queued before stream on call.
*/
static int msm_jpegdma_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct jpegdma_ctx *ctx = vb2_get_drv_priv(q);
int ret;
ret = msm_jpegdma_hw_get(ctx->jdma_device);
if (ret < 0) {
dev_err(ctx->jdma_device->dev, "Fail to get dma hw\n");
return ret;
}
if (!atomic_read(&ctx->active)) {
ret = msm_jpegdma_update_hw_config(ctx);
if (ret < 0) {
dev_err(ctx->jdma_device->dev, "Fail to configure hw\n");
return ret;
}
atomic_set(&ctx->active, 1);
}
return 0;
}
/*
* msm_jpegdma_stop_streaming - vb2_ops stop_streaming callback.
* @q: Pointer to vb2 queue struct.
*/
static void msm_jpegdma_stop_streaming(struct vb2_queue *q)
{
struct jpegdma_ctx *ctx = vb2_get_drv_priv(q);
unsigned long time;
int ret = 0;
atomic_set(&ctx->active, 0);
time = wait_for_completion_timeout(&ctx->completion,
msecs_to_jiffies(MSM_JPEGDMA_STREAM_OFF_TIMEOUT_MS));
if (!time) {
dev_err(ctx->jdma_device->dev, "Ctx wait timeout\n");
ret = -ETIME;
}
if (ctx->jdma_device->ref_count > 0)
msm_jpegdma_hw_put(ctx->jdma_device);
}
/* Videobuf2 queue callbacks. */
static struct vb2_ops msm_jpegdma_vb2_q_ops = {
.queue_setup = msm_jpegdma_queue_setup,
.buf_queue = msm_jpegdma_buf_queue,
.start_streaming = msm_jpegdma_start_streaming,
.stop_streaming = msm_jpegdma_stop_streaming,
};
/*
* msm_jpegdma_get_userptr - Map and get buffer handler for user pointer buffer.
* @alloc_ctx: Contexts allocated in buf_setup.
* @vaddr: Virtual addr passed from userpsace (in our case ion fd)
* @size: Size of the buffer
* @write: True if buffer will be used for writing the data.
*/
static void *msm_jpegdma_get_userptr(struct device *alloc_ctx,
unsigned long vaddr, unsigned long size,
enum dma_data_direction dma_dir)
{
struct msm_jpegdma_device *dma = (void *)alloc_ctx;
struct msm_jpegdma_buf_handle *buf;
struct msm_jpeg_dma_buff __user *up_buff;
struct msm_jpeg_dma_buff kp_buff;
int ret;
msm_jpegdma_cast_long_to_buff_ptr(vaddr, &up_buff);
if (!access_ok(VERIFY_READ, up_buff,
sizeof(struct msm_jpeg_dma_buff)) ||
get_user(kp_buff.fd, &up_buff->fd)) {
dev_err(dma->dev, "Error getting user data\n");
return ERR_PTR(-ENOMEM);
}
if (!access_ok(VERIFY_WRITE, up_buff,
sizeof(struct msm_jpeg_dma_buff)) ||
put_user(kp_buff.fd, &up_buff->fd)) {
dev_err(dma->dev, "Error putting user data\n");
return ERR_PTR(-ENOMEM);
}
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
ret = msm_jpegdma_hw_map_buffer(dma, kp_buff.fd, buf);
if (ret < 0 || buf->size < size)
goto error;
return buf;
error:
kzfree(buf);
return ERR_PTR(-ENOMEM);
}
/*
* msm_jpegdma_put_userptr - Unmap and free buffer handler.
* @buf_priv: Buffer handler allocated get_userptr callback.
*/
static void msm_jpegdma_put_userptr(void *buf_priv)
{
if (IS_ERR_OR_NULL(buf_priv))
return;
msm_jpegdma_hw_unmap_buffer(buf_priv);
kzfree(buf_priv);
}
/* Videobuf2 memory callbacks. */
static struct vb2_mem_ops msm_jpegdma_vb2_mem_ops = {
.get_userptr = msm_jpegdma_get_userptr,
.put_userptr = msm_jpegdma_put_userptr,
};
/*
* msm_jpegdma_queue_init - m2m_ops queue_setup callback.
* @priv: Pointer to jpegdma ctx.
* @src_vq: vb2 source queue.
* @dst_vq: vb2 destination queue.
*/
static int msm_jpegdma_queue_init(void *priv, struct vb2_queue *src_vq,
struct vb2_queue *dst_vq)
{
struct jpegdma_ctx *ctx = priv;
int ret;
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
src_vq->io_modes = VB2_USERPTR;
src_vq->drv_priv = ctx;
src_vq->mem_ops = &msm_jpegdma_vb2_mem_ops;
src_vq->ops = &msm_jpegdma_vb2_q_ops;
src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
ret = vb2_queue_init(src_vq);
if (ret) {
dev_err(ctx->jdma_device->dev, "Can not init src queue\n");
return ret;
}
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
dst_vq->io_modes = VB2_USERPTR;
dst_vq->drv_priv = ctx;
dst_vq->mem_ops = &msm_jpegdma_vb2_mem_ops;
dst_vq->ops = &msm_jpegdma_vb2_q_ops;
dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
ret = vb2_queue_init(dst_vq);
if (ret) {
dev_err(ctx->jdma_device->dev, "Can not init dst queue\n");
return ret;
}
return 0;
}
/*
* msm_jpegdma_open - Fd device open method.
* @file: Pointer to file struct.
*/
static int msm_jpegdma_open(struct file *file)
{
struct msm_jpegdma_device *device = video_drvdata(file);
struct video_device *video = video_devdata(file);
struct jpegdma_ctx *ctx;
int ret;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->lock);
ctx->jdma_device = device;
dev_dbg(ctx->jdma_device->dev, "Jpeg v4l2 dma open\n");
/* Set ctx defaults */
ctx->timeperframe.numerator = 1;
ctx->timeperframe.denominator = MSM_JPEGDMA_DEFAULT_FPS;
atomic_set(&ctx->active, 0);
v4l2_fh_init(&ctx->fh, video);
file->private_data = &ctx->fh;
v4l2_fh_add(&ctx->fh);
ctx->m2m_ctx = v4l2_m2m_ctx_init(device->m2m_dev,
ctx, msm_jpegdma_queue_init);
if (IS_ERR_OR_NULL(ctx->m2m_ctx)) {
ret = PTR_ERR(ctx->m2m_ctx);
goto error_m2m_init;
}
ret = cam_config_ahb_clk(NULL, 0, CAM_AHB_CLIENT_JPEG,
CAM_AHB_SVS_VOTE);
if (ret < 0) {
pr_err("%s: failed to vote for AHB\n", __func__);
goto ahb_vote_fail;
}
init_completion(&ctx->completion);
complete_all(&ctx->completion);
dev_dbg(ctx->jdma_device->dev, "Jpeg v4l2 dma open success\n");
return 0;
ahb_vote_fail:
v4l2_m2m_ctx_release(ctx->m2m_ctx);
error_m2m_init:
v4l2_fh_del(&ctx->fh);
v4l2_fh_exit(&ctx->fh);
kfree(ctx);
return ret;
}
/*
* msm_jpegdma_release - Fd device release method.
* @file: Pointer to file struct.
*/
static int msm_jpegdma_release(struct file *file)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(file->private_data);
/* release all the resources */
if (ctx->jdma_device->ref_count > 0)
msm_jpegdma_hw_put(ctx->jdma_device);
atomic_set(&ctx->active, 0);
complete_all(&ctx->completion);
v4l2_m2m_ctx_release(ctx->m2m_ctx);
v4l2_fh_del(&ctx->fh);
v4l2_fh_exit(&ctx->fh);
kfree(ctx);
if (cam_config_ahb_clk(NULL, 0, CAM_AHB_CLIENT_JPEG,
CAM_AHB_SUSPEND_VOTE) < 0)
pr_err("%s: failed to remove vote for AHB\n", __func__);
return 0;
}
/*
* msm_jpegdma_poll - Fd device pool method.
* @file: Pointer to file struct.
* @wait: Pointer to pool table struct.
*/
static unsigned int msm_jpegdma_poll(struct file *file,
struct poll_table_struct *wait)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(file->private_data);
return v4l2_m2m_poll(file, ctx->m2m_ctx, wait);
}
/* Dma device file operations callbacks */
static const struct v4l2_file_operations fd_fops = {
.owner = THIS_MODULE,
.open = msm_jpegdma_open,
.release = msm_jpegdma_release,
.poll = msm_jpegdma_poll,
.unlocked_ioctl = video_ioctl2,
};
/*
* msm_jpegdma_querycap - V4l2 ioctl query capability handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @cap: Pointer to v4l2_capability struct need to be filled.
*/
static int msm_jpegdma_querycap(struct file *file,
void *fh, struct v4l2_capability *cap)
{
cap->bus_info[0] = 0;
strlcpy(cap->driver, MSM_JPEGDMA_DRV_NAME, sizeof(cap->driver));
strlcpy(cap->card, MSM_JPEGDMA_DRV_NAME, sizeof(cap->card));
cap->capabilities = V4L2_CAP_STREAMING |
V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_CAPTURE;
return 0;
}
/*
* msm_jpegdma_enum_fmt_vid_cap - V4l2 ioctl enumerate output format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_fmtdesc struct need to be filled.
*/
static int msm_jpegdma_enum_fmt_vid_cap(struct file *file,
void *fh, struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
f->pixelformat = formats[f->index].fourcc;
strlcpy(f->description, formats[f->index].name,
sizeof(f->description));
return 0;
}
/*
* msm_jpegdma_enum_fmt_vid_out - V4l2 ioctl enumerate capture format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_fmtdesc struct need to be filled.
*/
static int msm_jpegdma_enum_fmt_vid_out(struct file *file,
void *fh, struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
f->pixelformat = formats[f->index].fourcc;
strlcpy(f->description, formats[f->index].name,
sizeof(f->description));
return 0;
}
/*
* msm_jpegdma_g_fmt_cap - V4l2 ioctl get capture format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_format struct need to be filled.
*/
static int msm_jpegdma_g_fmt_cap(struct file *file, void *fh,
struct v4l2_format *f)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
*f = ctx->format_cap;
return 0;
}
/*
* msm_jpegdma_g_fmt_out - V4l2 ioctl get output format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_format struct need to be filled.
*/
static int msm_jpegdma_g_fmt_out(struct file *file, void *fh,
struct v4l2_format *f)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
*f = ctx->format_out;
return 0;
}
/*
* msm_jpegdma_try_fmt_vid_cap - V4l2 ioctl try capture format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_format struct.
*/
static int msm_jpegdma_try_fmt_vid_cap(struct file *file,
void *fh, struct v4l2_format *f)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
msm_jpegdma_align_format(f, ctx->format_idx);
return 0;
}
/*
* msm_jpegdma_try_fmt_vid_out - V4l2 ioctl try output format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_format struct.
*/
static int msm_jpegdma_try_fmt_vid_out(struct file *file,
void *fh, struct v4l2_format *f)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
msm_jpegdma_align_format(f, ctx->format_idx);
return 0;
}
/*
* msm_jpegdma_s_fmt_vid_cap - V4l2 ioctl set capture format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_format struct.
*/
static int msm_jpegdma_s_fmt_vid_cap(struct file *file,
void *fh, struct v4l2_format *f)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
int ret;
ret = msm_jpegdma_get_format_idx(ctx, f);
if (ret < 0)
return -EINVAL;
ctx->format_idx = ret;
msm_jpegdma_align_format(f, ctx->format_idx);
/* Initialize crop with output height */
ctx->crop.top = 0;
ctx->crop.left = 0;
ctx->crop.width = ctx->format_out.fmt.pix.width;
ctx->crop.height = ctx->format_out.fmt.pix.height;
ctx->format_cap = *f;
return 0;
}
/*
* msm_jpegdma_s_fmt_vid_out - V4l2 ioctl set output format handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @f: Pointer to v4l2_format struct.
*/
static int msm_jpegdma_s_fmt_vid_out(struct file *file,
void *fh, struct v4l2_format *f)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
int ret;
ret = msm_jpegdma_get_format_idx(ctx, f);
if (ret < 0)
return -EINVAL;
ctx->format_idx = ret;
msm_jpegdma_align_format(f, ctx->format_idx);
/* Initialize crop */
ctx->crop.top = 0;
ctx->crop.left = 0;
ctx->crop.width = f->fmt.pix.width;
ctx->crop.height = f->fmt.pix.height;
ctx->format_out = *f;
return 0;
}
/*
* msm_jpegdma_reqbufs - V4l2 ioctl request buffers handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @req: Pointer to v4l2_requestbuffer struct.
*/
static int msm_jpegdma_reqbufs(struct file *file,
void *fh, struct v4l2_requestbuffers *req)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, req);
}
/*
* msm_jpegdma_qbuf - V4l2 ioctl queue buffer handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @buf: Pointer to v4l2_buffer struct.
*/
static int msm_jpegdma_qbuf(struct file *file, void *fh,
struct v4l2_buffer *buf)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
struct msm_jpeg_dma_buff __user *up_buff;
struct msm_jpeg_dma_buff kp_buff;
int ret;
msm_jpegdma_cast_long_to_buff_ptr(buf->m.userptr, &up_buff);
mutex_lock(&ctx->lock);
if (!access_ok(VERIFY_READ, up_buff,
sizeof(struct msm_jpeg_dma_buff)) ||
get_user(kp_buff.fd, &up_buff->fd) ||
get_user(kp_buff.offset, &up_buff->offset)) {
dev_err(ctx->jdma_device->dev, "Error getting user data\n");
mutex_unlock(&ctx->lock);
return -EFAULT;
}
if (!access_ok(VERIFY_WRITE, up_buff,
sizeof(struct msm_jpeg_dma_buff)) ||
put_user(kp_buff.fd, &up_buff->fd) ||
put_user(kp_buff.offset, &up_buff->offset)) {
dev_err(ctx->jdma_device->dev, "Error putting user data\n");
mutex_unlock(&ctx->lock);
return -EFAULT;
}
switch (buf->type) {
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
ctx->in_offset = kp_buff.offset;
dev_dbg(ctx->jdma_device->dev, "input buf offset %d\n",
ctx->in_offset);
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
ctx->out_offset = kp_buff.offset;
dev_dbg(ctx->jdma_device->dev, "output buf offset %d\n",
ctx->out_offset);
break;
}
if (atomic_read(&ctx->active))
ret = msm_jpegdma_update_hw_config(ctx);
ret = v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf);
if (ret < 0)
dev_err(ctx->jdma_device->dev, "QBuf fail\n");
mutex_unlock(&ctx->lock);
return ret;
}
/*
* msm_jpegdma_dqbuf - V4l2 ioctl dequeue buffer handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @buf: Pointer to v4l2_buffer struct.
*/
static int msm_jpegdma_dqbuf(struct file *file,
void *fh, struct v4l2_buffer *buf)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf);
}
/*
* msm_jpegdma_streamon - V4l2 ioctl stream on handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @buf_type: V4l2 buffer type.
*/
static int msm_jpegdma_streamon(struct file *file,
void *fh, enum v4l2_buf_type buf_type)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
int ret;
if (!msm_jpegdma_config_ok(ctx))
return -EINVAL;
ret = v4l2_m2m_streamon(file, ctx->m2m_ctx, buf_type);
if (ret < 0)
dev_err(ctx->jdma_device->dev, "Stream on fail\n");
return ret;
}
/*
* msm_jpegdma_streamoff - V4l2 ioctl stream off handler.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @buf_type: V4l2 buffer type.
*/
static int msm_jpegdma_streamoff(struct file *file,
void *fh, enum v4l2_buf_type buf_type)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
int ret;
ret = v4l2_m2m_streamoff(file, ctx->m2m_ctx, buf_type);
if (ret < 0)
dev_err(ctx->jdma_device->dev, "Stream off fails\n");
return ret;
}
/*
* msm_jpegdma_cropcap - V4l2 ioctl crop capabilities.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @a: Pointer to v4l2_cropcap struct need to be set.
*/
static int msm_jpegdma_cropcap(struct file *file, void *fh,
struct v4l2_cropcap *a)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
struct v4l2_format *format;
switch (a->type) {
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
format = &ctx->format_out;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
format = &ctx->format_cap;
break;
default:
return -EINVAL;
}
a->bounds.top = 0;
a->bounds.left = 0;
a->bounds.width = format->fmt.pix.width;
a->bounds.height = format->fmt.pix.height;
a->defrect = ctx->crop;
a->pixelaspect.numerator = 1;
a->pixelaspect.denominator = 1;
return 0;
}
/*
* msm_jpegdma_g_crop - V4l2 ioctl get crop.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @crop: Pointer to v4l2_crop struct need to be set.
*/
static int msm_jpegdma_g_crop(struct file *file, void *fh,
struct v4l2_crop *crop)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
switch (crop->type) {
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
crop->c = ctx->crop;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
crop->c.left = 0;
crop->c.top = 0;
crop->c.width = ctx->format_cap.fmt.pix.width;
crop->c.height = ctx->format_cap.fmt.pix.height;
break;
default:
return -EINVAL;
}
return 0;
}
/*
* msm_jpegdma_s_crop - V4l2 ioctl set crop.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @crop: Pointer to v4l2_crop struct need to be set.
*/
static int msm_jpegdma_s_crop(struct file *file, void *fh,
const struct v4l2_crop *crop)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
int ret = 0;
/* Crop is supported only for input buffers */
if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
return -EINVAL;
if (crop->c.left < 0 || crop->c.top < 0 ||
crop->c.height == 0 || crop->c.width == 0)
return -EINVAL;
/* Upscale is not supported */
if (crop->c.width < ctx->format_cap.fmt.pix.width)
return -EINVAL;
if (crop->c.height < ctx->format_cap.fmt.pix.height)
return -EINVAL;
if (crop->c.width + crop->c.left > ctx->format_out.fmt.pix.width)
return -EINVAL;
if (crop->c.height + crop->c.top > ctx->format_out.fmt.pix.height)
return -EINVAL;
if (crop->c.width % formats[ctx->format_idx].h_align)
return -EINVAL;
if (crop->c.height % formats[ctx->format_idx].v_align)
return -EINVAL;
mutex_lock(&ctx->lock);
ctx->crop = crop->c;
if (atomic_read(&ctx->active))
ret = msm_jpegdma_update_hw_config(ctx);
mutex_unlock(&ctx->lock);
return ret;
}
/*
* msm_jpegdma_g_crop - V4l2 ioctl get parm.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @a: Pointer to v4l2_streamparm struct need to be filled.
*/
static int msm_jpegdma_g_parm(struct file *file, void *fh,
struct v4l2_streamparm *a)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
/* Get param is supported only for input buffers */
if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
return -EINVAL;
a->parm.output.capability = 0;
a->parm.output.extendedmode = 0;
a->parm.output.outputmode = 0;
a->parm.output.writebuffers = 0;
a->parm.output.timeperframe = ctx->timeperframe;
return 0;
}
/*
* msm_jpegdma_s_crop - V4l2 ioctl set parm.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @a: Pointer to v4l2_streamparm struct need to be set.
*/
static int msm_jpegdma_s_parm(struct file *file, void *fh,
struct v4l2_streamparm *a)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
/* Set param is supported only for input buffers */
if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
return -EINVAL;
if (!a->parm.output.timeperframe.numerator ||
!a->parm.output.timeperframe.denominator)
return -EINVAL;
/* Frame rate is not supported during streaming */
if (atomic_read(&ctx->active))
return -EINVAL;
ctx->timeperframe = a->parm.output.timeperframe;
return 0;
}
/*
* msm_fd_g_ctrl - V4l2 ioctl get control.
* @file: Pointer to file struct.
* @fh: V4l2 File handle.
* @sub: Pointer to v4l2_control struct need to be filled.
*/
static int msm_jpegdma_g_ctrl(struct file *file, void *fh,
struct v4l2_control *a)
{
struct jpegdma_ctx *ctx = msm_jpegdma_ctx_from_fh(fh);
switch (a->id) {
case V4L2_CID_JPEG_DMA_MAX_DOWN_SCALE:
a->value = msm_jpegdma_hw_get_max_downscale(ctx->jdma_device);
if (a->value < 0)
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
/* V4l2 ioctl handlers */
static const struct v4l2_ioctl_ops fd_ioctl_ops = {
.vidioc_querycap = msm_jpegdma_querycap,
.vidioc_enum_fmt_vid_out = msm_jpegdma_enum_fmt_vid_out,
.vidioc_enum_fmt_vid_cap = msm_jpegdma_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_out = msm_jpegdma_g_fmt_out,
.vidioc_g_fmt_vid_cap = msm_jpegdma_g_fmt_cap,
.vidioc_try_fmt_vid_out = msm_jpegdma_try_fmt_vid_out,
.vidioc_try_fmt_vid_cap = msm_jpegdma_try_fmt_vid_cap,
.vidioc_s_fmt_vid_out = msm_jpegdma_s_fmt_vid_out,
.vidioc_s_fmt_vid_cap = msm_jpegdma_s_fmt_vid_cap,
.vidioc_reqbufs = msm_jpegdma_reqbufs,
.vidioc_qbuf = msm_jpegdma_qbuf,
.vidioc_dqbuf = msm_jpegdma_dqbuf,
.vidioc_streamon = msm_jpegdma_streamon,
.vidioc_streamoff = msm_jpegdma_streamoff,
.vidioc_cropcap = msm_jpegdma_cropcap,
.vidioc_g_crop = msm_jpegdma_g_crop,
.vidioc_s_crop = msm_jpegdma_s_crop,
.vidioc_g_parm = msm_jpegdma_g_parm,
.vidioc_s_parm = msm_jpegdma_s_parm,
.vidioc_g_ctrl = msm_jpegdma_g_ctrl,
};
/*
* msm_jpegdma_process_buffers - Start dma processing.
* @ctx: Pointer dma context.
* @src_buf: Pointer to Vb2 source buffer.
* @dst_buf: Pointer to Vb2 destination buffer.
*/
static void msm_jpegdma_process_buffers(struct jpegdma_ctx *ctx,
struct vb2_v4l2_buffer *src_buf, struct vb2_v4l2_buffer *dst_buf)
{
struct msm_jpegdma_buf_handle *buf_handle;
struct msm_jpegdma_addr addr;
int plane_idx;
int config_idx;
buf_handle = dst_buf->vb2_buf.planes[0].mem_priv;
addr.out_addr = buf_handle->addr;
buf_handle = src_buf->vb2_buf.planes[0].mem_priv;
addr.in_addr = buf_handle->addr;
plane_idx = ctx->plane_idx;
config_idx = ctx->config_idx;
msm_jpegdma_hw_start(ctx->jdma_device, &addr,
&ctx->plane_config[config_idx].plane[plane_idx],
&ctx->plane_config[config_idx].speed);
}
/*
* msm_jpegdma_device_run - Dma device run.
* @priv: Pointer dma context.
*/
static void msm_jpegdma_device_run(void *priv)
{
struct vb2_v4l2_buffer *src_buf;
struct vb2_v4l2_buffer *dst_buf;
struct jpegdma_ctx *ctx = priv;
dev_dbg(ctx->jdma_device->dev, "Jpeg v4l2 dma device run E\n");
dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
if (src_buf == NULL || dst_buf == NULL) {
dev_err(ctx->jdma_device->dev, "Error, buffer list empty\n");
return;
}
if (ctx->pending_config) {
msm_jpegdma_schedule_next_config(ctx);
ctx->pending_config = 0;
}
msm_jpegdma_process_buffers(ctx, src_buf, dst_buf);
dev_dbg(ctx->jdma_device->dev, "Jpeg v4l2 dma device run X\n");
}
/*
* msm_jpegdma_job_abort - Dma abort job.
* @priv: Pointer dma context.
*/
static void msm_jpegdma_job_abort(void *priv)
{
struct jpegdma_ctx *ctx = priv;
msm_jpegdma_hw_abort(ctx->jdma_device);
v4l2_m2m_job_finish(ctx->jdma_device->m2m_dev, ctx->m2m_ctx);
}
/*
* msm_jpegdma_job_ready - Dma check if job is ready
* @priv: Pointer dma context.
*/
static int msm_jpegdma_job_ready(void *priv)
{
struct jpegdma_ctx *ctx = priv;
if (atomic_read(&ctx->active)) {
init_completion(&ctx->completion);
return 1;
}
return 0;
}
/* V4l2 mem2mem handlers */
static struct v4l2_m2m_ops msm_jpegdma_m2m_ops = {
.device_run = msm_jpegdma_device_run,
.job_abort = msm_jpegdma_job_abort,
.job_ready = msm_jpegdma_job_ready,
};
/*
* msm_jpegdma_isr_processing_done - Invoked by dma_hw when processing is done.
* @dma: Pointer dma device.
*/
void msm_jpegdma_isr_processing_done(struct msm_jpegdma_device *dma)
{
struct vb2_v4l2_buffer *src_buf;
struct vb2_v4l2_buffer *dst_buf;
struct jpegdma_ctx *ctx;
mutex_lock(&dma->lock);
ctx = v4l2_m2m_get_curr_priv(dma->m2m_dev);
if (ctx) {
mutex_lock(&ctx->lock);
ctx->plane_idx++;
if (ctx->plane_idx >= formats[ctx->format_idx].num_planes) {
src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx);
dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
if (src_buf == NULL || dst_buf == NULL) {
dev_err(ctx->jdma_device->dev, "Error, buffer list empty\n");
mutex_unlock(&ctx->lock);
mutex_unlock(&dma->lock);
return;
}
complete_all(&ctx->completion);
ctx->plane_idx = 0;
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
v4l2_m2m_job_finish(ctx->jdma_device->m2m_dev,
ctx->m2m_ctx);
} else {
dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
if (src_buf == NULL || dst_buf == NULL) {
dev_err(ctx->jdma_device->dev, "Error, buffer list empty\n");
mutex_unlock(&ctx->lock);
mutex_unlock(&dma->lock);
return;
}
msm_jpegdma_process_buffers(ctx, src_buf, dst_buf);
}
mutex_unlock(&ctx->lock);
}
mutex_unlock(&dma->lock);
}
/*
* jpegdma_probe - Dma device probe method.
* @pdev: Pointer Dma platform device.
*/
static int jpegdma_probe(struct platform_device *pdev)
{
struct msm_jpegdma_device *jpegdma;
int ret;
int i;
dev_dbg(&pdev->dev, "jpeg v4l2 DMA probed\n");
/* Jpeg dma device struct */
jpegdma = kzalloc(sizeof(struct msm_jpegdma_device), GFP_KERNEL);
if (!jpegdma)
return -ENOMEM;
mutex_init(&jpegdma->lock);
init_completion(&jpegdma->hw_reset_completion);
init_completion(&jpegdma->hw_halt_completion);
jpegdma->dev = &pdev->dev;
jpegdma->pdev = pdev;
if (pdev->dev.of_node)
of_property_read_u32((&pdev->dev)->of_node, "cell-index",
&pdev->id);
/* Get resources */
ret = msm_jpegdma_hw_get_mem_resources(pdev, jpegdma);
if (ret < 0)
goto error_mem_resources;
/* get all the regulators */
ret = msm_camera_get_regulator_info(pdev, &jpegdma->dma_vdd,
&jpegdma->num_reg);
if (ret < 0)
goto error_get_regulators;
/* get all the clocks */
ret = msm_camera_get_clk_info(pdev, &jpegdma->jpeg_clk_info,
&jpegdma->clk, &jpegdma->num_clk);
if (ret < 0)
goto error_get_clocks;
/*set memcore and mem periphery logic flags to 0*/
for (i = 0; i < jpegdma->num_clk; i++) {
if ((strcmp(jpegdma->jpeg_clk_info[i].clk_name,
"core_clk") == 0) ||
(strcmp(jpegdma->jpeg_clk_info[i].clk_name,
"mmss_camss_jpeg_axi_clk") == 0)) {
msm_camera_set_clk_flags(jpegdma->clk[i],
CLKFLAG_NORETAIN_MEM);
msm_camera_set_clk_flags(jpegdma->clk[i],
CLKFLAG_NORETAIN_PERIPH);
}
}
ret = msm_jpegdma_hw_get_qos(jpegdma);
if (ret < 0)
goto error_qos_get;
ret = msm_jpegdma_hw_get_vbif(jpegdma);
if (ret < 0)
goto error_vbif_get;
ret = msm_jpegdma_hw_get_prefetch(jpegdma);
if (ret < 0)
goto error_prefetch_get;
/* get the irq resource */
jpegdma->irq = msm_camera_get_irq(pdev, "jpeg");
if (!jpegdma->irq)
goto error_hw_get_irq;
switch (pdev->id) {
case 3:
jpegdma->bus_client = CAM_BUS_CLIENT_JPEG_DMA;
break;
default:
pr_err("%s: invalid cell id :%d\n",
__func__, pdev->id);
goto error_reg_bus;
}
/* register bus client */
ret = msm_camera_register_bus_client(pdev,
jpegdma->bus_client);
if (ret < 0) {
pr_err("Fail to register bus client\n");
ret = -EINVAL;
goto error_reg_bus;
}
ret = msm_jpegdma_hw_get_capabilities(jpegdma);
if (ret < 0)
goto error_hw_get_cap;
/* mem2mem device */
jpegdma->m2m_dev = v4l2_m2m_init(&msm_jpegdma_m2m_ops);
if (IS_ERR(jpegdma->m2m_dev)) {
dev_err(&pdev->dev, "Failed to init mem2mem device\n");
ret = PTR_ERR(jpegdma->m2m_dev);
goto error_m2m_init;
}
/* v4l2 device */
ret = v4l2_device_register(&pdev->dev, &jpegdma->v4l2_dev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register v4l2 device\n");
goto error_v4l2_register;
}
jpegdma->video.fops = &fd_fops;
jpegdma->video.ioctl_ops = &fd_ioctl_ops;
jpegdma->video.minor = -1;
jpegdma->video.release = video_device_release;
jpegdma->video.v4l2_dev = &jpegdma->v4l2_dev;
jpegdma->video.vfl_dir = VFL_DIR_M2M;
jpegdma->video.vfl_type = VFL_TYPE_GRABBER;
strlcpy(jpegdma->video.name, MSM_JPEGDMA_DRV_NAME,
sizeof(jpegdma->video.name));
ret = video_register_device(&jpegdma->video, VFL_TYPE_GRABBER, -1);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register video device\n");
goto error_video_register;
}
video_set_drvdata(&jpegdma->video, jpegdma);
platform_set_drvdata(pdev, jpegdma);
dev_dbg(&pdev->dev, "jpeg v4l2 DMA probe success\n");
return 0;
error_video_register:
v4l2_device_unregister(&jpegdma->v4l2_dev);
error_v4l2_register:
v4l2_m2m_release(jpegdma->m2m_dev);
error_m2m_init:
error_hw_get_cap:
msm_camera_unregister_bus_client(jpegdma->bus_client);
error_reg_bus:
error_hw_get_irq:
msm_jpegdma_hw_put_prefetch(jpegdma);
error_prefetch_get:
msm_jpegdma_hw_put_vbif(jpegdma);
error_vbif_get:
msm_jpegdma_hw_put_qos(jpegdma);
error_qos_get:
msm_camera_put_clk_info(pdev, &jpegdma->jpeg_clk_info,
&jpegdma->clk, jpegdma->num_clk);
error_get_clocks:
msm_camera_put_regulators(pdev, &jpegdma->dma_vdd,
jpegdma->num_reg);
error_get_regulators:
msm_jpegdma_hw_release_mem_resources(jpegdma);
error_mem_resources:
kfree(jpegdma);
return ret;
}
/*
* jpegdma_device_remove - Jpegdma device remove method.
* @pdev: Pointer jpegdma platform device.
*/
static int jpegdma_device_remove(struct platform_device *pdev)
{
struct msm_jpegdma_device *dma;
dma = platform_get_drvdata(pdev);
if (dma == NULL) {
dev_err(&pdev->dev, "Can not get jpeg dma drvdata\n");
return 0;
}
video_unregister_device(&dma->video);
v4l2_device_unregister(&dma->v4l2_dev);
v4l2_m2m_release(dma->m2m_dev);
/* unregister bus client */
msm_camera_unregister_bus_client(dma->bus_client);
/* release all the regulators */
msm_camera_put_regulators(dma->pdev, &dma->dma_vdd,
dma->num_reg);
/* release all the clocks */
msm_camera_put_clk_info(dma->pdev, &dma->jpeg_clk_info,
&dma->clk, dma->num_clk);
msm_jpegdma_hw_release_mem_resources(dma);
kfree(dma);
return 0;
}
/* Device tree match struct */
static const struct of_device_id msm_jpegdma_dt_match[] = {
{.compatible = "qcom,jpegdma"},
{}
};
/* Jpeg dma platform driver definition */
static struct platform_driver jpegdma_driver = {
.probe = jpegdma_probe,
.remove = jpegdma_device_remove,
.driver = {
.name = MSM_JPEGDMA_DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = msm_jpegdma_dt_match,
},
};
static int __init msm_jpegdma_init_module(void)
{
return platform_driver_register(&jpegdma_driver);
}
static void __exit msm_jpegdma_exit_module(void)
{
platform_driver_unregister(&jpegdma_driver);
}
module_init(msm_jpegdma_init_module);
module_exit(msm_jpegdma_exit_module);
MODULE_DESCRIPTION("MSM JPEG DMA driver");