// SPDX-License-Identifier: GPL-2.0+ 
 | 
/* 
 | 
 * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) 
 | 
 * All rights reserved. 
 | 
 * Author: Yong Deng <yong.deng@magewell.com> 
 | 
 */ 
 | 
  
 | 
#include <linux/of.h> 
 | 
  
 | 
#include <media/v4l2-device.h> 
 | 
#include <media/v4l2-event.h> 
 | 
#include <media/v4l2-ioctl.h> 
 | 
#include <media/v4l2-mc.h> 
 | 
#include <media/videobuf2-dma-contig.h> 
 | 
#include <media/videobuf2-v4l2.h> 
 | 
  
 | 
#include "sun6i_csi.h" 
 | 
#include "sun6i_video.h" 
 | 
  
 | 
/* This is got from BSP sources. */ 
 | 
#define MIN_WIDTH    (32) 
 | 
#define MIN_HEIGHT    (32) 
 | 
#define MAX_WIDTH    (4800) 
 | 
#define MAX_HEIGHT    (4800) 
 | 
  
 | 
struct sun6i_csi_buffer { 
 | 
    struct vb2_v4l2_buffer        vb; 
 | 
    struct list_head        list; 
 | 
  
 | 
    dma_addr_t            dma_addr; 
 | 
    bool                queued_to_csi; 
 | 
}; 
 | 
  
 | 
static const u32 supported_pixformats[] = { 
 | 
    V4L2_PIX_FMT_SBGGR8, 
 | 
    V4L2_PIX_FMT_SGBRG8, 
 | 
    V4L2_PIX_FMT_SGRBG8, 
 | 
    V4L2_PIX_FMT_SRGGB8, 
 | 
    V4L2_PIX_FMT_SBGGR10, 
 | 
    V4L2_PIX_FMT_SGBRG10, 
 | 
    V4L2_PIX_FMT_SGRBG10, 
 | 
    V4L2_PIX_FMT_SRGGB10, 
 | 
    V4L2_PIX_FMT_SBGGR12, 
 | 
    V4L2_PIX_FMT_SGBRG12, 
 | 
    V4L2_PIX_FMT_SGRBG12, 
 | 
    V4L2_PIX_FMT_SRGGB12, 
 | 
    V4L2_PIX_FMT_YUYV, 
 | 
    V4L2_PIX_FMT_YVYU, 
 | 
    V4L2_PIX_FMT_UYVY, 
 | 
    V4L2_PIX_FMT_VYUY, 
 | 
    V4L2_PIX_FMT_HM12, 
 | 
    V4L2_PIX_FMT_NV12, 
 | 
    V4L2_PIX_FMT_NV21, 
 | 
    V4L2_PIX_FMT_YUV420, 
 | 
    V4L2_PIX_FMT_YVU420, 
 | 
    V4L2_PIX_FMT_NV16, 
 | 
    V4L2_PIX_FMT_NV61, 
 | 
    V4L2_PIX_FMT_YUV422P, 
 | 
    V4L2_PIX_FMT_RGB565, 
 | 
    V4L2_PIX_FMT_RGB565X, 
 | 
    V4L2_PIX_FMT_JPEG, 
 | 
}; 
 | 
  
 | 
static bool is_pixformat_valid(unsigned int pixformat) 
 | 
{ 
 | 
    unsigned int i; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(supported_pixformats); i++) 
 | 
        if (supported_pixformats[i] == pixformat) 
 | 
            return true; 
 | 
  
 | 
    return false; 
 | 
} 
 | 
  
 | 
static struct v4l2_subdev * 
 | 
sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad) 
 | 
{ 
 | 
    struct media_pad *remote; 
 | 
  
 | 
    remote = media_entity_remote_pad(&video->pad); 
 | 
  
 | 
    if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) 
 | 
        return NULL; 
 | 
  
 | 
    if (pad) 
 | 
        *pad = remote->index; 
 | 
  
 | 
    return media_entity_to_v4l2_subdev(remote->entity); 
 | 
} 
 | 
  
 | 
