| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Cedrus VPU driver |
| * |
| * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> |
| * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> |
| * Copyright (C) 2018 Bootlin |
| * |
| * Based on the vim2m driver, that is: |
| * |
| * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. |
| * Pawel Osciak, <pawel@osciak.com> |
| * Marek Szyprowski, <m.szyprowski@samsung.com> |
| */ |
| |
| #include <linux/pm_runtime.h> |
| |
| #include <media/videobuf2-dma-contig.h> |
| #include <media/v4l2-device.h> |
| #include <media/v4l2-ioctl.h> |
| #include <media/v4l2-event.h> |
| #include <media/v4l2-mem2mem.h> |
| |
| #include "cedrus.h" |
| #include "cedrus_video.h" |
| #include "cedrus_dec.h" |
| #include "cedrus_hw.h" |
| |
| #define CEDRUS_DECODE_SRC BIT(0) |
| #define CEDRUS_DECODE_DST BIT(1) |
| |
| #define CEDRUS_MIN_WIDTH 16U |
| #define CEDRUS_MIN_HEIGHT 16U |
| #define CEDRUS_MAX_WIDTH 4096U |
| #define CEDRUS_MAX_HEIGHT 2304U |
| |
| static struct cedrus_format cedrus_formats[] = { |
| { |
| .pixelformat = V4L2_PIX_FMT_MPEG2_SLICE, |
| .directions = CEDRUS_DECODE_SRC, |
| .capabilities = CEDRUS_CAPABILITY_MPEG2_DEC, |
| }, |
| { |
| .pixelformat = V4L2_PIX_FMT_H264_SLICE, |
| .directions = CEDRUS_DECODE_SRC, |
| .capabilities = CEDRUS_CAPABILITY_H264_DEC, |
| }, |
| { |
| .pixelformat = V4L2_PIX_FMT_HEVC_SLICE, |
| .directions = CEDRUS_DECODE_SRC, |
| .capabilities = CEDRUS_CAPABILITY_H265_DEC, |
| }, |
| { |
| .pixelformat = V4L2_PIX_FMT_VP8_FRAME, |
| .directions = CEDRUS_DECODE_SRC, |
| .capabilities = CEDRUS_CAPABILITY_VP8_DEC, |
| }, |
| { |
| .pixelformat = V4L2_PIX_FMT_NV12_32L32, |
| .directions = CEDRUS_DECODE_DST, |
| }, |
| { |
| .pixelformat = V4L2_PIX_FMT_NV12, |
| .directions = CEDRUS_DECODE_DST, |
| .capabilities = CEDRUS_CAPABILITY_UNTILED, |
| }, |
| }; |
| |
| #define CEDRUS_FORMATS_COUNT ARRAY_SIZE(cedrus_formats) |
| |
| static inline struct cedrus_ctx *cedrus_file2ctx(struct file *file) |
| { |
| return container_of(file->private_data, struct cedrus_ctx, fh); |
| } |
| |
| static struct cedrus_format *cedrus_find_format(u32 pixelformat, u32 directions, |
| unsigned int capabilities) |
| { |
| struct cedrus_format *first_valid_fmt = NULL; |
| struct cedrus_format *fmt; |
| unsigned int i; |
| |
| for (i = 0; i < CEDRUS_FORMATS_COUNT; i++) { |
| fmt = &cedrus_formats[i]; |
| |
| if ((fmt->capabilities & capabilities) != fmt->capabilities || |
| !(fmt->directions & directions)) |
| continue; |
| |
| if (fmt->pixelformat == pixelformat) |
| break; |
| |
| if (!first_valid_fmt) |
| first_valid_fmt = fmt; |
| } |
| |
| if (i == CEDRUS_FORMATS_COUNT) |
| return first_valid_fmt; |
| |
| return &cedrus_formats[i]; |
| } |
| |
| void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt) |
| { |
| unsigned int width = pix_fmt->width; |
| unsigned int height = pix_fmt->height; |
| unsigned int sizeimage = pix_fmt->sizeimage; |
| unsigned int bytesperline = pix_fmt->bytesperline; |
| |
| pix_fmt->field = V4L2_FIELD_NONE; |
| |
| /* Limit to hardware min/max. */ |
| width = clamp(width, CEDRUS_MIN_WIDTH, CEDRUS_MAX_WIDTH); |
| height = clamp(height, CEDRUS_MIN_HEIGHT, CEDRUS_MAX_HEIGHT); |
| |
| switch (pix_fmt->pixelformat) { |
| case V4L2_PIX_FMT_MPEG2_SLICE: |
| case V4L2_PIX_FMT_H264_SLICE: |
| case V4L2_PIX_FMT_HEVC_SLICE: |
| case V4L2_PIX_FMT_VP8_FRAME: |
| /* Zero bytes per line for encoded source. */ |
| bytesperline = 0; |
| /* Choose some minimum size since this can't be 0 */ |
| sizeimage = max_t(u32, SZ_1K, sizeimage); |
| break; |
| |
| case V4L2_PIX_FMT_NV12_32L32: |
| /* 32-aligned stride. */ |
| bytesperline = ALIGN(width, 32); |
| |
| /* 32-aligned height. */ |
| height = ALIGN(height, 32); |
| |
| /* Luma plane size. */ |
| sizeimage = bytesperline * height; |
| |
| /* Chroma plane size. */ |
| sizeimage += bytesperline * ALIGN(height, 64) / 2; |
| |
| break; |
| |
| case V4L2_PIX_FMT_NV12: |
| /* 16-aligned stride. */ |
| bytesperline = ALIGN(width, 16); |
| |
| /* 16-aligned height. */ |
| height = ALIGN(height, 16); |
| |
| /* Luma plane size. */ |
| sizeimage = bytesperline * height; |
| |
| /* Chroma plane size. */ |
| sizeimage += bytesperline * height / 2; |
| |
| break; |
| } |
| |
| pix_fmt->width = width; |
| pix_fmt->height = height; |
| |
| pix_fmt->bytesperline = bytesperline; |
| pix_fmt->sizeimage = sizeimage; |
| } |
| |
| static int cedrus_querycap(struct file *file, void *priv, |
| struct v4l2_capability *cap) |
| { |
| strscpy(cap->driver, CEDRUS_NAME, sizeof(cap->driver)); |
| strscpy(cap->card, CEDRUS_NAME, sizeof(cap->card)); |
| snprintf(cap->bus_info, sizeof(cap->bus_info), |
| "platform:%s", CEDRUS_NAME); |
| |
| return 0; |
| } |
| |
| static int cedrus_enum_fmt(struct file *file, struct v4l2_fmtdesc *f, |
| u32 direction) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| struct cedrus_dev *dev = ctx->dev; |
| unsigned int capabilities = dev->capabilities; |
| struct cedrus_format *fmt; |
| unsigned int i, index; |
| |
| /* Index among formats that match the requested direction. */ |
| index = 0; |
| |
| for (i = 0; i < CEDRUS_FORMATS_COUNT; i++) { |
| fmt = &cedrus_formats[i]; |
| |
| if (fmt->capabilities && (fmt->capabilities & capabilities) != |
| fmt->capabilities) |
| continue; |
| |
| if (!(cedrus_formats[i].directions & direction)) |
| continue; |
| |
| if (index == f->index) |
| break; |
| |
| index++; |
| } |
| |
| /* Matched format. */ |
| if (i < CEDRUS_FORMATS_COUNT) { |
| f->pixelformat = cedrus_formats[i].pixelformat; |
| |
| return 0; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int cedrus_enum_fmt_vid_cap(struct file *file, void *priv, |
| struct v4l2_fmtdesc *f) |
| { |
| return cedrus_enum_fmt(file, f, CEDRUS_DECODE_DST); |
| } |
| |
| static int cedrus_enum_fmt_vid_out(struct file *file, void *priv, |
| struct v4l2_fmtdesc *f) |
| { |
| return cedrus_enum_fmt(file, f, CEDRUS_DECODE_SRC); |
| } |
| |
| static int cedrus_g_fmt_vid_cap(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| |
| f->fmt.pix = ctx->dst_fmt; |
| return 0; |
| } |
| |
| static int cedrus_g_fmt_vid_out(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| |
| f->fmt.pix = ctx->src_fmt; |
| return 0; |
| } |
| |
| static int cedrus_try_fmt_vid_cap(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| struct cedrus_dev *dev = ctx->dev; |
| struct v4l2_pix_format *pix_fmt = &f->fmt.pix; |
| struct cedrus_format *fmt = |
| cedrus_find_format(pix_fmt->pixelformat, CEDRUS_DECODE_DST, |
| dev->capabilities); |
| |
| if (!fmt) |
| return -EINVAL; |
| |
| pix_fmt->pixelformat = fmt->pixelformat; |
| pix_fmt->width = ctx->src_fmt.width; |
| pix_fmt->height = ctx->src_fmt.height; |
| cedrus_prepare_format(pix_fmt); |
| |
| return 0; |
| } |
| |
| static int cedrus_try_fmt_vid_out(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| struct cedrus_dev *dev = ctx->dev; |
| struct v4l2_pix_format *pix_fmt = &f->fmt.pix; |
| struct cedrus_format *fmt = |
| cedrus_find_format(pix_fmt->pixelformat, CEDRUS_DECODE_SRC, |
| dev->capabilities); |
| |
| if (!fmt) |
| return -EINVAL; |
| |
| pix_fmt->pixelformat = fmt->pixelformat; |
| cedrus_prepare_format(pix_fmt); |
| |
| return 0; |
| } |
| |
| static int cedrus_s_fmt_vid_cap(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| struct vb2_queue *vq; |
| int ret; |
| |
| vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); |
| if (vb2_is_busy(vq)) |
| return -EBUSY; |
| |
| ret = cedrus_try_fmt_vid_cap(file, priv, f); |
| if (ret) |
| return ret; |
| |
| ctx->dst_fmt = f->fmt.pix; |
| |
| return 0; |
| } |
| |
| static int cedrus_s_fmt_vid_out(struct file *file, void *priv, |
| struct v4l2_format *f) |
| { |
| struct cedrus_ctx *ctx = cedrus_file2ctx(file); |
| struct vb2_queue *vq; |
| struct vb2_queue *peer_vq; |
| int ret; |
| |
| ret = cedrus_try_fmt_vid_out(file, priv, f); |
| if (ret) |
| return ret; |
| |
| vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); |
| /* |
| * In order to support dynamic resolution change, |
| * the decoder admits a resolution change, as long |
| * as the pixelformat remains. Can't be done if streaming. |
| */ |
| if (vb2_is_streaming(vq) || (vb2_is_busy(vq) && |
| f->fmt.pix.pixelformat != ctx->src_fmt.pixelformat)) |
| return -EBUSY; |
| /* |
| * Since format change on the OUTPUT queue will reset |
| * the CAPTURE queue, we can't allow doing so |
| * when the CAPTURE queue has buffers allocated. |
| */ |
| peer_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, |
| V4L2_BUF_TYPE_VIDEO_CAPTURE); |
| if (vb2_is_busy(peer_vq)) |
| return -EBUSY; |
| |
| ret = cedrus_try_fmt_vid_out(file, priv, f); |
| if (ret) |
| return ret; |
| |
| ctx->src_fmt = f->fmt.pix; |
| |
| switch (ctx->src_fmt.pixelformat) { |
| case V4L2_PIX_FMT_H264_SLICE: |
| case V4L2_PIX_FMT_HEVC_SLICE: |
| vq->subsystem_flags |= |
| VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; |
| break; |
| default: |
| vq->subsystem_flags &= |
| ~VB2_V4L2_FL_SUPPORTS_M2M_HOLD_CAPTURE_BUF; |
| break; |
| } |
| |
| /* Propagate format information to capture. */ |
| ctx->dst_fmt.colorspace = f->fmt.pix.colorspace; |
| ctx->dst_fmt.xfer_func = f->fmt.pix.xfer_func; |
| ctx->dst_fmt.ycbcr_enc = f->fmt.pix.ycbcr_enc; |
| ctx->dst_fmt.quantization = f->fmt.pix.quantization; |
| ctx->dst_fmt.width = ctx->src_fmt.width; |
| ctx->dst_fmt.height = ctx->src_fmt.height; |
| cedrus_prepare_format(&ctx->dst_fmt); |
| |
| return 0; |
| } |
| |
| const struct v4l2_ioctl_ops cedrus_ioctl_ops = { |
| .vidioc_querycap = cedrus_querycap, |
| |
| .vidioc_enum_fmt_vid_cap = cedrus_enum_fmt_vid_cap, |
| .vidioc_g_fmt_vid_cap = cedrus_g_fmt_vid_cap, |
| .vidioc_try_fmt_vid_cap = cedrus_try_fmt_vid_cap, |
| .vidioc_s_fmt_vid_cap = cedrus_s_fmt_vid_cap, |
| |
| .vidioc_enum_fmt_vid_out = cedrus_enum_fmt_vid_out, |
| .vidioc_g_fmt_vid_out = cedrus_g_fmt_vid_out, |
| .vidioc_try_fmt_vid_out = cedrus_try_fmt_vid_out, |
| .vidioc_s_fmt_vid_out = cedrus_s_fmt_vid_out, |
| |
| .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_prepare_buf = v4l2_m2m_ioctl_prepare_buf, |
| .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, |
| .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, |
| |
| .vidioc_streamon = v4l2_m2m_ioctl_streamon, |
| .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, |
| |
| .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd, |
| .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd, |
| |
| .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| }; |
| |
| static int cedrus_queue_setup(struct vb2_queue *vq, unsigned int *nbufs, |
| unsigned int *nplanes, unsigned int sizes[], |
| struct device *alloc_devs[]) |
| { |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vq); |
| struct v4l2_pix_format *pix_fmt; |
| |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) |
| pix_fmt = &ctx->src_fmt; |
| else |
| pix_fmt = &ctx->dst_fmt; |
| |
| if (*nplanes) { |
| if (sizes[0] < pix_fmt->sizeimage) |
| return -EINVAL; |
| } else { |
| sizes[0] = pix_fmt->sizeimage; |
| *nplanes = 1; |
| } |
| |
| return 0; |
| } |
| |
| static void cedrus_queue_cleanup(struct vb2_queue *vq, u32 state) |
| { |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vq); |
| struct vb2_v4l2_buffer *vbuf; |
| |
| for (;;) { |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) |
| vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); |
| else |
| vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); |
| |
| if (!vbuf) |
| return; |
| |
| v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, |
| &ctx->hdl); |
| v4l2_m2m_buf_done(vbuf, state); |
| } |
| } |
| |
| static int cedrus_buf_out_validate(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| |
| vbuf->field = V4L2_FIELD_NONE; |
| return 0; |
| } |
| |
| static int cedrus_buf_prepare(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vq); |
| struct v4l2_pix_format *pix_fmt; |
| |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) |
| pix_fmt = &ctx->src_fmt; |
| else |
| pix_fmt = &ctx->dst_fmt; |
| |
| if (vb2_plane_size(vb, 0) < pix_fmt->sizeimage) |
| return -EINVAL; |
| |
| /* |
| * Buffer's bytesused must be written by driver for CAPTURE buffers. |
| * (for OUTPUT buffers, if userspace passes 0 bytesused, v4l2-core sets |
| * it to buffer length). |
| */ |
| if (V4L2_TYPE_IS_CAPTURE(vq->type)) |
| vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); |
| |
| return 0; |
| } |
| |
| static int cedrus_start_streaming(struct vb2_queue *vq, unsigned int count) |
| { |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vq); |
| struct cedrus_dev *dev = ctx->dev; |
| int ret = 0; |
| |
| switch (ctx->src_fmt.pixelformat) { |
| case V4L2_PIX_FMT_MPEG2_SLICE: |
| ctx->current_codec = CEDRUS_CODEC_MPEG2; |
| break; |
| |
| case V4L2_PIX_FMT_H264_SLICE: |
| ctx->current_codec = CEDRUS_CODEC_H264; |
| break; |
| |
| case V4L2_PIX_FMT_HEVC_SLICE: |
| ctx->current_codec = CEDRUS_CODEC_H265; |
| break; |
| |
| case V4L2_PIX_FMT_VP8_FRAME: |
| ctx->current_codec = CEDRUS_CODEC_VP8; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) { |
| ret = pm_runtime_resume_and_get(dev->dev); |
| if (ret < 0) |
| goto err_cleanup; |
| |
| if (dev->dec_ops[ctx->current_codec]->start) { |
| ret = dev->dec_ops[ctx->current_codec]->start(ctx); |
| if (ret) |
| goto err_pm; |
| } |
| } |
| |
| return 0; |
| |
| err_pm: |
| pm_runtime_put(dev->dev); |
| err_cleanup: |
| cedrus_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); |
| |
| return ret; |
| } |
| |
| static void cedrus_stop_streaming(struct vb2_queue *vq) |
| { |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vq); |
| struct cedrus_dev *dev = ctx->dev; |
| |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) { |
| if (dev->dec_ops[ctx->current_codec]->stop) |
| dev->dec_ops[ctx->current_codec]->stop(ctx); |
| |
| pm_runtime_put(dev->dev); |
| } |
| |
| cedrus_queue_cleanup(vq, VB2_BUF_STATE_ERROR); |
| } |
| |
| static void cedrus_buf_queue(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| |
| v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); |
| } |
| |
| static void cedrus_buf_request_complete(struct vb2_buffer *vb) |
| { |
| struct cedrus_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| |
| v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); |
| } |
| |
| static struct vb2_ops cedrus_qops = { |
| .queue_setup = cedrus_queue_setup, |
| .buf_prepare = cedrus_buf_prepare, |
| .buf_queue = cedrus_buf_queue, |
| .buf_out_validate = cedrus_buf_out_validate, |
| .buf_request_complete = cedrus_buf_request_complete, |
| .start_streaming = cedrus_start_streaming, |
| .stop_streaming = cedrus_stop_streaming, |
| .wait_prepare = vb2_ops_wait_prepare, |
| .wait_finish = vb2_ops_wait_finish, |
| }; |
| |
| int cedrus_queue_init(void *priv, struct vb2_queue *src_vq, |
| struct vb2_queue *dst_vq) |
| { |
| struct cedrus_ctx *ctx = priv; |
| int ret; |
| |
| src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; |
| src_vq->io_modes = VB2_MMAP | VB2_DMABUF; |
| src_vq->dma_attrs = DMA_ATTR_NO_KERNEL_MAPPING; |
| src_vq->drv_priv = ctx; |
| src_vq->buf_struct_size = sizeof(struct cedrus_buffer); |
| src_vq->ops = &cedrus_qops; |
| src_vq->mem_ops = &vb2_dma_contig_memops; |
| src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| src_vq->lock = &ctx->dev->dev_mutex; |
| src_vq->dev = ctx->dev->dev; |
| src_vq->supports_requests = true; |
| src_vq->requires_requests = true; |
| |
| ret = vb2_queue_init(src_vq); |
| if (ret) |
| return ret; |
| |
| dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; |
| dst_vq->drv_priv = ctx; |
| dst_vq->buf_struct_size = sizeof(struct cedrus_buffer); |
| dst_vq->ops = &cedrus_qops; |
| dst_vq->mem_ops = &vb2_dma_contig_memops; |
| dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| dst_vq->lock = &ctx->dev->dev_mutex; |
| dst_vq->dev = ctx->dev->dev; |
| |
| return vb2_queue_init(dst_vq); |
| } |