// SPDX-License-Identifier: GPL-2.0 /* * Rockchip VPU codec driver * * Copyright (C) 2018 Collabora, Ltd. * Copyright (C) 2018 Rockchip Electronics Co., Ltd. * Alpha Lin * Jeffy Chen * * Copyright 2018 Google LLC. * Tomasz Figa * * Based on s5p-mfc driver by Samsung Electronics Co., Ltd. * Copyright (C) 2010-2011 Samsung Electronics Co., Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include "rockchip_vpu.h" #include "rockchip_vpu_hw.h" #include "rockchip_vpu_common.h" /** * struct v4l2_format_info - information about a V4L2 format * @format: 4CC format identifier (V4L2_PIX_FMT_*) * @header_size: Size of header, optional and used by compressed formats * @num_planes: Number of planes (1 to 3) * @cpp: Number of bytes per pixel (per plane) * @hsub: Horizontal chroma subsampling factor * @vsub: Vertical chroma subsampling factor * @is_compressed: Is it a compressed format? * @multiplanar: Is it a multiplanar variant format? (e.g. NV12M) */ struct v4l2_format_info { u32 format; u32 header_size; u8 num_planes; u8 cpp[3]; u8 hsub; u8 vsub; u8 is_compressed; u8 multiplanar; }; static const struct v4l2_format_info * v4l2_format_info(u32 format) { static const struct v4l2_format_info formats[] = { { .format = V4L2_PIX_FMT_YUV420M, .num_planes = 3, .cpp = { 1, 1, 1 }, .hsub = 2, .vsub = 2, .multiplanar = 1 }, { .format = V4L2_PIX_FMT_NV12M, .num_planes = 2, .cpp = { 1, 2, 0 }, .hsub = 2, .vsub = 2, .multiplanar = 1 }, { .format = V4L2_PIX_FMT_YUYV, .num_planes = 1, .cpp = { 2, 0, 0 }, .hsub = 2, .vsub = 1 }, { .format = V4L2_PIX_FMT_UYVY, .num_planes = 1, .cpp = { 2, 0, 0 }, .hsub = 2, .vsub = 1 }, }; unsigned int i; for (i = 0; i < ARRAY_SIZE(formats); ++i) { if (formats[i].format == format) return &formats[i]; } vpu_err("Unsupported V4L 4CC format (%08x)\n", format); return NULL; } static void fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, int pixelformat, int width, int height) { const struct v4l2_format_info *info; struct v4l2_plane_pix_format *plane; int i; info = v4l2_format_info(pixelformat); if (!info) return; pixfmt->width = width; pixfmt->height = height; pixfmt->pixelformat = pixelformat; if (!info->multiplanar) { pixfmt->num_planes = 1; plane = &pixfmt->plane_fmt[0]; plane->bytesperline = info->is_compressed ? 0 : width * info->cpp[0]; plane->sizeimage = info->header_size; for (i = 0; i < info->num_planes; i++) { unsigned int hsub = (i == 0) ? 1 : info->hsub; unsigned int vsub = (i == 0) ? 1 : info->vsub; plane->sizeimage += info->cpp[i] * DIV_ROUND_UP(width, hsub) * DIV_ROUND_UP(height, vsub); } } else { pixfmt->num_planes = info->num_planes; for (i = 0; i < info->num_planes; i++) { unsigned int hsub = (i == 0) ? 1 : info->hsub; unsigned int vsub = (i == 0) ? 1 : info->vsub; plane = &pixfmt->plane_fmt[i]; plane->bytesperline = info->cpp[i] * DIV_ROUND_UP(width, hsub); plane->sizeimage = plane->bytesperline * DIV_ROUND_UP(height, vsub); } } } static const struct rockchip_vpu_fmt * rockchip_vpu_find_format(struct rockchip_vpu_ctx *ctx, u32 fourcc) { struct rockchip_vpu_dev *dev = ctx->dev; const struct rockchip_vpu_fmt *formats; unsigned int num_fmts, i; formats = dev->variant->enc_fmts; num_fmts = dev->variant->num_enc_fmts; for (i = 0; i < num_fmts; i++) if (formats[i].fourcc == fourcc) return &formats[i]; return NULL; } static const struct rockchip_vpu_fmt * rockchip_vpu_get_default_fmt(struct rockchip_vpu_ctx *ctx, bool bitstream) { struct rockchip_vpu_dev *dev = ctx->dev; const struct rockchip_vpu_fmt *formats; unsigned int num_fmts, i; formats = dev->variant->enc_fmts; num_fmts = dev->variant->num_enc_fmts; for (i = 0; i < num_fmts; i++) { if (bitstream == (formats[i].codec_mode != RK_VPU_MODE_NONE)) return &formats[i]; } return NULL; } static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { struct rockchip_vpu_dev *vpu = video_drvdata(file); strscpy(cap->driver, vpu->dev->driver->name, sizeof(cap->driver)); strscpy(cap->card, vpu->vfd_enc->name, sizeof(cap->card)); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform: %s", vpu->dev->driver->name); return 0; } static int vidioc_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize) { struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); const struct rockchip_vpu_fmt *fmt; if (fsize->index != 0) { vpu_debug(0, "invalid frame size index (expected 0, got %d)\n", fsize->index); return -EINVAL; } fmt = rockchip_vpu_find_format(ctx, fsize->pixel_format); if (!fmt) { vpu_debug(0, "unsupported bitstream format (%08x)\n", fsize->pixel_format); return -EINVAL; } /* This only makes sense for coded formats */ if (fmt->codec_mode == RK_VPU_MODE_NONE) return -EINVAL; fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; fsize->stepwise = fmt->frmsize; return 0; } static int vidioc_enum_fmt_vid_cap_mplane(struct file *file, void *priv, struct v4l2_fmtdesc *f) { struct rockchip_vpu_dev *dev = video_drvdata(file); const struct rockchip_vpu_fmt *fmt; const struct rockchip_vpu_fmt *formats; int num_fmts, i, j = 0; formats = dev->variant->enc_fmts; num_fmts = dev->variant->num_enc_fmts; for (i = 0; i < num_fmts; i++) { /* Skip uncompressed formats */ if (formats[i].codec_mode == RK_VPU_MODE_NONE) continue; if (j == f->index) { fmt = &formats[i]; f->pixelformat = fmt->fourcc; return 0; } ++j; } return -EINVAL; } static int vidioc_enum_fmt_vid_out_mplane(struct file *file, void *priv, struct v4l2_fmtdesc *f) { struct rockchip_vpu_dev *dev = video_drvdata(file); const struct rockchip_vpu_fmt *formats; const struct rockchip_vpu_fmt *fmt; int num_fmts, i, j = 0; formats = dev->variant->enc_fmts; num_fmts = dev->variant->num_enc_fmts; for (i = 0; i < num_fmts; i++) { if (formats[i].codec_mode != RK_VPU_MODE_NONE) continue; if (j == f->index) { fmt = &formats[i]; f->pixelformat = fmt->fourcc; return 0; } ++j; } return -EINVAL; } static int vidioc_g_fmt_out_mplane(struct file *file, void *priv, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); vpu_debug(4, "f->type = %d\n", f->type); *pix_mp = ctx->src_fmt; return 0; } static int vidioc_g_fmt_cap_mplane(struct file *file, void *priv, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); vpu_debug(4, "f->type = %d\n", f->type); *pix_mp = ctx->dst_fmt; return 0; } static int vidioc_try_fmt_cap_mplane(struct file *file, void *priv, struct v4l2_format *f) { struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; const struct rockchip_vpu_fmt *fmt; vpu_debug(4, "%c%c%c%c\n", (pix_mp->pixelformat & 0x7f), (pix_mp->pixelformat >> 8) & 0x7f, (pix_mp->pixelformat >> 16) & 0x7f, (pix_mp->pixelformat >> 24) & 0x7f); fmt = rockchip_vpu_find_format(ctx, pix_mp->pixelformat); if (!fmt) { fmt = rockchip_vpu_get_default_fmt(ctx, true); f->fmt.pix.pixelformat = fmt->fourcc; } pix_mp->num_planes = 1; pix_mp->field = V4L2_FIELD_NONE; pix_mp->width = clamp(pix_mp->width, fmt->frmsize.min_width, fmt->frmsize.max_width); pix_mp->height = clamp(pix_mp->height, fmt->frmsize.min_height, fmt->frmsize.max_height); /* Round up to macroblocks. */ pix_mp->width = round_up(pix_mp->width, JPEG_MB_DIM); pix_mp->height = round_up(pix_mp->height, JPEG_MB_DIM); /* * For compressed formats the application can specify * sizeimage. If the application passes a zero sizeimage, * let's default to the maximum frame size. */ if (!pix_mp->plane_fmt[0].sizeimage) pix_mp->plane_fmt[0].sizeimage = fmt->header_size + pix_mp->width * pix_mp->height * fmt->max_depth; memset(pix_mp->plane_fmt[0].reserved, 0, sizeof(pix_mp->plane_fmt[0].reserved)); return 0; } static int vidioc_try_fmt_out_mplane(struct file *file, void *priv, struct v4l2_format *f) { struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; const struct rockchip_vpu_fmt *fmt; unsigned int width, height; int i; vpu_debug(4, "%c%c%c%c\n", (pix_mp->pixelformat & 0x7f), (pix_mp->pixelformat >> 8) & 0x7f, (pix_mp->pixelformat >> 16) & 0x7f, (pix_mp->pixelformat >> 24) & 0x7f); fmt = rockchip_vpu_find_format(ctx, pix_mp->pixelformat); if (!fmt) { fmt = rockchip_vpu_get_default_fmt(ctx, false); f->fmt.pix.pixelformat = fmt->fourcc; } pix_mp->field = V4L2_FIELD_NONE; width = clamp(pix_mp->width, ctx->vpu_dst_fmt->frmsize.min_width, ctx->vpu_dst_fmt->frmsize.max_width); height = clamp(pix_mp->height, ctx->vpu_dst_fmt->frmsize.min_height, ctx->vpu_dst_fmt->frmsize.max_height); /* Round up to macroblocks. */ width = round_up(width, JPEG_MB_DIM); height = round_up(height, JPEG_MB_DIM); /* Fill remaining fields */ fill_pixfmt_mp(pix_mp, fmt->fourcc, width, height); for (i = 0; i < pix_mp->num_planes; i++) { memset(pix_mp->plane_fmt[i].reserved, 0, sizeof(pix_mp->plane_fmt[i].reserved)); } return 0; } void rockchip_vpu_enc_reset_dst_fmt(struct rockchip_vpu_dev *vpu, struct rockchip_vpu_ctx *ctx) { struct v4l2_pix_format_mplane *fmt = &ctx->dst_fmt; ctx->vpu_dst_fmt = rockchip_vpu_get_default_fmt(ctx, true); memset(fmt, 0, sizeof(*fmt)); fmt->num_planes = 1; fmt->width = clamp(fmt->width, ctx->vpu_dst_fmt->frmsize.min_width, ctx->vpu_dst_fmt->frmsize.max_width); fmt->height = clamp(fmt->height, ctx->vpu_dst_fmt->frmsize.min_height, ctx->vpu_dst_fmt->frmsize.max_height); fmt->pixelformat = ctx->vpu_dst_fmt->fourcc; fmt->field = V4L2_FIELD_NONE; fmt->colorspace = V4L2_COLORSPACE_JPEG, fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; fmt->quantization = V4L2_QUANTIZATION_DEFAULT; fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; fmt->plane_fmt[0].sizeimage = ctx->vpu_dst_fmt->header_size + fmt->width * fmt->height * ctx->vpu_dst_fmt->max_depth; } void rockchip_vpu_enc_reset_src_fmt(struct rockchip_vpu_dev *vpu, struct rockchip_vpu_ctx *ctx) { struct v4l2_pix_format_mplane *fmt = &ctx->src_fmt; unsigned int width, height; ctx->vpu_src_fmt = rockchip_vpu_get_default_fmt(ctx, false); memset(fmt, 0, sizeof(*fmt)); width = clamp(fmt->width, ctx->vpu_dst_fmt->frmsize.min_width, ctx->vpu_dst_fmt->frmsize.max_width); height = clamp(fmt->height, ctx->vpu_dst_fmt->frmsize.min_height, ctx->vpu_dst_fmt->frmsize.max_height); fmt->field = V4L2_FIELD_NONE; fmt->colorspace = V4L2_COLORSPACE_JPEG, fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; fmt->quantization = V4L2_QUANTIZATION_DEFAULT; fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; fill_pixfmt_mp(fmt, ctx->vpu_src_fmt->fourcc, width, height); } static int vidioc_s_fmt_out_mplane(struct file *file, void *priv, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); struct vb2_queue *vq; int ret; /* Change not allowed if queue is streaming. */ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (vb2_is_streaming(vq)) return -EBUSY; ret = vidioc_try_fmt_out_mplane(file, priv, f); if (ret) return ret; ctx->vpu_src_fmt = rockchip_vpu_find_format(ctx, pix_mp->pixelformat); ctx->src_fmt = *pix_mp; /* Propagate to the CAPTURE format */ ctx->dst_fmt.colorspace = pix_mp->colorspace; ctx->dst_fmt.ycbcr_enc = pix_mp->ycbcr_enc; ctx->dst_fmt.xfer_func = pix_mp->xfer_func; ctx->dst_fmt.quantization = pix_mp->quantization; ctx->dst_fmt.width = pix_mp->width; ctx->dst_fmt.height = pix_mp->height; vpu_debug(0, "OUTPUT codec mode: %d\n", ctx->vpu_src_fmt->codec_mode); vpu_debug(0, "fmt - w: %d, h: %d, mb - w: %d, h: %d\n", pix_mp->width, pix_mp->height, JPEG_MB_WIDTH(pix_mp->width), JPEG_MB_HEIGHT(pix_mp->height)); return 0; } static int vidioc_s_fmt_cap_mplane(struct file *file, void *priv, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv); struct rockchip_vpu_dev *vpu = ctx->dev; struct vb2_queue *vq, *peer_vq; int ret; /* Change not allowed if queue is streaming. */ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); if (vb2_is_streaming(vq)) return -EBUSY; /* * Since format change on the CAPTURE queue will reset * the OUTPUT queue, we can't allow doing so * when the OUTPUT queue has buffers allocated. */ peer_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); if (vb2_is_busy(peer_vq) && (pix_mp->pixelformat != ctx->dst_fmt.pixelformat || pix_mp->height != ctx->dst_fmt.height || pix_mp->width != ctx->dst_fmt.width)) return -EBUSY; ret = vidioc_try_fmt_cap_mplane(file, priv, f); if (ret) return ret; ctx->vpu_dst_fmt = rockchip_vpu_find_format(ctx, pix_mp->pixelformat); ctx->dst_fmt = *pix_mp; vpu_debug(0, "CAPTURE codec mode: %d\n", ctx->vpu_dst_fmt->codec_mode); vpu_debug(0, "fmt - w: %d, h: %d, mb - w: %d, h: %d\n", pix_mp->width, pix_mp->height, JPEG_MB_WIDTH(pix_mp->width), JPEG_MB_HEIGHT(pix_mp->height)); /* * Current raw format might have become invalid with newly * selected codec, so reset it to default just to be safe and * keep internal driver state sane. User is mandated to set * the raw format again after we return, so we don't need * anything smarter. */ rockchip_vpu_enc_reset_src_fmt(vpu, ctx); return 0; } const struct v4l2_ioctl_ops rockchip_vpu_enc_ioctl_ops = { .vidioc_querycap = vidioc_querycap, .vidioc_enum_framesizes = vidioc_enum_framesizes, .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_cap_mplane, .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_out_mplane, .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_out_mplane, .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_cap_mplane, .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_out_mplane, .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_cap_mplane, .vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_out_mplane, .vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_cap_mplane, .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_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_streamon = v4l2_m2m_ioctl_streamon, .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, }; static int rockchip_vpu_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], struct device *alloc_devs[]) { struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(vq); struct v4l2_pix_format_mplane *pixfmt; int i; switch (vq->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: pixfmt = &ctx->dst_fmt; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: pixfmt = &ctx->src_fmt; break; default: vpu_err("invalid queue type: %d\n", vq->type); return -EINVAL; } if (*num_planes) { if (*num_planes != pixfmt->num_planes) return -EINVAL; for (i = 0; i < pixfmt->num_planes; ++i) if (sizes[i] < pixfmt->plane_fmt[i].sizeimage) return -EINVAL; return 0; } *num_planes = pixfmt->num_planes; for (i = 0; i < pixfmt->num_planes; ++i) sizes[i] = pixfmt->plane_fmt[i].sizeimage; return 0; } static int rockchip_vpu_buf_prepare(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vb2_queue *vq = vb->vb2_queue; struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(vq); struct v4l2_pix_format_mplane *pixfmt; unsigned int sz; int ret = 0; int i; switch (vq->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: pixfmt = &ctx->dst_fmt; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: pixfmt = &ctx->src_fmt; if (vbuf->field == V4L2_FIELD_ANY) vbuf->field = V4L2_FIELD_NONE; if (vbuf->field != V4L2_FIELD_NONE) { vpu_debug(4, "field %d not supported\n", vbuf->field); return -EINVAL; } break; default: vpu_err("invalid queue type: %d\n", vq->type); return -EINVAL; } for (i = 0; i < pixfmt->num_planes; ++i) { sz = pixfmt->plane_fmt[i].sizeimage; vpu_debug(4, "plane %d size: %ld, sizeimage: %u\n", i, vb2_plane_size(vb, i), sz); if (vb2_plane_size(vb, i) < sz) { vpu_err("plane %d is too small\n", i); ret = -EINVAL; break; } } return ret; } static void rockchip_vpu_buf_queue(struct vb2_buffer *vb) { struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); } static int rockchip_vpu_start_streaming(struct vb2_queue *q, unsigned int count) { struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(q); enum rockchip_vpu_codec_mode codec_mode; if (V4L2_TYPE_IS_OUTPUT(q->type)) ctx->sequence_out = 0; else ctx->sequence_cap = 0; /* Set codec_ops for the chosen destination format */ codec_mode = ctx->vpu_dst_fmt->codec_mode; vpu_debug(4, "Codec mode = %d\n", codec_mode); ctx->codec_ops = &ctx->dev->variant->codec_ops[codec_mode]; /* A bounce buffer is needed for the JPEG payload */ if (!V4L2_TYPE_IS_OUTPUT(q->type)) { ctx->bounce_size = ctx->dst_fmt.plane_fmt[0].sizeimage - ctx->vpu_dst_fmt->header_size; ctx->bounce_buf = dma_alloc_attrs(ctx->dev->dev, ctx->bounce_size, &ctx->bounce_dma_addr, GFP_KERNEL, DMA_ATTR_ALLOC_SINGLE_PAGES); } return 0; } static void rockchip_vpu_stop_streaming(struct vb2_queue *q) { struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(q); if (!V4L2_TYPE_IS_OUTPUT(q->type)) dma_free_attrs(ctx->dev->dev, ctx->bounce_size, ctx->bounce_buf, ctx->bounce_dma_addr, DMA_ATTR_ALLOC_SINGLE_PAGES); /* * The mem2mem framework calls v4l2_m2m_cancel_job before * .stop_streaming, so there isn't any job running and * it is safe to return all the buffers. */ for (;;) { struct vb2_v4l2_buffer *vbuf; if (V4L2_TYPE_IS_OUTPUT(q->type)) 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); } } const struct vb2_ops rockchip_vpu_enc_queue_ops = { .queue_setup = rockchip_vpu_queue_setup, .buf_prepare = rockchip_vpu_buf_prepare, .buf_queue = rockchip_vpu_buf_queue, .start_streaming = rockchip_vpu_start_streaming, .stop_streaming = rockchip_vpu_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, };