static int sun6i_video_queue_setup(struct vb2_queue *vq, 
 | 
                   unsigned int *nbuffers, 
 | 
                   unsigned int *nplanes, 
 | 
                   unsigned int sizes[], 
 | 
                   struct device *alloc_devs[]) 
 | 
{ 
 | 
    struct sun6i_video *video = vb2_get_drv_priv(vq); 
 | 
    unsigned int size = video->fmt.fmt.pix.sizeimage; 
 | 
  
 | 
    if (*nplanes) 
 | 
        return sizes[0] < size ? -EINVAL : 0; 
 | 
  
 | 
    *nplanes = 1; 
 | 
    sizes[0] = size; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sun6i_video_buffer_prepare(struct vb2_buffer *vb) 
 | 
{ 
 | 
    struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 
 | 
    struct sun6i_csi_buffer *buf = 
 | 
            container_of(vbuf, struct sun6i_csi_buffer, vb); 
 | 
    struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue); 
 | 
    unsigned long size = video->fmt.fmt.pix.sizeimage; 
 | 
  
 | 
    if (vb2_plane_size(vb, 0) < size) { 
 | 
        v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n", 
 | 
             vb2_plane_size(vb, 0), size); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    vb2_set_plane_payload(vb, 0, size); 
 | 
  
 | 
    buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); 
 | 
  
 | 
    vbuf->field = video->fmt.fmt.pix.field; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count) 
 | 
{ 
 | 
    struct sun6i_video *video = vb2_get_drv_priv(vq); 
 | 
    struct sun6i_csi_buffer *buf; 
 | 
    struct sun6i_csi_buffer *next_buf; 
 | 
    struct sun6i_csi_config config; 
 | 
    struct v4l2_subdev *subdev; 
 | 
    unsigned long flags; 
 | 
    int ret; 
 | 
  
 | 
    video->sequence = 0; 
 | 
  
 | 
    ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe); 
 | 
    if (ret < 0) 
 | 
        goto clear_dma_queue; 
 | 
  
 | 
    if (video->mbus_code == 0) { 
 | 
        ret = -EINVAL; 
 | 
        goto stop_media_pipeline; 
 | 
    } 
 | 
  
 | 
    subdev = sun6i_video_remote_subdev(video, NULL); 
 | 
    if (!subdev) { 
 | 
        ret = -EINVAL; 
 | 
        goto stop_media_pipeline; 
 | 
    } 
 | 
  
 | 
    config.pixelformat = video->fmt.fmt.pix.pixelformat; 
 | 
    config.code = video->mbus_code; 
 | 
    config.field = video->fmt.fmt.pix.field; 
 | 
    config.width = video->fmt.fmt.pix.width; 
 | 
    config.height = video->fmt.fmt.pix.height; 
 | 
  
 | 
    ret = sun6i_csi_update_config(video->csi, &config); 
 | 
    if (ret < 0) 
 | 
        goto stop_media_pipeline; 
 | 
  
 | 
    spin_lock_irqsave(&video->dma_queue_lock, flags); 
 | 
  
 | 
    buf = list_first_entry(&video->dma_queue, 
 | 
                   struct sun6i_csi_buffer, list); 
 | 
    buf->queued_to_csi = true; 
 | 
    sun6i_csi_update_buf_addr(video->csi, buf->dma_addr); 
 | 
  
 | 
    sun6i_csi_set_stream(video->csi, true); 
 | 
  
 | 
    /* 
 | 
     * CSI will lookup the next dma buffer for next frame before the 
 | 
     * the current frame done IRQ triggered. This is not documented 
 | 
     * but reported by OndÅ™ej Jirman. 
 | 
     * The BSP code has workaround for this too. It skip to mark the 
 | 
     * first buffer as frame done for VB2 and pass the second buffer 
 | 
     * to CSI in the first frame done ISR call. Then in second frame 
 | 
     * done ISR call, it mark the first buffer as frame done for VB2 
 | 
     * and pass the third buffer to CSI. And so on. The bad thing is 
 | 
     * that the first buffer will be written twice and the first frame 
 | 
     * is dropped even the queued buffer is sufficient. 
 | 
     * So, I make some improvement here. Pass the next buffer to CSI 
 | 
     * just follow starting the CSI. In this case, the first frame 
 | 
     * will be stored in first buffer, second frame in second buffer. 
 | 
     * This method is used to avoid dropping the first frame, it 
 | 
     * would also drop frame when lacking of queued buffer. 
 | 
     */ 
 | 
    next_buf = list_next_entry(buf, list); 
 | 
    next_buf->queued_to_csi = true; 
 | 
    sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); 
 | 
  
 | 
    spin_unlock_irqrestore(&video->dma_queue_lock, flags); 
 | 
  
 | 
    ret = v4l2_subdev_call(subdev, video, s_stream, 1); 
 | 
    if (ret && ret != -ENOIOCTLCMD) 
 | 
        goto stop_csi_stream; 
 | 
  
 | 
    return 0; 
 | 
  
 | 
