// SPDX-License-Identifier: GPL-2.0 /* * ISI V4L2 memory to memory driver for i.MX8QXP/QM platform * * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which * used to process image from camera sensor or memory to memory or DC * * Copyright (c) 2019 NXP Semiconductor */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imx8-isi-core.h" struct mxc_isi_m2m_buffer { struct v4l2_m2m_buffer buf; dma_addr_t dma_addrs[3]; }; struct mxc_isi_m2m_ctx_queue_data { struct v4l2_pix_format_mplane format; const struct mxc_isi_format_info *info; u32 sequence; }; struct mxc_isi_m2m_ctx { struct v4l2_fh fh; struct mxc_isi_m2m *m2m; /* Protects the m2m vb2 queues */ struct mutex vb2_lock; struct { struct mxc_isi_m2m_ctx_queue_data out; struct mxc_isi_m2m_ctx_queue_data cap; } queues; struct { struct v4l2_ctrl_handler handler; unsigned int alpha; bool hflip; bool vflip; } ctrls; bool chained; }; static inline struct mxc_isi_m2m_buffer * to_isi_m2m_buffer(struct vb2_v4l2_buffer *buf) { return container_of(buf, struct mxc_isi_m2m_buffer, buf.vb); } static inline struct mxc_isi_m2m_ctx *to_isi_m2m_ctx(struct v4l2_fh *fh) { return container_of(fh, struct mxc_isi_m2m_ctx, fh); } static inline struct mxc_isi_m2m_ctx_queue_data * mxc_isi_m2m_ctx_qdata(struct mxc_isi_m2m_ctx *ctx, enum v4l2_buf_type type) { if (V4L2_TYPE_IS_OUTPUT(type)) return &ctx->queues.out; else return &ctx->queues.cap; } /* ----------------------------------------------------------------------------- * V4L2 M2M device operations */ static void mxc_isi_m2m_frame_write_done(struct mxc_isi_pipe *pipe, u32 status) { struct mxc_isi_m2m *m2m = &pipe->isi->m2m; struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; struct mxc_isi_m2m_ctx *ctx; ctx = v4l2_m2m_get_curr_priv(m2m->m2m_dev); if (!ctx) { dev_err(m2m->isi->dev, "Instance released before the end of transaction\n"); return; } src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, false); src_vbuf->sequence = ctx->queues.out.sequence++; dst_vbuf->sequence = ctx->queues.cap.sequence++; v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE); v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE); v4l2_m2m_job_finish(m2m->m2m_dev, ctx->fh.m2m_ctx); } static void mxc_isi_m2m_device_run(void *priv) { struct mxc_isi_m2m_ctx *ctx = priv; struct mxc_isi_m2m *m2m = ctx->m2m; struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; struct mxc_isi_m2m_buffer *src_buf, *dst_buf; mxc_isi_channel_disable(m2m->pipe); mutex_lock(&m2m->lock); /* If the context has changed, reconfigure the channel. */ if (m2m->last_ctx != ctx) { const struct v4l2_area in_size = { .width = ctx->queues.out.format.width, .height = ctx->queues.out.format.height, }; const struct v4l2_area scale = { .width = ctx->queues.cap.format.width, .height = ctx->queues.cap.format.height, }; const struct v4l2_rect crop = { .width = ctx->queues.cap.format.width, .height = ctx->queues.cap.format.height, }; mxc_isi_channel_config(m2m->pipe, MXC_ISI_INPUT_MEM, &in_size, &scale, &crop, ctx->queues.out.info->encoding, ctx->queues.cap.info->encoding); mxc_isi_channel_set_input_format(m2m->pipe, ctx->queues.out.info, &ctx->queues.out.format); mxc_isi_channel_set_output_format(m2m->pipe, ctx->queues.cap.info, &ctx->queues.cap.format); m2m->last_ctx = ctx; } mutex_unlock(&m2m->lock); mutex_lock(ctx->ctrls.handler.lock); mxc_isi_channel_set_alpha(m2m->pipe, ctx->ctrls.alpha); mxc_isi_channel_set_flip(m2m->pipe, ctx->ctrls.hflip, ctx->ctrls.vflip); mutex_unlock(ctx->ctrls.handler.lock); src_vbuf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); dst_vbuf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); src_buf = to_isi_m2m_buffer(src_vbuf); dst_buf = to_isi_m2m_buffer(dst_vbuf); mxc_isi_channel_set_inbuf(m2m->pipe, src_buf->dma_addrs[0]); mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF1); mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF2); mxc_isi_channel_enable(m2m->pipe); mxc_isi_channel_m2m_start(m2m->pipe); } static const struct v4l2_m2m_ops mxc_isi_m2m_ops = { .device_run = mxc_isi_m2m_device_run, }; /* ----------------------------------------------------------------------------- * videobuf2 queue operations */ static int mxc_isi_m2m_vb2_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], struct device *alloc_devs[]) { struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q); const struct mxc_isi_m2m_ctx_queue_data *qdata = mxc_isi_m2m_ctx_qdata(ctx, q->type); return mxc_isi_video_queue_setup(&qdata->format, qdata->info, num_buffers, num_planes, sizes); } static int mxc_isi_m2m_vb2_buffer_init(struct vb2_buffer *vb2) { struct vb2_queue *vq = vb2->vb2_queue; struct mxc_isi_m2m_buffer *buf = to_isi_m2m_buffer(to_vb2_v4l2_buffer(vb2)); struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue); const struct mxc_isi_m2m_ctx_queue_data *qdata = mxc_isi_m2m_ctx_qdata(ctx, vq->type); mxc_isi_video_buffer_init(vb2, buf->dma_addrs, qdata->info, &qdata->format); return 0; } static int mxc_isi_m2m_vb2_buffer_prepare(struct vb2_buffer *vb2) { struct vb2_queue *vq = vb2->vb2_queue; struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vq); const struct mxc_isi_m2m_ctx_queue_data *qdata = mxc_isi_m2m_ctx_qdata(ctx, vq->type); return mxc_isi_video_buffer_prepare(ctx->m2m->isi, vb2, qdata->info, &qdata->format); } static void mxc_isi_m2m_vb2_buffer_queue(struct vb2_buffer *vb2) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue); v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); } static int mxc_isi_m2m_vb2_start_streaming(struct vb2_queue *q, unsigned int count) { struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q); struct mxc_isi_m2m_ctx_queue_data *qdata = mxc_isi_m2m_ctx_qdata(ctx, q->type); qdata->sequence = 0; return 0; } static void mxc_isi_m2m_vb2_stop_streaming(struct vb2_queue *q) { struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q); struct vb2_v4l2_buffer *vbuf; for (;;) { if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); else vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); if (!vbuf) break; v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); } } static const struct vb2_ops mxc_isi_m2m_vb2_qops = { .queue_setup = mxc_isi_m2m_vb2_queue_setup, .buf_init = mxc_isi_m2m_vb2_buffer_init, .buf_prepare = mxc_isi_m2m_vb2_buffer_prepare, .buf_queue = mxc_isi_m2m_vb2_buffer_queue, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, .start_streaming = mxc_isi_m2m_vb2_start_streaming, .stop_streaming = mxc_isi_m2m_vb2_stop_streaming, }; static int mxc_isi_m2m_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) { struct mxc_isi_m2m_ctx *ctx = priv; struct mxc_isi_m2m *m2m = ctx->m2m; int ret; src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; src_vq->io_modes = VB2_MMAP | VB2_DMABUF; src_vq->drv_priv = ctx; src_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer); src_vq->ops = &mxc_isi_m2m_vb2_qops; src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; src_vq->lock = &ctx->vb2_lock; src_vq->dev = m2m->isi->dev; ret = vb2_queue_init(src_vq); if (ret) return ret; dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; dst_vq->drv_priv = ctx; dst_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer); dst_vq->ops = &mxc_isi_m2m_vb2_qops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; dst_vq->lock = &ctx->vb2_lock; dst_vq->dev = m2m->isi->dev; return vb2_queue_init(dst_vq); } /* ----------------------------------------------------------------------------- * V4L2 controls */ static inline struct mxc_isi_m2m_ctx * ctrl_to_mxc_isi_m2m_ctx(struct v4l2_ctrl *ctrl) { return container_of(ctrl->handler, struct mxc_isi_m2m_ctx, ctrls.handler); } static int mxc_isi_m2m_ctx_s_ctrl(struct v4l2_ctrl *ctrl) { struct mxc_isi_m2m_ctx *ctx = ctrl_to_mxc_isi_m2m_ctx(ctrl); switch (ctrl->id) { case V4L2_CID_HFLIP: ctx->ctrls.hflip = ctrl->val; break; case V4L2_CID_VFLIP: ctx->ctrls.vflip = ctrl->val; break; case V4L2_CID_ALPHA_COMPONENT: ctx->ctrls.alpha = ctrl->val; break; } return 0; } static const struct v4l2_ctrl_ops mxc_isi_m2m_ctx_ctrl_ops = { .s_ctrl = mxc_isi_m2m_ctx_s_ctrl, }; static int mxc_isi_m2m_ctx_ctrls_create(struct mxc_isi_m2m_ctx *ctx) { struct v4l2_ctrl_handler *handler = &ctx->ctrls.handler; int ret; v4l2_ctrl_handler_init(handler, 3); v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0); v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); if (handler->error) { ret = handler->error; v4l2_ctrl_handler_free(handler); return ret; } ctx->fh.ctrl_handler = handler; return 0; } static void mxc_isi_m2m_ctx_ctrls_delete(struct mxc_isi_m2m_ctx *ctx) { v4l2_ctrl_handler_free(&ctx->ctrls.handler); } /* ----------------------------------------------------------------------------- * V4L2 ioctls */ static int mxc_isi_m2m_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver)); strscpy(cap->card, MXC_ISI_M2M, sizeof(cap->card)); cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } static int mxc_isi_m2m_enum_fmt_vid(struct file *file, void *fh, struct v4l2_fmtdesc *f) { const enum mxc_isi_video_type type = f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ? MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP; const struct mxc_isi_format_info *info; info = mxc_isi_format_enum(f->index, type); if (!info) return -EINVAL; f->pixelformat = info->fourcc; f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC | V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC; return 0; } static const struct mxc_isi_format_info * __mxc_isi_m2m_try_fmt_vid(struct mxc_isi_m2m_ctx *ctx, struct v4l2_pix_format_mplane *pix, const enum mxc_isi_video_type type) { if (type == MXC_ISI_VIDEO_M2M_CAP) { /* Downscaling only */ pix->width = min(pix->width, ctx->queues.out.format.width); pix->height = min(pix->height, ctx->queues.out.format.height); } return mxc_isi_format_try(ctx->m2m->pipe, pix, type); } static int mxc_isi_m2m_try_fmt_vid(struct file *file, void *fh, struct v4l2_format *f) { const enum mxc_isi_video_type type = f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ? MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP; struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); __mxc_isi_m2m_try_fmt_vid(ctx, &f->fmt.pix_mp, type); return 0; } static int mxc_isi_m2m_g_fmt_vid(struct file *file, void *fh, struct v4l2_format *f) { struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); const struct mxc_isi_m2m_ctx_queue_data *qdata = mxc_isi_m2m_ctx_qdata(ctx, f->type); f->fmt.pix_mp = qdata->format; return 0; } static int mxc_isi_m2m_s_fmt_vid(struct file *file, void *fh, struct v4l2_format *f) { const enum mxc_isi_video_type type = f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ? MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP; struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; const struct mxc_isi_format_info *info; struct vb2_queue *vq; vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (!vq) return -EINVAL; if (vb2_is_busy(vq)) return -EBUSY; info = __mxc_isi_m2m_try_fmt_vid(ctx, pix, type); if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { ctx->queues.out.format = *pix; ctx->queues.out.info = info; } /* * Always set the format on the capture side, due to either format * propagation or direct setting. */ ctx->queues.cap.format = *pix; ctx->queues.cap.info = info; return 0; } static int mxc_isi_m2m_streamon(struct file *file, void *fh, enum v4l2_buf_type type) { struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); const struct v4l2_pix_format_mplane *out_pix = &ctx->queues.out.format; const struct v4l2_pix_format_mplane *cap_pix = &ctx->queues.cap.format; const struct mxc_isi_format_info *cap_info = ctx->queues.cap.info; const struct mxc_isi_format_info *out_info = ctx->queues.out.info; struct mxc_isi_m2m *m2m = ctx->m2m; bool bypass; int ret; mutex_lock(&m2m->lock); if (m2m->usage_count == INT_MAX) { ret = -EOVERFLOW; goto unlock; } bypass = cap_pix->width == out_pix->width && cap_pix->height == out_pix->height && cap_info->encoding == out_info->encoding; /* * Acquire the pipe and initialize the channel with the first user of * the M2M device. */ if (m2m->usage_count == 0) { ret = mxc_isi_channel_acquire(m2m->pipe, &mxc_isi_m2m_frame_write_done, bypass); if (ret) goto unlock; mxc_isi_channel_get(m2m->pipe); } m2m->usage_count++; /* * Allocate resources for the channel, counting how many users require * buffer chaining. */ if (!ctx->chained && out_pix->width > MXC_ISI_MAX_WIDTH_UNCHAINED) { ret = mxc_isi_channel_chain(m2m->pipe, bypass); if (ret) goto deinit; m2m->chained_count++; ctx->chained = true; } /* * Drop the lock to start the stream, as the .device_run() operation * needs to acquire it. */ mutex_unlock(&m2m->lock); ret = v4l2_m2m_ioctl_streamon(file, fh, type); if (ret) { /* Reacquire the lock for the cleanup path. */ mutex_lock(&m2m->lock); goto unchain; } return 0; unchain: if (ctx->chained && --m2m->chained_count == 0) mxc_isi_channel_unchain(m2m->pipe); ctx->chained = false; deinit: if (--m2m->usage_count == 0) { mxc_isi_channel_put(m2m->pipe); mxc_isi_channel_release(m2m->pipe); } unlock: mutex_unlock(&m2m->lock); return ret; } static int mxc_isi_m2m_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) { struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh); struct mxc_isi_m2m *m2m = ctx->m2m; v4l2_m2m_ioctl_streamoff(file, fh, type); mutex_lock(&m2m->lock); /* * If the last context is this one, reset it to make sure the device * will be reconfigured when streaming is restarted. */ if (m2m->last_ctx == ctx) m2m->last_ctx = NULL; /* Free the channel resources if this is the last chained context. */ if (ctx->chained && --m2m->chained_count == 0) mxc_isi_channel_unchain(m2m->pipe); ctx->chained = false; /* Turn off the light with the last user. */ if (--m2m->usage_count == 0) { mxc_isi_channel_disable(m2m->pipe); mxc_isi_channel_put(m2m->pipe); mxc_isi_channel_release(m2m->pipe); } WARN_ON(m2m->usage_count < 0); mutex_unlock(&m2m->lock); return 0; } static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = { .vidioc_querycap = mxc_isi_m2m_querycap, .vidioc_enum_fmt_vid_cap = mxc_isi_m2m_enum_fmt_vid, .vidioc_enum_fmt_vid_out = mxc_isi_m2m_enum_fmt_vid, .vidioc_g_fmt_vid_cap_mplane = mxc_isi_m2m_g_fmt_vid, .vidioc_g_fmt_vid_out_mplane = mxc_isi_m2m_g_fmt_vid, .vidioc_s_fmt_vid_cap_mplane = mxc_isi_m2m_s_fmt_vid, .vidioc_s_fmt_vid_out_mplane = mxc_isi_m2m_s_fmt_vid, .vidioc_try_fmt_vid_cap_mplane = mxc_isi_m2m_try_fmt_vid, .vidioc_try_fmt_vid_out_mplane = mxc_isi_m2m_try_fmt_vid, .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, .vidioc_streamon = mxc_isi_m2m_streamon, .vidioc_streamoff = mxc_isi_m2m_streamoff, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* ----------------------------------------------------------------------------- * Video device file operations */ static void mxc_isi_m2m_init_format(struct mxc_isi_m2m_ctx *ctx, struct mxc_isi_m2m_ctx_queue_data *qdata, enum mxc_isi_video_type type) { qdata->format.width = MXC_ISI_DEF_WIDTH; qdata->format.height = MXC_ISI_DEF_HEIGHT; qdata->format.pixelformat = MXC_ISI_DEF_PIXEL_FORMAT; qdata->info = mxc_isi_format_try(ctx->m2m->pipe, &qdata->format, type); } static int mxc_isi_m2m_open(struct file *file) { struct video_device *vdev = video_devdata(file); struct mxc_isi_m2m *m2m = video_drvdata(file); struct mxc_isi_m2m_ctx *ctx; int ret; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; ctx->m2m = m2m; mutex_init(&ctx->vb2_lock); v4l2_fh_init(&ctx->fh, vdev); file->private_data = &ctx->fh; ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(m2m->m2m_dev, ctx, &mxc_isi_m2m_queue_init); if (IS_ERR(ctx->fh.m2m_ctx)) { ret = PTR_ERR(ctx->fh.m2m_ctx); ctx->fh.m2m_ctx = NULL; goto err_fh; } mxc_isi_m2m_init_format(ctx, &ctx->queues.out, MXC_ISI_VIDEO_M2M_OUT); mxc_isi_m2m_init_format(ctx, &ctx->queues.cap, MXC_ISI_VIDEO_M2M_CAP); ret = mxc_isi_m2m_ctx_ctrls_create(ctx); if (ret) goto err_ctx; ret = pm_runtime_resume_and_get(m2m->isi->dev); if (ret) goto err_ctrls; v4l2_fh_add(&ctx->fh); return 0; err_ctrls: mxc_isi_m2m_ctx_ctrls_delete(ctx); err_ctx: v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); err_fh: v4l2_fh_exit(&ctx->fh); mutex_destroy(&ctx->vb2_lock); kfree(ctx); return ret; } static int mxc_isi_m2m_release(struct file *file) { struct mxc_isi_m2m *m2m = video_drvdata(file); struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(file->private_data); v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); mxc_isi_m2m_ctx_ctrls_delete(ctx); v4l2_fh_del(&ctx->fh); v4l2_fh_exit(&ctx->fh); mutex_destroy(&ctx->vb2_lock); kfree(ctx); pm_runtime_put(m2m->isi->dev); return 0; } static const struct v4l2_file_operations mxc_isi_m2m_fops = { .owner = THIS_MODULE, .open = mxc_isi_m2m_open, .release = mxc_isi_m2m_release, .poll = v4l2_m2m_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = v4l2_m2m_fop_mmap, }; /* ----------------------------------------------------------------------------- * Registration */ int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev) { struct mxc_isi_m2m *m2m = &isi->m2m; struct video_device *vdev = &m2m->vdev; struct media_link *link; int ret; m2m->isi = isi; m2m->pipe = &isi->pipes[0]; mutex_init(&m2m->lock); /* Initialize the video device and create controls. */ snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.m2m"); vdev->fops = &mxc_isi_m2m_fops; vdev->ioctl_ops = &mxc_isi_m2m_ioctl_ops; vdev->v4l2_dev = v4l2_dev; vdev->minor = -1; vdev->release = video_device_release_empty; vdev->vfl_dir = VFL_DIR_M2M; vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; video_set_drvdata(vdev, m2m); /* Create the M2M device. */ m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops); if (IS_ERR(m2m->m2m_dev)) { dev_err(isi->dev, "failed to initialize m2m device\n"); ret = PTR_ERR(m2m->m2m_dev); goto err_mutex; } /* Register the video device. */ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret < 0) { dev_err(isi->dev, "failed to register m2m device\n"); goto err_m2m; } /* * Populate the media graph. We can't use the mem2mem helper * v4l2_m2m_register_media_controller() as the M2M interface needs to * be connected to the existing entities in the graph, so we have to * wire things up manually: * * - The entity in the video_device, which isn't touched by the V4L2 * core for M2M devices, is used as the source I/O entity in the * graph, connected to the crossbar switch. * * - The video device at the end of the pipeline provides the sink * entity, and is already wired up in the graph. * * - A new interface is created, pointing at both entities. The sink * entity will thus have two interfaces pointing to it. */ m2m->pad.flags = MEDIA_PAD_FL_SOURCE; vdev->entity.name = "mxc_isi.output"; vdev->entity.function = MEDIA_ENT_F_IO_V4L; ret = media_entity_pads_init(&vdev->entity, 1, &m2m->pad); if (ret) goto err_video; ret = media_device_register_entity(v4l2_dev->mdev, &vdev->entity); if (ret) goto err_entity_cleanup; ret = media_create_pad_link(&vdev->entity, 0, &m2m->isi->crossbar.sd.entity, m2m->isi->crossbar.num_sinks - 1, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); if (ret) goto err_entity_unreg; m2m->intf = media_devnode_create(v4l2_dev->mdev, MEDIA_INTF_T_V4L_VIDEO, 0, VIDEO_MAJOR, vdev->minor); if (!m2m->intf) { ret = -ENOMEM; goto err_entity_unreg; } link = media_create_intf_link(&vdev->entity, &m2m->intf->intf, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); if (!link) { ret = -ENOMEM; goto err_devnode; } link = media_create_intf_link(&m2m->pipe->video.vdev.entity, &m2m->intf->intf, MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); if (!link) { ret = -ENOMEM; goto err_devnode; } return 0; err_devnode: media_devnode_remove(m2m->intf); err_entity_unreg: media_device_unregister_entity(&vdev->entity); err_entity_cleanup: media_entity_cleanup(&vdev->entity); err_video: video_unregister_device(vdev); err_m2m: v4l2_m2m_release(m2m->m2m_dev); err_mutex: mutex_destroy(&m2m->lock); return ret; } int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi) { struct mxc_isi_m2m *m2m = &isi->m2m; struct video_device *vdev = &m2m->vdev; video_unregister_device(vdev); v4l2_m2m_release(m2m->m2m_dev); media_devnode_remove(m2m->intf); media_entity_cleanup(&vdev->entity); mutex_destroy(&m2m->lock); return 0; }