stop_csi_stream: 
 | 
    sun6i_csi_set_stream(video->csi, false); 
 | 
stop_media_pipeline: 
 | 
    media_pipeline_stop(&video->vdev.entity); 
 | 
clear_dma_queue: 
 | 
    spin_lock_irqsave(&video->dma_queue_lock, flags); 
 | 
    list_for_each_entry(buf, &video->dma_queue, list) 
 | 
        vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); 
 | 
    INIT_LIST_HEAD(&video->dma_queue); 
 | 
    spin_unlock_irqrestore(&video->dma_queue_lock, flags); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static void sun6i_video_stop_streaming(struct vb2_queue *vq) 
 | 
{ 
 | 
    struct sun6i_video *video = vb2_get_drv_priv(vq); 
 | 
    struct v4l2_subdev *subdev; 
 | 
    unsigned long flags; 
 | 
    struct sun6i_csi_buffer *buf; 
 | 
  
 | 
    subdev = sun6i_video_remote_subdev(video, NULL); 
 | 
    if (subdev) 
 | 
        v4l2_subdev_call(subdev, video, s_stream, 0); 
 | 
  
 | 
    sun6i_csi_set_stream(video->csi, false); 
 | 
  
 | 
    media_pipeline_stop(&video->vdev.entity); 
 | 
  
 | 
    /* Release all active buffers */ 
 | 
    spin_lock_irqsave(&video->dma_queue_lock, flags); 
 | 
    list_for_each_entry(buf, &video->dma_queue, list) 
 | 
        vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 
 | 
    INIT_LIST_HEAD(&video->dma_queue); 
 | 
    spin_unlock_irqrestore(&video->dma_queue_lock, flags); 
 | 
} 
 | 
  
 | 
static void sun6i_video_buffer_queue(struct vb2_buffer *vb) 
 | 
{ 
 | 
    struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 
 | 
    struct sun6i_csi_buffer *buf = 
 | 
            container_of(vbuf, struct sun6i_csi_buffer, vb); 
 | 
    struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue); 
 | 
    unsigned long flags; 
 | 
  
 | 
    spin_lock_irqsave(&video->dma_queue_lock, flags); 
 | 
    buf->queued_to_csi = false; 
 | 
    list_add_tail(&buf->list, &video->dma_queue); 
 | 
    spin_unlock_irqrestore(&video->dma_queue_lock, flags); 
 | 
} 
 | 
  
 | 
void sun6i_video_frame_done(struct sun6i_video *video) 
 | 
{ 
 | 
    struct sun6i_csi_buffer *buf; 
 | 
    struct sun6i_csi_buffer *next_buf; 
 | 
    struct vb2_v4l2_buffer *vbuf; 
 | 
  
 | 
    spin_lock(&video->dma_queue_lock); 
 | 
  
 | 
    buf = list_first_entry(&video->dma_queue, 
 | 
                   struct sun6i_csi_buffer, list); 
 | 
    if (list_is_last(&buf->list, &video->dma_queue)) { 
 | 
        dev_dbg(video->csi->dev, "Frame dropped!\n"); 
 | 
        goto unlock; 
 | 
    } 
 | 
  
 | 
    next_buf = list_next_entry(buf, list); 
 | 
    /* If a new buffer (#next_buf) had not been queued to CSI, the old 
 | 
     * buffer (#buf) is still holding by CSI for storing the next 
 | 
     * frame. So, we queue a new buffer (#next_buf) to CSI then wait 
 | 
     * for next ISR call. 
 | 
     */ 
 | 
    if (!next_buf->queued_to_csi) { 
 | 
        next_buf->queued_to_csi = true; 
 | 
        sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); 
 | 
        dev_dbg(video->csi->dev, "Frame dropped!\n"); 
 | 
        goto unlock; 
 | 
    } 
 | 
  
 | 
    list_del(&buf->list); 
 | 
    vbuf = &buf->vb; 
 | 
    vbuf->vb2_buf.timestamp = ktime_get_ns(); 
 | 
    vbuf->sequence = video->sequence; 
 | 
    vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); 
 | 
  
 | 
    /* Prepare buffer for next frame but one.  */ 
 | 
    if (!list_is_last(&next_buf->list, &video->dma_queue)) { 
 | 
        next_buf = list_next_entry(next_buf, list); 
 | 
        next_buf->queued_to_csi = true; 
 | 
        sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); 
 | 
    } else { 
 | 
        dev_dbg(video->csi->dev, "Next frame will be dropped!\n"); 
 | 
    } 
 | 
  
 | 
unlock: 
 | 
    video->sequence++; 
 | 
    spin_unlock(&video->dma_queue_lock); 
 | 
} 
 | 
  
 | 
static const struct vb2_ops sun6i_csi_vb2_ops = { 
 | 
    .queue_setup        = sun6i_video_queue_setup, 
 | 
    .wait_prepare        = vb2_ops_wait_prepare, 
 | 
    .wait_finish        = vb2_ops_wait_finish, 
 | 
    .buf_prepare        = sun6i_video_buffer_prepare, 
 | 
    .start_streaming    = sun6i_video_start_streaming, 
 | 
    .stop_streaming        = sun6i_video_stop_streaming, 
 | 
    .buf_queue        = sun6i_video_buffer_queue, 
 | 
}; 
 | 
  
 | 
static int vidioc_querycap(struct file *file, void *priv, 
 | 
               struct v4l2_capability *cap) 
 | 
{ 
 | 
    struct sun6i_video *video = video_drvdata(file); 
 | 
  
 | 
    strscpy(cap->driver, "sun6i-video", sizeof(cap->driver)); 
 | 
    strscpy(cap->card, video->vdev.name, sizeof(cap->card)); 
 | 
    snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", 
 | 
         video->csi->dev->of_node->name); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, 
 | 
                   struct v4l2_fmtdesc *f) 
 | 
{ 
 | 
    u32 index = f->index; 
 | 
  
 | 
    if (index >= ARRAY_SIZE(supported_pixformats)) 
 | 
        return -EINVAL; 
 | 
  
 | 
    f->pixelformat = supported_pixformats[index]; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, 
 | 
                struct v4l2_format *fmt) 
 | 
{ 
 | 
    struct sun6i_video *video = video_drvdata(file); 
 | 
  
 | 
    *fmt = video->fmt; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sun6i_video_try_fmt(struct sun6i_video *video, 
 | 
                   struct v4l2_format *f) 
 | 
{ 
 | 
    struct v4l2_pix_format *pixfmt = &f->fmt.pix; 
 | 
    int bpp; 
 | 
  
 | 
    if (!is_pixformat_valid(pixfmt->pixelformat)) 
 | 
        pixfmt->pixelformat = supported_pixformats[0]; 
 | 
  
 | 
    v4l_bound_align_image(&pixfmt->width, MIN_WIDTH, MAX_WIDTH, 1, 
 | 
                  &pixfmt->height, MIN_HEIGHT, MAX_WIDTH, 1, 1); 
 | 
  
 | 
    bpp = sun6i_csi_get_bpp(pixfmt->pixelformat); 
 | 
    pixfmt->bytesperline = (pixfmt->width * bpp) >> 3; 
 | 
    pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; 
 | 
  
 | 
    if (pixfmt->field == V4L2_FIELD_ANY) 
 | 
        pixfmt->field = V4L2_FIELD_NONE; 
 | 
  
 | 
    pixfmt->colorspace = V4L2_COLORSPACE_RAW; 
 | 
    pixfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; 
 | 
    pixfmt->quantization = V4L2_QUANTIZATION_DEFAULT; 
 | 
    pixfmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f) 
 | 
{ 
 | 
    int ret; 
 | 
  
 | 
    ret = sun6i_video_try_fmt(video, f); 
 | 
    if (ret) 
 | 
        return ret; 
 | 
  
 | 
    video->fmt = *f; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, 
 | 
                struct v4l2_format *f) 
 | 
{ 
 | 
    struct sun6i_video *video = video_drvdata(file); 
 | 
  
 | 
    if (vb2_is_busy(&video->vb2_vidq)) 
 | 
        return -EBUSY; 
 | 
  
 | 
    return sun6i_video_set_fmt(video, f); 
 | 
} 
 | 
  
 | 
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, 
 | 
                  struct v4l2_format *f) 
 | 
{ 
 | 
    struct sun6i_video *video = video_drvdata(file); 
 | 
  
 | 
    return sun6i_video_try_fmt(video, f); 
 | 
} 
 | 
  
 | 
static int vidioc_enum_input(struct file *file, void *fh, 
 | 
                 struct v4l2_input *inp) 
 | 
{ 
 | 
    if (inp->index != 0) 
 | 
        return -EINVAL; 
 | 
  
 | 
    strscpy(inp->name, "camera", sizeof(inp->name)); 
 | 
    inp->type = V4L2_INPUT_TYPE_CAMERA; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) 
 | 
{ 
 | 
    *i = 0; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int vidioc_s_input(struct file *file, void *fh, unsigned int i) 
 | 
{ 
 | 
    if (i != 0) 
 | 
        return -EINVAL; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = { 
 | 
    .vidioc_querycap        = vidioc_querycap, 
 | 
    .vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid_cap, 
 | 
    .vidioc_g_fmt_vid_cap        = vidioc_g_fmt_vid_cap, 
 | 
    .vidioc_s_fmt_vid_cap        = vidioc_s_fmt_vid_cap, 
 | 
    .vidioc_try_fmt_vid_cap        = vidioc_try_fmt_vid_cap, 
 | 
  
 | 
    .vidioc_enum_input        = vidioc_enum_input, 
 | 
    .vidioc_s_input            = vidioc_s_input, 
 | 
    .vidioc_g_input            = vidioc_g_input, 
 | 
  
 | 
    .vidioc_reqbufs            = vb2_ioctl_reqbufs, 
 | 
    .vidioc_querybuf        = vb2_ioctl_querybuf, 
 | 
    .vidioc_qbuf            = vb2_ioctl_qbuf, 
 | 
    .vidioc_expbuf            = vb2_ioctl_expbuf, 
 | 
    .vidioc_dqbuf            = vb2_ioctl_dqbuf, 
 | 
    .vidioc_create_bufs        = vb2_ioctl_create_bufs, 
 | 
    .vidioc_prepare_buf        = vb2_ioctl_prepare_buf, 
 | 
    .vidioc_streamon        = vb2_ioctl_streamon, 
 | 
    .vidioc_streamoff        = vb2_ioctl_streamoff, 
 | 
  
 | 
    .vidioc_log_status        = v4l2_ctrl_log_status, 
 | 
    .vidioc_subscribe_event        = v4l2_ctrl_subscribe_event, 
 | 
    .vidioc_unsubscribe_event    = v4l2_event_unsubscribe, 
 | 
}; 
 | 
  
 | 
/* ----------------------------------------------------------------------------- 
 | 
 * V4L2 file operations 
 | 
 */ 
 | 
static int sun6i_video_open(struct file *file) 
 | 
{ 
 | 
    struct sun6i_video *video = video_drvdata(file); 
 | 
    int ret; 
 | 
  
 | 
    if (mutex_lock_interruptible(&video->lock)) 
 | 
        return -ERESTARTSYS; 
 | 
  
 | 
    ret = v4l2_fh_open(file); 
 | 
    if (ret < 0) 
 | 
        goto unlock; 
 | 
  
 | 
    ret = v4l2_pipeline_pm_get(&video->vdev.entity); 
 | 
    if (ret < 0) 
 | 
        goto fh_release; 
 | 
  
 | 
    /* check if already powered */ 
 | 
    if (!v4l2_fh_is_singular_file(file)) 
 | 
        goto unlock; 
 | 
  
 | 
    ret = sun6i_csi_set_power(video->csi, true); 
 | 
    if (ret < 0) 
 | 
        goto fh_release; 
 | 
  
 | 
    mutex_unlock(&video->lock); 
 | 
    return 0; 
 | 
  
 | 
fh_release: 
 | 
    v4l2_fh_release(file); 
 | 
unlock: 
 | 
    mutex_unlock(&video->lock); 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static int sun6i_video_close(struct file *file) 
 | 
{ 
 | 
    struct sun6i_video *video = video_drvdata(file); 
 | 
    bool last_fh; 
 | 
  
 | 
    mutex_lock(&video->lock); 
 | 
  
 | 
    last_fh = v4l2_fh_is_singular_file(file); 
 | 
  
 | 
    _vb2_fop_release(file, NULL); 
 | 
  
 | 
    v4l2_pipeline_pm_put(&video->vdev.entity); 
 | 
  
 | 
    if (last_fh) 
 | 
        sun6i_csi_set_power(video->csi, false); 
 | 
  
 | 
    mutex_unlock(&video->lock); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct v4l2_file_operations sun6i_video_fops = { 
 | 
    .owner        = THIS_MODULE, 
 | 
    .open        = sun6i_video_open, 
 | 
    .release    = sun6i_video_close, 
 | 
    .unlocked_ioctl    = video_ioctl2, 
 | 
    .mmap        = vb2_fop_mmap, 
 | 
    .poll        = vb2_fop_poll 
 | 
}; 
 | 
  
 | 
/* ----------------------------------------------------------------------------- 
 | 
 * Media Operations 
 | 
 */ 
 | 
static int sun6i_video_link_validate_get_format(struct media_pad *pad, 
 | 
                        struct v4l2_subdev_format *fmt) 
 | 
{ 
 | 
    if (is_media_entity_v4l2_subdev(pad->entity)) { 
 | 
        struct v4l2_subdev *sd = 
 | 
                media_entity_to_v4l2_subdev(pad->entity); 
 | 
  
 | 
        fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; 
 | 
        fmt->pad = pad->index; 
 | 
        return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); 
 | 
    } 
 | 
  
 | 
    return -EINVAL; 
 | 
} 
 | 
  
 | 
static int sun6i_video_link_validate(struct media_link *link) 
 | 
{ 
 | 
    struct video_device *vdev = container_of(link->sink->entity, 
 | 
                         struct video_device, entity); 
 | 
    struct sun6i_video *video = video_get_drvdata(vdev); 
 | 
    struct v4l2_subdev_format source_fmt; 
 | 
    int ret; 
 | 
  
 | 
    video->mbus_code = 0; 
 | 
  
 | 
    if (!media_entity_remote_pad(link->sink->entity->pads)) { 
 | 
        dev_info(video->csi->dev, 
 | 
             "video node %s pad not connected\n", vdev->name); 
 | 
        return -ENOLINK; 
 | 
    } 
 | 
  
 | 
    ret = sun6i_video_link_validate_get_format(link->source, &source_fmt); 
 | 
    if (ret < 0) 
 | 
        return ret; 
 | 
  
 | 
    if (!sun6i_csi_is_format_supported(video->csi, 
 | 
                       video->fmt.fmt.pix.pixelformat, 
 | 
                       source_fmt.format.code)) { 
 | 
        dev_err(video->csi->dev, 
 | 
            "Unsupported pixformat: 0x%x with mbus code: 0x%x!\n", 
 | 
            video->fmt.fmt.pix.pixelformat, 
 | 
            source_fmt.format.code); 
 | 
        return -EPIPE; 
 | 
    } 
 | 
  
 | 
    if (source_fmt.format.width != video->fmt.fmt.pix.width || 
 | 
        source_fmt.format.height != video->fmt.fmt.pix.height) { 
 | 
        dev_err(video->csi->dev, 
 | 
            "Wrong width or height %ux%u (%ux%u expected)\n", 
 | 
            video->fmt.fmt.pix.width, video->fmt.fmt.pix.height, 
 | 
            source_fmt.format.width, source_fmt.format.height); 
 | 
        return -EPIPE; 
 | 
    } 
 | 
  
 | 
    video->mbus_code = source_fmt.format.code; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct media_entity_operations sun6i_video_media_ops = { 
 | 
    .link_validate = sun6i_video_link_validate 
 | 
}; 
 | 
  
 | 
int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi, 
 | 
             const char *name) 
 | 
{ 
 | 
    struct video_device *vdev = &video->vdev; 
 | 
    struct vb2_queue *vidq = &video->vb2_vidq; 
 | 
    struct v4l2_format fmt = { 0 }; 
 | 
    int ret; 
 | 
  
 | 
    video->csi = csi; 
 | 
  
 | 
    /* Initialize the media entity... */ 
 | 
    video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; 
 | 
    vdev->entity.ops = &sun6i_video_media_ops; 
 | 
    ret = media_entity_pads_init(&vdev->entity, 1, &video->pad); 
 | 
    if (ret < 0) 
 | 
        return ret; 
 | 
  
 | 
    mutex_init(&video->lock); 
 | 
  
 | 
    INIT_LIST_HEAD(&video->dma_queue); 
 | 
    spin_lock_init(&video->dma_queue_lock); 
 | 
  
 | 
    video->sequence = 0; 
 | 
  
 | 
    /* Setup default format */ 
 | 
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
 | 
    fmt.fmt.pix.pixelformat = supported_pixformats[0]; 
 | 
    fmt.fmt.pix.width = 1280; 
 | 
    fmt.fmt.pix.height = 720; 
 | 
    fmt.fmt.pix.field = V4L2_FIELD_NONE; 
 | 
    sun6i_video_set_fmt(video, &fmt); 
 | 
  
 | 
    /* Initialize videobuf2 queue */ 
 | 
    vidq->type            = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
 | 
    vidq->io_modes            = VB2_MMAP | VB2_DMABUF; 
 | 
    vidq->drv_priv            = video; 
 | 
    vidq->buf_struct_size        = sizeof(struct sun6i_csi_buffer); 
 | 
    vidq->ops            = &sun6i_csi_vb2_ops; 
 | 
    vidq->mem_ops            = &vb2_dma_contig_memops; 
 | 
    vidq->timestamp_flags        = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; 
 | 
    vidq->lock            = &video->lock; 
 | 
    /* Make sure non-dropped frame */ 
 | 
    vidq->min_buffers_needed    = 3; 
 | 
    vidq->dev            = csi->dev; 
 | 
  
 | 
    ret = vb2_queue_init(vidq); 
 | 
    if (ret) { 
 | 
        v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret); 
 | 
        goto clean_entity; 
 | 
    } 
 | 
  
 | 
    /* Register video device */ 
 | 
    strscpy(vdev->name, name, sizeof(vdev->name)); 
 | 
    vdev->release        = video_device_release_empty; 
 | 
    vdev->fops        = &sun6i_video_fops; 
 | 
    vdev->ioctl_ops        = &sun6i_video_ioctl_ops; 
 | 
    vdev->vfl_type        = VFL_TYPE_VIDEO; 
 | 
    vdev->vfl_dir        = VFL_DIR_RX; 
 | 
    vdev->v4l2_dev        = &csi->v4l2_dev; 
 | 
    vdev->queue        = vidq; 
 | 
    vdev->lock        = &video->lock; 
 | 
    vdev->device_caps    = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; 
 | 
    video_set_drvdata(vdev, video); 
 | 
  
 | 
    ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); 
 | 
    if (ret < 0) { 
 | 
        v4l2_err(&csi->v4l2_dev, 
 | 
             "video_register_device failed: %d\n", ret); 
 | 
        goto clean_entity; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
  
 | 
clean_entity: 
 | 
    media_entity_cleanup(&video->vdev.entity); 
 | 
    mutex_destroy(&video->lock); 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
void sun6i_video_cleanup(struct sun6i_video *video) 
 | 
{ 
 | 
    vb2_video_unregister_device(&video->vdev); 
 | 
    media_entity_cleanup(&video->vdev.entity); 
 | 
    mutex_destroy(&video->lock); 
 | 
} 
 |