diff --git a/projects/Allwinner/linux/linux.aarch64.conf b/projects/Allwinner/linux/linux.aarch64.conf index b05ad99598..4c6e67b678 100644 --- a/projects/Allwinner/linux/linux.aarch64.conf +++ b/projects/Allwinner/linux/linux.aarch64.conf @@ -3306,6 +3306,7 @@ CONFIG_V4L_MEM2MEM_DRIVERS=y # CONFIG_VIDEO_MEM2MEM_DEINTERLACE is not set CONFIG_VIDEO_SUN8I_DEINTERLACE=m CONFIG_VIDEO_SUN8I_ROTATE=m +CONFIG_VIDEO_SUN50I_DEINTERLACE=m CONFIG_DVB_PLATFORM_DRIVERS=y # diff --git a/projects/Allwinner/patches/ffmpeg/0001-WIP-deint-filter.patch b/projects/Allwinner/patches/ffmpeg/0001-WIP-deint-filter.patch new file mode 100644 index 0000000000..34ccc5b6e0 --- /dev/null +++ b/projects/Allwinner/patches/ffmpeg/0001-WIP-deint-filter.patch @@ -0,0 +1,924 @@ +From 39069d9cc03a42cd497dd6b9756116ff4b684a5d Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Tue, 3 Dec 2019 21:01:18 +0100 +Subject: [PATCH] WIP deint filter + +--- + libavfilter/Makefile | 1 + + libavfilter/allfilters.c | 1 + + libavfilter/vf_deinterlace_v4l2m2m.c | 879 +++++++++++++++++++++++++++ + 3 files changed, 881 insertions(+) + create mode 100644 libavfilter/vf_deinterlace_v4l2m2m.c + +diff --git a/libavfilter/Makefile b/libavfilter/Makefile +index 512354065305..625fd29f9313 100644 +--- a/libavfilter/Makefile ++++ b/libavfilter/Makefile +@@ -218,6 +218,7 @@ OBJS-$(CONFIG_DEFLATE_FILTER) += vf_neighbor.o + OBJS-$(CONFIG_DEFLICKER_FILTER) += vf_deflicker.o + OBJS-$(CONFIG_DEINTERLACE_QSV_FILTER) += vf_deinterlace_qsv.o + OBJS-$(CONFIG_DEINTERLACE_VAAPI_FILTER) += vf_deinterlace_vaapi.o vaapi_vpp.o ++OBJS-$(CONFIG_DEINTERLACE_V4L2M2M_FILTER) += vf_deinterlace_v4l2m2m.o + OBJS-$(CONFIG_DEJUDDER_FILTER) += vf_dejudder.o + OBJS-$(CONFIG_DELOGO_FILTER) += vf_delogo.o + OBJS-$(CONFIG_DENOISE_VAAPI_FILTER) += vf_misc_vaapi.o vaapi_vpp.o +diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c +index 1183e4026751..fe5a2e8c02e8 100644 +--- a/libavfilter/allfilters.c ++++ b/libavfilter/allfilters.c +@@ -204,6 +204,7 @@ extern AVFilter ff_vf_dedot; + extern AVFilter ff_vf_deflate; + extern AVFilter ff_vf_deflicker; + extern AVFilter ff_vf_deinterlace_qsv; ++extern AVFilter ff_vf_deinterlace_v4l2m2m; + extern AVFilter ff_vf_deinterlace_vaapi; + extern AVFilter ff_vf_dejudder; + extern AVFilter ff_vf_delogo; +diff --git a/libavfilter/vf_deinterlace_v4l2m2m.c b/libavfilter/vf_deinterlace_v4l2m2m.c +new file mode 100644 +index 000000000000..1029e5b620fd +--- /dev/null ++++ b/libavfilter/vf_deinterlace_v4l2m2m.c +@@ -0,0 +1,879 @@ ++/* ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg 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 ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++/** ++ * @file ++ * deinterlace video filter - V4L2 M2M ++ */ ++ ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "libavutil/avassert.h" ++#include "libavutil/avstring.h" ++#include "libavutil/common.h" ++#include "libavutil/hwcontext.h" ++#include "libavutil/hwcontext_drm.h" ++#include "libavutil/internal.h" ++#include "libavutil/mathematics.h" ++#include "libavutil/opt.h" ++#include "libavutil/pixdesc.h" ++#include "libavutil/time.h" ++ ++#include "avfilter.h" ++#include "formats.h" ++#include "internal.h" ++#include "video.h" ++ ++typedef struct V4L2Queue V4L2Queue; ++typedef struct DeintV4L2M2MContextShared DeintV4L2M2MContextShared; ++ ++typedef struct V4L2PlaneInfo { ++ int bytesperline; ++ size_t length; ++} V4L2PlaneInfo; ++ ++typedef struct V4L2Buffer { ++ int enqueued; ++ int reenqueue; ++ int fd; ++ struct v4l2_buffer buffer; ++ struct v4l2_plane planes[VIDEO_MAX_PLANES]; ++ int num_planes; ++ V4L2PlaneInfo plane_info[VIDEO_MAX_PLANES]; ++ AVDRMFrameDescriptor drm_frame; ++ V4L2Queue *q; ++} V4L2Buffer; ++ ++typedef struct V4L2Queue { ++ struct v4l2_format format; ++ int num_buffers; ++ V4L2Buffer *buffers; ++ DeintV4L2M2MContextShared *ctx; ++} V4L2Queue; ++ ++typedef struct DeintV4L2M2MContextShared { ++ int fd; ++ int done; ++ int width; ++ int height; ++ int orig_width; ++ int orig_height; ++ atomic_uint refcount; ++ ++ AVBufferRef *hw_frames_ctx; ++ ++ int frame_count; ++ AVFrame *frames[2]; ++ ++ V4L2Queue output; ++ V4L2Queue capture; ++} DeintV4L2M2MContextShared; ++ ++typedef struct DeintV4L2M2MContext { ++ const AVClass *class; ++ ++ DeintV4L2M2MContextShared *shared; ++} DeintV4L2M2MContext; ++ ++static int deint_v4l2m2m_prepare_context(DeintV4L2M2MContextShared *ctx) ++{ ++ struct v4l2_capability cap; ++ int ret; ++ ++ memset(&cap, 0, sizeof(cap)); ++ ret = ioctl(ctx->fd, VIDIOC_QUERYCAP, &cap); ++ if (ret < 0) ++ return ret; ++ ++ if (!(cap.capabilities & V4L2_CAP_STREAMING)) ++ return AVERROR(EINVAL); ++ ++ if (cap.capabilities & V4L2_CAP_VIDEO_M2M) { ++ ctx->capture.format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ++ ctx->output.format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ++ ++ return 0; ++ } ++ ++ if (cap.capabilities & V4L2_CAP_VIDEO_M2M_MPLANE) { ++ ctx->capture.format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ ctx->output.format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; ++ ++ return 0; ++ } ++ ++ return AVERROR(EINVAL); ++} ++ ++static int deint_v4l2m2m_try_format(V4L2Queue *queue) ++{ ++ struct v4l2_format *fmt = &queue->format; ++ DeintV4L2M2MContextShared *ctx = queue->ctx; ++ int ret, field; ++ ++ ret = ioctl(ctx->fd, VIDIOC_G_FMT, fmt); ++ if (ret) ++ av_log(NULL, AV_LOG_ERROR, "VIDIOC_G_FMT failed: %d\n", ret); ++ ++ if (V4L2_TYPE_IS_OUTPUT(fmt->type)) ++ field = V4L2_FIELD_INTERLACED_TB; ++ else ++ field = V4L2_FIELD_NONE; ++ ++ if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { ++ fmt->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; ++ fmt->fmt.pix_mp.field = field; ++ fmt->fmt.pix_mp.width = ctx->width; ++ fmt->fmt.pix_mp.height = ctx->height; ++ } else { ++ fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; ++ fmt->fmt.pix.field = field; ++ fmt->fmt.pix.width = ctx->width; ++ fmt->fmt.pix.height = ctx->height; ++ } ++ ++ ret = ioctl(ctx->fd, VIDIOC_TRY_FMT, fmt); ++ if (ret) ++ return AVERROR(EINVAL); ++ ++ if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { ++ if (fmt->fmt.pix_mp.pixelformat != V4L2_PIX_FMT_NV12 || ++ fmt->fmt.pix_mp.field != field) { ++ av_log(NULL, AV_LOG_DEBUG, "format not supported for type %d\n", fmt->type); ++ ++ return AVERROR(EINVAL); ++ } ++ } else { ++ if (fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_NV12 || ++ fmt->fmt.pix.field != field) { ++ av_log(NULL, AV_LOG_DEBUG, "format not supported for type %d\n", fmt->type); ++ ++ return AVERROR(EINVAL); ++ } ++ } ++ ++ return 0; ++} ++ ++static int deint_v4l2m2m_set_format(V4L2Queue *queue, uint32_t field, int width, int height) ++{ ++ struct v4l2_format *fmt = &queue->format; ++ DeintV4L2M2MContextShared *ctx = queue->ctx; ++ int ret; ++ ++ if (V4L2_TYPE_IS_MULTIPLANAR(fmt->type)) { ++ fmt->fmt.pix_mp.field = field; ++ fmt->fmt.pix_mp.width = width; ++ fmt->fmt.pix_mp.height = height; ++ /* TODO: bytesperline and imagesize */ ++ } else { ++ fmt->fmt.pix.field = field; ++ fmt->fmt.pix.width = width; ++ fmt->fmt.pix.height = height; ++ fmt->fmt.pix.sizeimage = 0; ++ fmt->fmt.pix.bytesperline = 0; ++ } ++ ++ ret = ioctl(ctx->fd, VIDIOC_S_FMT, fmt); ++ if (ret) ++ av_log(NULL, AV_LOG_ERROR, "VIDIOC_S_FMT failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int deint_v4l2m2m_probe_device(DeintV4L2M2MContextShared *ctx, char *node) ++{ ++ int ret; ++ ++ ctx->fd = open(node, O_RDWR | O_NONBLOCK, 0); ++ if (ctx->fd < 0) ++ return AVERROR(errno); ++ ++ ret = deint_v4l2m2m_prepare_context(ctx); ++ if (ret) ++ goto fail; ++ ++ ret = deint_v4l2m2m_try_format(&ctx->capture); ++ if (ret) ++ goto fail; ++ ++ ret = deint_v4l2m2m_try_format(&ctx->output); ++ if (ret) ++ goto fail; ++ ++ return 0; ++ ++fail: ++ close(ctx->fd); ++ ctx->fd = -1; ++ ++ return ret; ++} ++ ++static int deint_v4l2m2m_find_device(DeintV4L2M2MContextShared *ctx) ++{ ++ int ret = AVERROR(EINVAL); ++ struct dirent *entry; ++ char node[PATH_MAX]; ++ DIR *dirp; ++ ++ dirp = opendir("/dev"); ++ if (!dirp) ++ return AVERROR(errno); ++ ++ for (entry = readdir(dirp); entry; entry = readdir(dirp)) { ++ ++ if (strncmp(entry->d_name, "video", 5)) ++ continue; ++ ++ snprintf(node, sizeof(node), "/dev/%s", entry->d_name); ++ av_log(NULL, AV_LOG_DEBUG, "probing device %s\n", node); ++ ret = deint_v4l2m2m_probe_device(ctx, node); ++ if (!ret) ++ break; ++ } ++ ++ closedir(dirp); ++ ++ if (ret) { ++ av_log(NULL, AV_LOG_ERROR, "Could not find a valid device\n"); ++ ctx->fd = -1; ++ ++ return ret; ++ } ++ ++ av_log(NULL, AV_LOG_INFO, "Using device %s\n", node); ++ ++ return 0; ++} ++ ++static int deint_v4l2m2m_enqueue_buffer(V4L2Buffer *buf) ++{ ++ int ret; ++ ++ ret = ioctl(buf->q->ctx->fd, VIDIOC_QBUF, &buf->buffer); ++ if (ret < 0) ++ return AVERROR(errno); ++ ++ buf->enqueued = 1; ++ ++ return 0; ++} ++ ++static int v4l2_buffer_export_drm(V4L2Buffer* avbuf) ++{ ++ struct v4l2_exportbuffer expbuf; ++ int i, ret; ++ ++ for (i = 0; i < avbuf->num_planes; i++) { ++ memset(&expbuf, 0, sizeof(expbuf)); ++ ++ expbuf.index = avbuf->buffer.index; ++ expbuf.type = avbuf->buffer.type; ++ expbuf.plane = i; ++ ++ ret = ioctl(avbuf->q->ctx->fd, VIDIOC_EXPBUF, &expbuf); ++ if (ret < 0) ++ return AVERROR(errno); ++ ++ avbuf->fd = expbuf.fd; ++ ++ if (V4L2_TYPE_IS_MULTIPLANAR(avbuf->buffer.type)) { ++ /* drm frame */ ++ avbuf->drm_frame.objects[i].size = avbuf->buffer.m.planes[i].length; ++ avbuf->drm_frame.objects[i].fd = expbuf.fd; ++ avbuf->drm_frame.objects[i].format_modifier = DRM_FORMAT_MOD_LINEAR; ++ } else { ++ /* drm frame */ ++ avbuf->drm_frame.objects[0].size = avbuf->buffer.length; ++ avbuf->drm_frame.objects[0].fd = expbuf.fd; ++ avbuf->drm_frame.objects[0].format_modifier = DRM_FORMAT_MOD_LINEAR; ++ } ++ } ++ ++ return 0; ++} ++ ++static int deint_v4l2m2m_allocate_buffers(V4L2Queue *queue) ++{ ++ struct v4l2_format *fmt = &queue->format; ++ DeintV4L2M2MContextShared *ctx = queue->ctx; ++ struct v4l2_requestbuffers req; ++ int ret, i, j, multiplanar; ++ uint32_t memory; ++ ++ memory = V4L2_TYPE_IS_OUTPUT(fmt->type) ? ++ V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP; ++ ++ multiplanar = V4L2_TYPE_IS_MULTIPLANAR(fmt->type); ++ ++ memset(&req, 0, sizeof(req)); ++ req.count = queue->num_buffers; ++ req.memory = memory; ++ req.type = fmt->type; ++ ++ ret = ioctl(ctx->fd, VIDIOC_REQBUFS, &req); ++ if (ret < 0) { ++ av_log(NULL, AV_LOG_ERROR, "VIDIOC_REQBUFS failed: %s\n", strerror(errno)); ++ ++ return AVERROR(errno); ++ } ++ ++ queue->num_buffers = req.count; ++ queue->buffers = av_mallocz(queue->num_buffers * sizeof(V4L2Buffer)); ++ if (!queue->buffers) { ++ av_log(NULL, AV_LOG_ERROR, "malloc enomem\n"); ++ ++ return AVERROR(ENOMEM); ++ } ++ ++ for (i = 0; i < queue->num_buffers; i++) { ++ V4L2Buffer *buf = &queue->buffers[i]; ++ ++ buf->enqueued = 0; ++ buf->fd = -1; ++ buf->q = queue; ++ ++ buf->buffer.type = fmt->type; ++ buf->buffer.memory = memory; ++ buf->buffer.index = i; ++ ++ if (multiplanar) { ++ buf->buffer.length = VIDEO_MAX_PLANES; ++ buf->buffer.m.planes = buf->planes; ++ } ++ ++ ret = ioctl(ctx->fd, VIDIOC_QUERYBUF, &buf->buffer); ++ if (ret < 0) { ++ ret = AVERROR(errno); ++ ++ goto fail; ++ } ++ ++ if (multiplanar) ++ buf->num_planes = buf->buffer.length; ++ else ++ buf->num_planes = 1; ++ ++ for (j = 0; j < buf->num_planes; j++) { ++ V4L2PlaneInfo *info = &buf->plane_info[j]; ++ ++ if (multiplanar) { ++ info->bytesperline = fmt->fmt.pix_mp.plane_fmt[j].bytesperline; ++ info->length = buf->buffer.m.planes[j].length; ++ } else { ++ info->bytesperline = fmt->fmt.pix.bytesperline; ++ info->length = buf->buffer.length; ++ } ++ } ++ ++ if (!V4L2_TYPE_IS_OUTPUT(fmt->type)) { ++ ret = deint_v4l2m2m_enqueue_buffer(buf); ++ if (ret) ++ goto fail; ++ ++ ret = v4l2_buffer_export_drm(buf); ++ if (ret) ++ goto fail; ++ } ++ } ++ ++ return 0; ++ ++fail: ++ for (i = 0; i < queue->num_buffers; i++) ++ if (queue->buffers[i].fd >= 0) ++ close(queue->buffers[i].fd); ++ av_free(queue->buffers); ++ queue->buffers = NULL; ++ ++ return ret; ++} ++ ++static int deint_v4l2m2m_streamon(V4L2Queue *queue) ++{ ++ int type = queue->format.type; ++ int ret; ++ ++ ret = ioctl(queue->ctx->fd, VIDIOC_STREAMON, &type); ++ if (ret < 0) ++ return AVERROR(errno); ++ ++ return 0; ++} ++ ++static int deint_v4l2m2m_streamoff(V4L2Queue *queue) ++{ ++ int type = queue->format.type; ++ int ret; ++ ++ ret = ioctl(queue->ctx->fd, VIDIOC_STREAMOFF, &type); ++ if (ret < 0) ++ return AVERROR(errno); ++ ++ return 0; ++} ++ ++static V4L2Buffer* deint_v4l2m2m_dequeue_buffer(V4L2Queue *queue, int timeout) ++{ ++ struct v4l2_plane planes[VIDEO_MAX_PLANES]; ++ DeintV4L2M2MContextShared *ctx = queue->ctx; ++ struct v4l2_buffer buf = { 0 }; ++ V4L2Buffer* avbuf = NULL; ++ struct pollfd pfd; ++ short events; ++ int ret; ++ ++ if (V4L2_TYPE_IS_OUTPUT(queue->format.type)) ++ events = POLLOUT | POLLWRNORM; ++ else ++ events = POLLIN | POLLRDNORM; ++ ++ pfd.events = events; ++ pfd.fd = ctx->fd; ++ ++ for (;;) { ++ ret = poll(&pfd, 1, timeout); ++ if (ret > 0) ++ break; ++ if (errno == EINTR) ++ continue; ++ return NULL; ++ } ++ ++ if (pfd.revents & POLLERR) ++ return NULL; ++ ++ if (pfd.revents & events) { ++ memset(&buf, 0, sizeof(buf)); ++ buf.memory = V4L2_MEMORY_MMAP; ++ buf.type = queue->format.type; ++ if (V4L2_TYPE_IS_MULTIPLANAR(queue->format.type)) { ++ memset(planes, 0, sizeof(planes)); ++ buf.length = VIDEO_MAX_PLANES; ++ buf.m.planes = planes; ++ } ++ ++ ret = ioctl(ctx->fd, VIDIOC_DQBUF, &buf); ++ if (ret) { ++ if (errno != EAGAIN) ++ av_log(NULL, AV_LOG_DEBUG, "VIDIOC_DQBUF, errno (%s)\n", ++ av_err2str(AVERROR(errno))); ++ return NULL; ++ } ++ ++ avbuf = &queue->buffers[buf.index]; ++ avbuf->enqueued = 0; ++ avbuf->buffer = buf; ++ if (V4L2_TYPE_IS_MULTIPLANAR(queue->format.type)) { ++ memcpy(avbuf->planes, planes, sizeof(planes)); ++ avbuf->buffer.m.planes = avbuf->planes; ++ } ++ ++ return avbuf; ++ } ++ ++ return NULL; ++} ++ ++static V4L2Buffer *deint_v4l2m2m_find_free_buf(V4L2Queue *queue) ++{ ++ int i; ++ ++ for (i = 0; i < queue->num_buffers; i++) ++ if (!queue->buffers[i].enqueued) ++ return &queue->buffers[i]; ++ ++ return NULL; ++} ++ ++static int deint_v4l2m2m_enqueue(V4L2Queue *queue, const AVFrame* frame) ++{ ++ AVDRMFrameDescriptor *drm_desc = (AVDRMFrameDescriptor *)frame->data[0]; ++ V4L2Buffer *buf; ++ int i; ++ ++ if (V4L2_TYPE_IS_OUTPUT(queue->format.type)) ++ while (deint_v4l2m2m_dequeue_buffer(queue, 0)); ++ ++ buf = deint_v4l2m2m_find_free_buf(queue); ++ if (!buf) ++ return AVERROR(ENOMEM); ++ ++ if (V4L2_TYPE_IS_MULTIPLANAR(buf->buffer.type)) ++ for (i = 0; i < drm_desc->nb_objects; i++) ++ buf->buffer.m.planes[i].m.fd = drm_desc->objects[i].fd; ++ else ++ buf->buffer.m.fd = drm_desc->objects[0].fd; ++ ++ return deint_v4l2m2m_enqueue_buffer(buf); ++} ++ ++static void deint_v4l2m2m_destroy_context(DeintV4L2M2MContextShared *ctx) ++{ ++ if (atomic_fetch_sub(&ctx->refcount, 1) == 1) { ++ V4L2Queue *capture = &ctx->capture; ++ V4L2Queue *output = &ctx->output; ++ int i; ++ ++ av_log(NULL, AV_LOG_DEBUG, "%s - destroying context\n", __func__); ++ ++ if (ctx->fd >= 0) { ++ deint_v4l2m2m_streamoff(capture); ++ deint_v4l2m2m_streamoff(output); ++ } ++ ++ if (capture->buffers) ++ for (i = 0; i < capture->num_buffers; i++) { ++ capture->buffers[i].q = NULL; ++ if (capture->buffers[i].fd >= 0) ++ close(capture->buffers[i].fd); ++ } ++ ++ for (i = 0; i < ctx->frame_count; i++) ++ av_frame_free(&ctx->frames[i]); ++ ++ av_buffer_unref(&ctx->hw_frames_ctx); ++ ++ if (capture->buffers) ++ av_free(capture->buffers); ++ ++ if (output->buffers) ++ av_free(output->buffers); ++ ++ if (ctx->fd >= 0) { ++ close(ctx->fd); ++ ctx->fd = -1; ++ } ++ ++ av_free(ctx); ++ } ++} ++ ++static void v4l2_free_buffer(void *opaque, uint8_t *unused) ++{ ++ V4L2Buffer *buf = opaque; ++ DeintV4L2M2MContextShared *ctx = buf->q->ctx; ++ ++ if (!ctx->done) ++ deint_v4l2m2m_enqueue_buffer(buf); ++ ++ deint_v4l2m2m_destroy_context(ctx); ++} ++ ++static uint8_t *v4l2_get_drm_frame(V4L2Buffer *avbuf, int height) ++{ ++ AVDRMFrameDescriptor *drm_desc = &avbuf->drm_frame; ++ AVDRMLayerDescriptor *layer; ++ ++ /* fill the DRM frame descriptor */ ++ drm_desc->nb_objects = avbuf->num_planes; ++ drm_desc->nb_layers = 1; ++ ++ layer = &drm_desc->layers[0]; ++ layer->nb_planes = avbuf->num_planes; ++ ++ for (int i = 0; i < avbuf->num_planes; i++) { ++ layer->planes[i].object_index = i; ++ layer->planes[i].offset = 0; ++ layer->planes[i].pitch = avbuf->plane_info[i].bytesperline; ++ } ++ ++ layer->format = DRM_FORMAT_NV12; ++ ++ if (avbuf->num_planes == 1) { ++ layer->nb_planes = 2; ++ ++ layer->planes[1].object_index = 0; ++ layer->planes[1].offset = avbuf->plane_info[0].bytesperline * height; ++ layer->planes[1].pitch = avbuf->plane_info[0].bytesperline; ++ } ++ ++ return (uint8_t *)drm_desc; ++} ++ ++static int deint_v4l2m2m_dequeue_frame(V4L2Queue *queue, AVFrame* frame, int timeout) ++{ ++ DeintV4L2M2MContextShared *ctx = queue->ctx; ++ V4L2Buffer* avbuf; ++ ++ avbuf = deint_v4l2m2m_dequeue_buffer(queue, timeout); ++ if (!avbuf) { ++ av_log(NULL, AV_LOG_ERROR, "dequeueing failed\n"); ++ return AVERROR(EINVAL); ++ } ++ ++ frame->buf[0] = av_buffer_create((uint8_t *) &avbuf->drm_frame, ++ sizeof(avbuf->drm_frame), v4l2_free_buffer, ++ avbuf, AV_BUFFER_FLAG_READONLY); ++ if (!frame->buf[0]) ++ return AVERROR(ENOMEM); ++ ++ atomic_fetch_add(&ctx->refcount, 1); ++ ++ frame->data[0] = (uint8_t *)v4l2_get_drm_frame(avbuf, ctx->orig_height); ++ frame->format = AV_PIX_FMT_DRM_PRIME; ++ frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx); ++ frame->height = ctx->height; ++ frame->width = ctx->width; ++ ++ if (avbuf->buffer.flags & V4L2_BUF_FLAG_ERROR) { ++ av_log(NULL, AV_LOG_ERROR, "driver decode error\n"); ++ frame->decode_error_flags |= FF_DECODE_ERROR_INVALID_BITSTREAM; ++ } ++ ++ return 0; ++} ++ ++static int deint_v4l2m2m_dequeue(AVFilterContext *avctx, AVFrame *input_frame, int field) ++{ ++ DeintV4L2M2MContext *priv = avctx->priv; ++ DeintV4L2M2MContextShared *ctx = priv->shared; ++ AVFilterLink *outlink = avctx->outputs[0]; ++ AVFrame *output_frame; ++ int err; ++ ++ output_frame = av_frame_alloc(); ++ ++ if (!output_frame) ++ return AVERROR(ENOMEM); ++ ++ err = deint_v4l2m2m_dequeue_frame(&ctx->capture, output_frame, 500); ++ if (err < 0) { ++ av_log(priv, AV_LOG_ERROR, "no frame (field %d)\n", field); ++ goto fail; ++ } ++ ++ err = av_frame_copy_props(output_frame, input_frame); ++ if (err < 0) ++ goto fail; ++ ++ output_frame->interlaced_frame = 0; ++ ++ if (field == 0) { ++ output_frame->pts *= 2; ++ } else { ++ int64_t cur_pts = ctx->frames[0]->pts; ++ int64_t next_pts = ctx->frames[1]->pts; ++ ++ if (next_pts != AV_NOPTS_VALUE && cur_pts != AV_NOPTS_VALUE) { ++ output_frame->pts = next_pts + cur_pts; ++ } else { ++ output_frame->pts = AV_NOPTS_VALUE; ++ } ++ } ++ av_log(priv, AV_LOG_DEBUG, "pts: %"PRId64" (field %d)\n", output_frame->pts, field); ++ ++ return ff_filter_frame(outlink, output_frame); ++ ++fail: ++ av_frame_free(&output_frame); ++ return err; ++} ++ ++static int deint_v4l2m2m_config_props(AVFilterLink *outlink) ++{ ++ AVFilterLink *inlink = outlink->src->inputs[0]; ++ AVFilterContext *avctx = outlink->src; ++ DeintV4L2M2MContext *priv = avctx->priv; ++ DeintV4L2M2MContextShared *ctx = priv->shared; ++ int ret; ++ ++ ctx->height = avctx->inputs[0]->h; ++ ctx->width = avctx->inputs[0]->w; ++ ++ outlink->frame_rate = av_mul_q(inlink->frame_rate, ++ (AVRational){ 2, 1 }); ++ outlink->time_base = av_mul_q(inlink->time_base, ++ (AVRational){ 1, 2 }); ++ ++ ret = deint_v4l2m2m_find_device(ctx); ++ if (ret) ++ return ret; ++ ++ if (!inlink->hw_frames_ctx) { ++ av_log(priv, AV_LOG_ERROR, "No hw context provided on input\n"); ++ return AVERROR(EINVAL); ++ } ++ ++ ctx->hw_frames_ctx = av_buffer_ref(inlink->hw_frames_ctx); ++ if (!ctx->hw_frames_ctx) ++ return AVERROR(ENOMEM); ++ ++ return 0; ++} ++ ++static int deint_v4l2m2m_query_formats(AVFilterContext *avctx) ++{ ++ static const enum AVPixelFormat pixel_formats[] = { ++ AV_PIX_FMT_DRM_PRIME, ++ AV_PIX_FMT_NONE, ++ }; ++ ++ return ff_set_common_formats(avctx, ff_make_format_list(pixel_formats)); ++} ++ ++static int deint_v4l2m2m_filter_frame(AVFilterLink *link, AVFrame *in) ++{ ++ AVFilterContext *avctx = link->dst; ++ DeintV4L2M2MContext *priv = avctx->priv; ++ DeintV4L2M2MContextShared *ctx = priv->shared; ++ V4L2Queue *capture = &ctx->capture; ++ V4L2Queue *output = &ctx->output; ++ int ret; ++ ++ av_log(priv, AV_LOG_DEBUG, "input pts: %"PRId64"\n", in->pts); ++ if (!ctx->frame_count) { ++ AVDRMFrameDescriptor *drm_desc = (AVDRMFrameDescriptor *)in->data[0]; ++ unsigned int field; ++ ++ ctx->orig_width = drm_desc->layers[0].planes[0].pitch; ++ ctx->orig_height = drm_desc->layers[0].planes[1].offset / ctx->orig_width; ++ ++ if (in->top_field_first) ++ field = V4L2_FIELD_INTERLACED_TB; ++ else ++ field = V4L2_FIELD_INTERLACED_BT; ++ ++ ret = deint_v4l2m2m_set_format(output, field, ctx->orig_width, ctx->orig_height); ++ if (ret) ++ return ret; ++ ++ ret = deint_v4l2m2m_set_format(capture, V4L2_FIELD_NONE, ctx->orig_width, ctx->orig_height); ++ if (ret) ++ return ret; ++ ++ ret = deint_v4l2m2m_allocate_buffers(capture); ++ if (ret) ++ return ret; ++ ++ ret = deint_v4l2m2m_streamon(capture); ++ if (ret) ++ return ret; ++ ++ ret = deint_v4l2m2m_allocate_buffers(output); ++ if (ret) ++ return ret; ++ ++ ret = deint_v4l2m2m_streamon(output); ++ if (ret) ++ return ret; ++ } ++ ++ if (ctx->frame_count < 2) { ++ ctx->frames[ctx->frame_count++] = in; ++ } else { ++ av_frame_free(&ctx->frames[0]); ++ ctx->frames[0] = ctx->frames[1]; ++ ctx->frames[1] = in; ++ } ++ ++ ret = deint_v4l2m2m_enqueue(output, in); ++ if (ret) ++ return ret; ++ ++ if (ctx->frame_count == 2) { ++ ret = deint_v4l2m2m_dequeue(avctx, ctx->frames[0], 0); ++ if (ret) ++ return ret; ++ ++ ret = deint_v4l2m2m_dequeue(avctx, ctx->frames[0], 1); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static av_cold int deint_v4l2m2m_init(AVFilterContext *avctx) ++{ ++ DeintV4L2M2MContext *priv = avctx->priv; ++ DeintV4L2M2MContextShared *ctx; ++ ++ ctx = av_mallocz(sizeof(DeintV4L2M2MContextShared)); ++ if (!ctx) ++ return AVERROR(ENOMEM); ++ ++ priv->shared = ctx; ++ ctx->fd = -1; ++ ctx->output.ctx = ctx; ++ ctx->output.num_buffers = 6; ++ ctx->capture.ctx = ctx; ++ ctx->capture.num_buffers = 6; ++ ctx->done = 0; ++ atomic_init(&ctx->refcount, 1); ++ ++ return 0; ++} ++ ++static void deint_v4l2m2m_uninit(AVFilterContext *avctx) ++{ ++ DeintV4L2M2MContext *priv = avctx->priv; ++ DeintV4L2M2MContextShared *ctx = priv->shared; ++ ++ ctx->done = 1; ++ deint_v4l2m2m_destroy_context(ctx); ++} ++ ++static const AVOption deinterlace_v4l2m2m_options[] = { ++ { NULL }, ++}; ++ ++AVFILTER_DEFINE_CLASS(deinterlace_v4l2m2m); ++ ++static const AVFilterPad deint_v4l2m2m_inputs[] = { ++ { ++ .name = "default", ++ .type = AVMEDIA_TYPE_VIDEO, ++ .filter_frame = deint_v4l2m2m_filter_frame, ++ }, ++ { NULL } ++}; ++ ++static const AVFilterPad deint_v4l2m2m_outputs[] = { ++ { ++ .name = "default", ++ .type = AVMEDIA_TYPE_VIDEO, ++ .config_props = deint_v4l2m2m_config_props, ++ }, ++ { NULL } ++}; ++ ++AVFilter ff_vf_deinterlace_v4l2m2m = { ++ .name = "deinterlace_v4l2m2m", ++ .description = NULL_IF_CONFIG_SMALL("V4L2 M2M deinterlacer"), ++ .priv_size = sizeof(DeintV4L2M2MContext), ++ .init = &deint_v4l2m2m_init, ++ .uninit = &deint_v4l2m2m_uninit, ++ .query_formats = &deint_v4l2m2m_query_formats, ++ .inputs = deint_v4l2m2m_inputs, ++ .outputs = deint_v4l2m2m_outputs, ++ .priv_class = &deinterlace_v4l2m2m_class, ++}; +-- +2.29.2 + diff --git a/projects/Allwinner/patches/ffmpeg/0002-libavfilter-v4l2deinterlace-dequeue-both-destination.patch b/projects/Allwinner/patches/ffmpeg/0002-libavfilter-v4l2deinterlace-dequeue-both-destination.patch new file mode 100644 index 0000000000..cefbd9c64d --- /dev/null +++ b/projects/Allwinner/patches/ffmpeg/0002-libavfilter-v4l2deinterlace-dequeue-both-destination.patch @@ -0,0 +1,230 @@ +From 6bea46839ba23bffaa093bb9ed805d571aaa66ea Mon Sep 17 00:00:00 2001 +From: Alex Bee +Date: Wed, 30 Sep 2020 21:11:34 +0200 +Subject: [PATCH] libavfilter: v4l2deinterlace: dequeue both destination + buffers on time + +Signed-off-by: Alex Bee +--- + libavfilter/vf_deinterlace_v4l2m2m.c | 140 +++++++++++++++++---------- + 1 file changed, 88 insertions(+), 52 deletions(-) + +diff --git a/libavfilter/vf_deinterlace_v4l2m2m.c b/libavfilter/vf_deinterlace_v4l2m2m.c +index 1029e5b620fd..72d28333ffa7 100644 +--- a/libavfilter/vf_deinterlace_v4l2m2m.c ++++ b/libavfilter/vf_deinterlace_v4l2m2m.c +@@ -89,8 +89,14 @@ typedef struct DeintV4L2M2MContextShared { + + AVBufferRef *hw_frames_ctx; + +- int frame_count; +- AVFrame *frames[2]; ++ /* ++ * TODO: check if its really neccessary to hold this ++ * ref, it's only used for freeing av_frame on decoding ++ * end/abort ++ */ ++ AVFrame *cur_in_frame; ++ AVFrame *prev_in_frame; ++ unsigned int field_order; + + V4L2Queue output; + V4L2Queue capture; +@@ -557,8 +563,11 @@ static void deint_v4l2m2m_destroy_context(DeintV4L2M2MContextShared *ctx) + close(capture->buffers[i].fd); + } + +- for (i = 0; i < ctx->frame_count; i++) +- av_frame_free(&ctx->frames[i]); ++ if (ctx->cur_in_frame) ++ av_frame_free(&ctx->cur_in_frame); ++ ++ if (ctx->prev_in_frame) ++ av_frame_free(&ctx->prev_in_frame); + + av_buffer_unref(&ctx->hw_frames_ctx); + +@@ -652,49 +661,79 @@ static int deint_v4l2m2m_dequeue_frame(V4L2Queue *queue, AVFrame* frame, int tim + return 0; + } + +-static int deint_v4l2m2m_dequeue(AVFilterContext *avctx, AVFrame *input_frame, int field) ++static int deint_v4l2m2m_dequeue(AVFilterContext *avctx, AVFrame *input_frame) + { + DeintV4L2M2MContext *priv = avctx->priv; + DeintV4L2M2MContextShared *ctx = priv->shared; + AVFilterLink *outlink = avctx->outputs[0]; +- AVFrame *output_frame; ++ AVFrame *output_frame_1, *output_frame_2; ++ int64_t first_pts = AV_NOPTS_VALUE; + int err; + +- output_frame = av_frame_alloc(); ++ av_log(priv, AV_LOG_DEBUG, "input pts: %"PRId64" (field %d)\n", ++ input_frame->pts, ctx->field_order); + +- if (!output_frame) ++ output_frame_1 = av_frame_alloc(); ++ if (!output_frame_1) + return AVERROR(ENOMEM); + +- err = deint_v4l2m2m_dequeue_frame(&ctx->capture, output_frame, 500); ++ err = deint_v4l2m2m_dequeue_frame(&ctx->capture, output_frame_1, 500); + if (err < 0) { +- av_log(priv, AV_LOG_ERROR, "no frame (field %d)\n", field); +- goto fail; ++ av_log(priv, AV_LOG_ERROR, "no 1st frame (field %d)\n", ctx->field_order); ++ goto fail_out1; + } + +- err = av_frame_copy_props(output_frame, input_frame); ++ err = av_frame_copy_props(output_frame_1, input_frame); + if (err < 0) +- goto fail; ++ goto fail_out1; + +- output_frame->interlaced_frame = 0; ++ output_frame_1->interlaced_frame = 0; + +- if (field == 0) { +- output_frame->pts *= 2; +- } else { +- int64_t cur_pts = ctx->frames[0]->pts; +- int64_t next_pts = ctx->frames[1]->pts; ++ output_frame_2 = av_frame_alloc(); ++ if (!output_frame_2) { ++ err = AVERROR(ENOMEM); ++ goto fail_out1; ++ } ++ ++ err = deint_v4l2m2m_dequeue_frame(&ctx->capture, output_frame_2, 500); ++ if (err < 0) { ++ av_log(priv, AV_LOG_ERROR, "no 2nd frame (field %d)\n", ctx->field_order); ++ goto fail_out2; ++ } ++ ++ err = av_frame_copy_props(output_frame_2, input_frame); ++ if (err < 0) ++ goto fail_out2; ++ ++ output_frame_2->interlaced_frame = 0; + +- if (next_pts != AV_NOPTS_VALUE && cur_pts != AV_NOPTS_VALUE) { +- output_frame->pts = next_pts + cur_pts; +- } else { +- output_frame->pts = AV_NOPTS_VALUE; +- } ++ if (ctx->prev_in_frame && ctx->prev_in_frame->pts != AV_NOPTS_VALUE ++ && input_frame->pts != AV_NOPTS_VALUE) { ++ first_pts = (ctx->prev_in_frame->pts + input_frame->pts) / 2; ++ av_log(priv, AV_LOG_DEBUG, "calculated first pts %"PRId64"\n", first_pts); + } +- av_log(priv, AV_LOG_DEBUG, "pts: %"PRId64" (field %d)\n", output_frame->pts, field); + +- return ff_filter_frame(outlink, output_frame); ++ output_frame_1->pts = first_pts; ++ ++ err = ff_filter_frame(outlink, output_frame_1); ++ if (err < 0) { ++ av_frame_free(&output_frame_2); ++ return err; ++ } ++ err = ff_filter_frame(outlink, output_frame_2); ++ ++ if (err < 0) ++ return err; ++ ++ av_log(priv, AV_LOG_DEBUG, "1st frame pts: %"PRId64" 2nd frame pts: %"PRId64" first pts: %"PRId64" (field %d)\n", ++ output_frame_1->pts, output_frame_2->pts, first_pts, ctx->field_order); ++ ++ return 0; + +-fail: +- av_frame_free(&output_frame); ++fail_out2: ++ av_frame_free(&output_frame_2); ++fail_out1: ++ av_frame_free(&output_frame_1); + return err; + } + +@@ -749,20 +788,22 @@ static int deint_v4l2m2m_filter_frame(AVFilterLink *link, AVFrame *in) + V4L2Queue *output = &ctx->output; + int ret; + +- av_log(priv, AV_LOG_DEBUG, "input pts: %"PRId64"\n", in->pts); +- if (!ctx->frame_count) { ++ av_log(priv, AV_LOG_DEBUG, "input pts: %"PRId64" field :%d interlaced: %d\n", ++ in->pts, in->top_field_first, in->interlaced_frame); ++ ++ ctx->cur_in_frame = in; ++ ++ if (ctx->field_order == V4L2_FIELD_ANY) { + AVDRMFrameDescriptor *drm_desc = (AVDRMFrameDescriptor *)in->data[0]; +- unsigned int field; +- + ctx->orig_width = drm_desc->layers[0].planes[0].pitch; + ctx->orig_height = drm_desc->layers[0].planes[1].offset / ctx->orig_width; + +- if (in->top_field_first) +- field = V4L2_FIELD_INTERLACED_TB; ++ if (in->top_field_first) ++ ctx->field_order = V4L2_FIELD_INTERLACED_TB; + else +- field = V4L2_FIELD_INTERLACED_BT; ++ ctx->field_order = V4L2_FIELD_INTERLACED_BT; + +- ret = deint_v4l2m2m_set_format(output, field, ctx->orig_width, ctx->orig_height); ++ ret = deint_v4l2m2m_set_format(output, ctx->field_order, ctx->orig_width, ctx->orig_height); + if (ret) + return ret; + +@@ -787,27 +828,19 @@ static int deint_v4l2m2m_filter_frame(AVFilterLink *link, AVFrame *in) + return ret; + } + +- if (ctx->frame_count < 2) { +- ctx->frames[ctx->frame_count++] = in; +- } else { +- av_frame_free(&ctx->frames[0]); +- ctx->frames[0] = ctx->frames[1]; +- ctx->frames[1] = in; +- } +- + ret = deint_v4l2m2m_enqueue(output, in); + if (ret) + return ret; + +- if (ctx->frame_count == 2) { +- ret = deint_v4l2m2m_dequeue(avctx, ctx->frames[0], 0); +- if (ret) +- return ret; ++ ret = deint_v4l2m2m_dequeue(avctx, in); ++ if (ret) ++ return ret; + +- ret = deint_v4l2m2m_dequeue(avctx, ctx->frames[0], 1); +- if (ret) +- return ret; +- } ++ if (ctx->prev_in_frame) ++ av_frame_free(&ctx->prev_in_frame); ++ ++ ctx->prev_in_frame = in; ++ ctx->cur_in_frame = NULL; + + return 0; + } +@@ -828,6 +861,9 @@ static av_cold int deint_v4l2m2m_init(AVFilterContext *avctx) + ctx->capture.ctx = ctx; + ctx->capture.num_buffers = 6; + ctx->done = 0; ++ ctx->field_order = V4L2_FIELD_ANY; ++ ctx->cur_in_frame = NULL; ++ ctx->prev_in_frame = NULL; + atomic_init(&ctx->refcount, 1); + + return 0; +-- +2.29.2 + diff --git a/projects/Allwinner/patches/kodi/0001-WIP-DVDVideoCodecDRMPRIME-add-support-for-filters.patch b/projects/Allwinner/patches/kodi/0001-WIP-DVDVideoCodecDRMPRIME-add-support-for-filters.patch new file mode 100644 index 0000000000..bd0112f170 --- /dev/null +++ b/projects/Allwinner/patches/kodi/0001-WIP-DVDVideoCodecDRMPRIME-add-support-for-filters.patch @@ -0,0 +1,148 @@ +From 6789405cbea23dd0d53ba5a6833dc2266f166ad9 Mon Sep 17 00:00:00 2001 +From: Jonas Karlman +Date: Sun, 20 Oct 2019 17:10:07 +0000 +Subject: [PATCH 1/2] WIP: DVDVideoCodecDRMPRIME: add support for filters + +--- + .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp | 68 ++++++++++++++++--- + .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.h | 10 +++ + 2 files changed, 69 insertions(+), 9 deletions(-) + +diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp +index a12ef9ec1e96..2b334c95d47a 100644 +--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp ++++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp +@@ -27,6 +27,8 @@ + extern "C" + { + #include ++#include ++#include + #include + #include + #include +@@ -514,18 +516,30 @@ void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture) + pVideoPicture->dts = DVD_NOPTS_VALUE; + } + +-CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideoPicture) ++CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterIn() + { +- if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) +- Drain(); ++ if (!m_pFilterIn) ++ return VC_PICTURE; + +- if (pVideoPicture->videoBuffer) ++ int ret = av_buffersrc_add_frame(m_pFilterIn, m_pFrame); ++ if (ret < 0) + { +- pVideoPicture->videoBuffer->Release(); +- pVideoPicture->videoBuffer = nullptr; ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - buffersrc add frame failed: {} ({})", ++ __FUNCTION__, err, ret); ++ return VC_ERROR; + } + +- int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame); ++ return ProcessFilterOut(); ++} ++ ++CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterOut() ++{ ++ if (!m_pFilterOut) ++ return VC_EOF; ++ ++ int ret = av_buffersink_get_frame(m_pFilterOut, m_pFrame); + if (ret == AVERROR(EAGAIN)) + return VC_BUFFER; + else if (ret == AVERROR_EOF) +@@ -542,11 +556,47 @@ CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideo + { + char err[AV_ERROR_MAX_STRING_SIZE] = {}; + av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE); +- CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})", __FUNCTION__, +- err, ret); ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - buffersink get frame failed: {} ({})", ++ __FUNCTION__, err, ret); + return VC_ERROR; + } + ++ return VC_PICTURE; ++} ++ ++CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideoPicture) ++{ ++ if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) ++ Drain(); ++ ++ if (pVideoPicture->videoBuffer) ++ { ++ pVideoPicture->videoBuffer->Release(); ++ pVideoPicture->videoBuffer = nullptr; ++ } ++ ++ auto result = ProcessFilterOut(); ++ if (result != VC_PICTURE) ++ { ++ int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame); ++ if (ret == AVERROR(EAGAIN)) ++ return VC_BUFFER; ++ else if (ret == AVERROR_EOF) ++ return VC_EOF; ++ else if (ret) ++ { ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})", ++ __FUNCTION__, err, ret); ++ return VC_ERROR; ++ } ++ ++ result = ProcessFilterIn(); ++ if (result != VC_PICTURE) ++ return result; ++ } ++ + SetPictureParams(pVideoPicture); + + if (IsSupportedHwFormat(static_cast(m_pFrame->format))) +diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h +index 77d066c3d9ca..7112d1b48afb 100644 +--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h ++++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h +@@ -14,6 +14,11 @@ + + #include + ++extern "C" ++{ ++#include ++} ++ + class CDVDVideoCodecDRMPRIME : public CDVDVideoCodec + { + public: +@@ -35,6 +40,8 @@ protected: + void Drain(); + void SetPictureParams(VideoPicture* pVideoPicture); + void UpdateProcessInfo(struct AVCodecContext* avctx, const enum AVPixelFormat fmt); ++ CDVDVideoCodec::VCReturn ProcessFilterIn(); ++ CDVDVideoCodec::VCReturn ProcessFilterOut(); + static enum AVPixelFormat GetFormat(struct AVCodecContext* avctx, const enum AVPixelFormat* fmt); + static int GetBuffer(struct AVCodecContext* avctx, AVFrame* frame, int flags); + +@@ -43,5 +50,8 @@ protected: + CDVDStreamInfo m_hints; + AVCodecContext* m_pCodecContext = nullptr; + AVFrame* m_pFrame = nullptr; ++ AVFilterGraph* m_pFilterGraph = nullptr; ++ AVFilterContext* m_pFilterIn = nullptr; ++ AVFilterContext* m_pFilterOut = nullptr; + std::shared_ptr m_videoBufferPool; + }; +-- +2.29.2 + diff --git a/projects/Allwinner/patches/kodi/0002-WIP-DRMPRIME-deinterlace-filter.patch b/projects/Allwinner/patches/kodi/0002-WIP-DRMPRIME-deinterlace-filter.patch new file mode 100644 index 0000000000..d2155fbf94 --- /dev/null +++ b/projects/Allwinner/patches/kodi/0002-WIP-DRMPRIME-deinterlace-filter.patch @@ -0,0 +1,503 @@ +From 58f2acdc63d85eb9818d783a9a858b1ecc267fa7 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Thu, 26 Dec 2019 11:01:51 +0100 +Subject: [PATCH 2/2] WIP: DRMPRIME deinterlace filter + +--- + .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp | 368 +++++++++++++++--- + .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.h | 9 +- + 2 files changed, 322 insertions(+), 55 deletions(-) + +diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp +index 2b334c95d47a..1e5624e7af50 100644 +--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp ++++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp +@@ -79,12 +79,15 @@ CDVDVideoCodecDRMPRIME::CDVDVideoCodecDRMPRIME(CProcessInfo& processInfo) + : CDVDVideoCodec(processInfo) + { + m_pFrame = av_frame_alloc(); ++ m_pFilterFrame = av_frame_alloc(); + m_videoBufferPool = std::make_shared(); + } + + CDVDVideoCodecDRMPRIME::~CDVDVideoCodecDRMPRIME() + { + av_frame_free(&m_pFrame); ++ av_frame_free(&m_pFilterFrame); ++ FilterClose(); + avcodec_free_context(&m_pCodecContext); + } + +@@ -330,8 +333,19 @@ bool CDVDVideoCodecDRMPRIME::Open(CDVDStreamInfo& hints, CDVDCodecOptions& optio + } + + UpdateProcessInfo(m_pCodecContext, m_pCodecContext->pix_fmt); +- m_processInfo.SetVideoDeintMethod("none"); ++ m_processInfo.SetVideoInterlaced(false); + m_processInfo.SetVideoDAR(hints.aspect); ++ m_processInfo.SetVideoDeintMethod("none"); ++ ++ FilterTest(); ++ ++ if (!m_deintFilterName.empty()) ++ { ++ std::list methods; ++ methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE); ++ m_processInfo.UpdateDeinterlacingMethods(methods); ++ m_processInfo.SetDeinterlacingMethodDefault(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE); ++ } + + return true; + } +@@ -394,6 +408,8 @@ void CDVDVideoCodecDRMPRIME::Reset() + return; + + Drain(); ++ m_filters.clear(); ++ FilterClose(); + + do + { +@@ -432,7 +448,7 @@ void CDVDVideoCodecDRMPRIME::Drain() + } + } + +-void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture) ++bool CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture) + { + pVideoPicture->iWidth = m_pFrame->width; + pVideoPicture->iHeight = m_pFrame->height; +@@ -514,13 +530,232 @@ void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture) + ? DVD_NOPTS_VALUE + : static_cast(pts) * DVD_TIME_BASE / AV_TIME_BASE; + pVideoPicture->dts = DVD_NOPTS_VALUE; ++ ++ if (IsSupportedHwFormat(static_cast(m_pFrame->format))) ++ { ++ CVideoBufferDRMPRIMEFFmpeg* buffer = ++ dynamic_cast(m_videoBufferPool->Get()); ++ buffer->SetPictureParams(*pVideoPicture); ++ buffer->SetRef(m_pFrame); ++ pVideoPicture->videoBuffer = buffer; ++ } ++ else if (m_pFrame->opaque) ++ { ++ CVideoBufferDMA* buffer = static_cast(m_pFrame->opaque); ++ buffer->SetPictureParams(*pVideoPicture); ++ buffer->Acquire(); ++ buffer->SyncEnd(); ++ buffer->SetDimensions(m_pFrame->width, m_pFrame->height); ++ ++ pVideoPicture->videoBuffer = buffer; ++ av_frame_unref(m_pFrame); ++ } ++ ++ if (!pVideoPicture->videoBuffer) ++ { ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - videoBuffer:nullptr format:{}", __FUNCTION__, ++ av_get_pix_fmt_name(static_cast(m_pFrame->format))); ++ av_frame_unref(m_pFrame); ++ return false; ++ } ++ ++ return true; ++} ++ ++void CDVDVideoCodecDRMPRIME::FilterTest() ++{ ++ const AVFilter* filter; ++ void* opaque{}; ++ ++ m_deintFilterName.clear(); ++ ++ while ((filter = av_filter_iterate(&opaque)) != nullptr) ++ { ++ std::string name(filter->name); ++ ++ if (name.find("deinterlace") != std::string::npos) ++ { ++ if (FilterOpen(name, true)) ++ { ++ m_deintFilterName = name; ++ ++ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - found deinterlacing filter {}", ++ __FUNCTION__, name); ++ ++ return; ++ } ++ } ++ } ++ ++ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - no deinterlacing filter found", ++ __FUNCTION__); ++} ++ ++bool CDVDVideoCodecDRMPRIME::FilterOpen(const std::string& filters, bool test) ++{ ++ int result; ++ ++ if (m_pFilterGraph) ++ FilterClose(); ++ ++ if (filters.empty()) ++ return true; ++ ++ if (!(m_pFilterGraph = avfilter_graph_alloc())) ++ { ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - unable to alloc filter graph"); ++ return false; ++ } ++ ++ const AVFilter* srcFilter = avfilter_get_by_name("buffer"); ++ const AVFilter* outFilter = avfilter_get_by_name("buffersink"); ++ enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_DRM_PRIME, AV_PIX_FMT_NONE }; ++ ++ std::string args = StringUtils::Format("video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:" ++ "pixel_aspect=%d/%d:sws_param=flags=2", ++ m_pCodecContext->width, ++ m_pCodecContext->height, ++ m_pCodecContext->pix_fmt, ++ m_pCodecContext->time_base.num ? ++ m_pCodecContext->time_base.num : 1, ++ m_pCodecContext->time_base.num ? ++ m_pCodecContext->time_base.den : 1, ++ m_pCodecContext->sample_aspect_ratio.num != 0 ? ++ m_pCodecContext->sample_aspect_ratio.num : 1, ++ m_pCodecContext->sample_aspect_ratio.num != 0 ? ++ m_pCodecContext->sample_aspect_ratio.den : 1); ++ ++ result = avfilter_graph_create_filter(&m_pFilterIn, srcFilter, "src", ++ args.c_str(), NULL, m_pFilterGraph); ++ if (result < 0) ++ { ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, ++ "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_create_filter: src: {} ({})", ++ err, result); ++ return false; ++ } ++ ++ AVBufferSrcParameters *par = av_buffersrc_parameters_alloc(); ++ if (!par) ++ { ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - unable to alloc buffersrc"); ++ return false; ++ } ++ ++ memset(par, 0, sizeof(*par)); ++ par->format = AV_PIX_FMT_NONE; ++ par->hw_frames_ctx = m_pCodecContext->hw_device_ctx; ++ ++ result = av_buffersrc_parameters_set(m_pFilterIn, par); ++ if (result < 0) ++ { ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, ++ "CDVDVideoCodecDRMPRIME::FilterOpen - av_buffersrc_parameters_set: {} ({})", ++ err, result); ++ return false; ++ } ++ av_freep(&par); ++ ++ result = avfilter_graph_create_filter(&m_pFilterOut, outFilter, "out", ++ NULL, NULL, m_pFilterGraph); ++ if (result < 0) ++ { ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, ++ "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_create_filter: out: {} ({})", ++ err, result); ++ return false; ++ } ++ ++ result = av_opt_set_int_list(m_pFilterOut, "pix_fmts", &pix_fmts[0], ++ AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); ++ if (result < 0) ++ { ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - failed settings pix formats"); ++ return false; ++ } ++ ++ AVFilterInOut* outputs = avfilter_inout_alloc(); ++ AVFilterInOut* inputs = avfilter_inout_alloc(); ++ ++ outputs->name = av_strdup("in"); ++ outputs->filter_ctx = m_pFilterIn; ++ outputs->pad_idx = 0; ++ outputs->next = nullptr; ++ ++ inputs->name = av_strdup("out"); ++ inputs->filter_ctx = m_pFilterOut; ++ inputs->pad_idx = 0; ++ inputs->next = nullptr; ++ ++ result = avfilter_graph_parse_ptr(m_pFilterGraph, filters.c_str(), &inputs, &outputs, NULL); ++ avfilter_inout_free(&outputs); ++ avfilter_inout_free(&inputs); ++ ++ if (result < 0) ++ { ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_parse"); ++ return false; ++ } ++ ++ if ((result = avfilter_graph_config(m_pFilterGraph, nullptr)) < 0) ++ { ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_config: {} ({})", ++ err, result); ++ return false; ++ } ++ ++ if (test) ++ { ++ FilterClose(); ++ return true; ++ } ++ ++ if (filters.find("deinterlace") != std::string::npos) ++ { ++ m_processInfo.SetVideoDeintMethod(filters); ++ } ++ else ++ { ++ m_processInfo.SetVideoDeintMethod("none"); ++ } ++ ++ if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO)) ++ { ++ char* graphDump = avfilter_graph_dump(m_pFilterGraph, nullptr); ++ if (graphDump) ++ { ++ CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::FilterOpen - Final filter graph:\n%s", ++ graphDump); ++ av_freep(&graphDump); ++ } ++ } ++ ++ return true; ++} ++ ++void CDVDVideoCodecDRMPRIME::FilterClose() ++{ ++ if (m_pFilterGraph) ++ { ++ CLog::Log(LOGDEBUG, LOGVIDEO, "CDVDVideoCodecDRMPRIME::FilterClose - Freeing filter graph"); ++ avfilter_graph_free(&m_pFilterGraph); ++ ++ // Disposed by above code ++ m_pFilterIn = nullptr; ++ m_pFilterOut = nullptr; ++ } + } + + CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterIn() + { +- if (!m_pFilterIn) +- return VC_PICTURE; +- + int ret = av_buffersrc_add_frame(m_pFilterIn, m_pFrame); + if (ret < 0) + { +@@ -536,21 +771,14 @@ CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterIn() + + CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterOut() + { +- if (!m_pFilterOut) +- return VC_EOF; +- +- int ret = av_buffersink_get_frame(m_pFilterOut, m_pFrame); ++ int ret = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrame); + if (ret == AVERROR(EAGAIN)) + return VC_BUFFER; + else if (ret == AVERROR_EOF) + { +- if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) +- { +- CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - flush buffers", __FUNCTION__); +- avcodec_flush_buffers(m_pCodecContext); +- SetCodecControl(m_codecControlFlags & ~DVD_CODEC_CTRL_DRAIN); +- } +- return VC_EOF; ++ ret = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrame); ++ if (ret < 0) ++ return VC_BUFFER; + } + else if (ret) + { +@@ -561,9 +789,27 @@ CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterOut() + return VC_ERROR; + } + ++ av_frame_unref(m_pFrame); ++ av_frame_move_ref(m_pFrame, m_pFilterFrame); ++ + return VC_PICTURE; + } + ++std::string CDVDVideoCodecDRMPRIME::GetFilterChain(bool interlaced) ++{ ++ // ask codec to do deinterlacing if possible ++ EINTERLACEMETHOD mInt = m_processInfo.GetVideoSettings().m_InterlaceMethod; ++ std::string filterChain; ++ ++ if (!m_processInfo.Supports(mInt)) ++ mInt = m_processInfo.GetFallbackDeintMethod(); ++ ++ if (mInt != VS_INTERLACEMETHOD_NONE && interlaced && !m_deintFilterName.empty()) ++ filterChain += m_deintFilterName; ++ ++ return filterChain; ++} ++ + CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideoPicture) + { + if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) +@@ -575,57 +821,71 @@ CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideo + pVideoPicture->videoBuffer = nullptr; + } + +- auto result = ProcessFilterOut(); +- if (result != VC_PICTURE) ++ if (m_pFilterGraph) + { +- int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame); +- if (ret == AVERROR(EAGAIN)) +- return VC_BUFFER; +- else if (ret == AVERROR_EOF) +- return VC_EOF; +- else if (ret) ++ auto ret = ProcessFilterOut(); ++ if (ret == VC_PICTURE) + { +- char err[AV_ERROR_MAX_STRING_SIZE] = {}; +- av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE); +- CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})", +- __FUNCTION__, err, ret); +- return VC_ERROR; ++ if (!SetPictureParams(pVideoPicture)) ++ return VC_ERROR; ++ return VC_PICTURE; + } ++ else if (ret != VC_BUFFER) ++ { ++ return ret; ++ } ++ } + +- result = ProcessFilterIn(); +- if (result != VC_PICTURE) +- return result; ++ int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame); ++ if (ret == AVERROR(EAGAIN)) ++ return VC_BUFFER; ++ else if (ret == AVERROR_EOF) ++ return VC_EOF; ++ else if (ret) ++ { ++ char err[AV_ERROR_MAX_STRING_SIZE] = {}; ++ av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE); ++ CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})", ++ __FUNCTION__, err, ret); ++ return VC_ERROR; + } + +- SetPictureParams(pVideoPicture); ++ if (!m_processInfo.GetVideoInterlaced() && m_pFrame->interlaced_frame) ++ m_processInfo.SetVideoInterlaced(true); + +- if (IsSupportedHwFormat(static_cast(m_pFrame->format))) ++ std::string filterChain = GetFilterChain(m_pFrame->interlaced_frame); ++ if (!filterChain.empty()) + { +- CVideoBufferDRMPRIMEFFmpeg* buffer = +- dynamic_cast(m_videoBufferPool->Get()); +- buffer->SetPictureParams(*pVideoPicture); +- buffer->SetRef(m_pFrame); +- pVideoPicture->videoBuffer = buffer; ++ bool reopenFilter = false; ++ if (m_filters != filterChain) ++ reopenFilter = true; ++ ++ if (m_pFilterGraph && ++ (m_pFilterIn->outputs[0]->w != m_pCodecContext->width || ++ m_pFilterIn->outputs[0]->h != m_pCodecContext->height)) ++ reopenFilter = true; ++ ++ if (reopenFilter) ++ { ++ m_filters = filterChain; ++ if (!FilterOpen(filterChain, false)) ++ FilterClose(); ++ } ++ ++ if (m_pFilterGraph) ++ { ++ if (ProcessFilterIn() != VC_PICTURE) ++ return VC_NONE; ++ } + } +- else if (m_pFrame->opaque) ++ else + { +- CVideoBufferDMA* buffer = static_cast(m_pFrame->opaque); +- buffer->SetPictureParams(*pVideoPicture); +- buffer->Acquire(); +- buffer->SyncEnd(); +- buffer->SetDimensions(m_pFrame->width, m_pFrame->height); +- +- pVideoPicture->videoBuffer = buffer; +- av_frame_unref(m_pFrame); ++ m_filters.clear(); ++ FilterClose(); + } + +- if (!pVideoPicture->videoBuffer) +- { +- CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - videoBuffer:nullptr format:{}", __FUNCTION__, +- av_get_pix_fmt_name(static_cast(m_pFrame->format))); +- av_frame_unref(m_pFrame); ++ if (!SetPictureParams(pVideoPicture)) + return VC_ERROR; +- } + + return VC_PICTURE; + } +diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h +index 7112d1b48afb..13bec9513579 100644 +--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h ++++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h +@@ -38,18 +38,25 @@ public: + + protected: + void Drain(); +- void SetPictureParams(VideoPicture* pVideoPicture); ++ bool SetPictureParams(VideoPicture* pVideoPicture); + void UpdateProcessInfo(struct AVCodecContext* avctx, const enum AVPixelFormat fmt); + CDVDVideoCodec::VCReturn ProcessFilterIn(); + CDVDVideoCodec::VCReturn ProcessFilterOut(); + static enum AVPixelFormat GetFormat(struct AVCodecContext* avctx, const enum AVPixelFormat* fmt); + static int GetBuffer(struct AVCodecContext* avctx, AVFrame* frame, int flags); ++ bool FilterOpen(const std::string& filters, bool test); ++ void FilterClose(); ++ void FilterTest(); ++ std::string GetFilterChain(bool interlaced); + + std::string m_name; ++ std::string m_deintFilterName; ++ std::string m_filters; + int m_codecControlFlags = 0; + CDVDStreamInfo m_hints; + AVCodecContext* m_pCodecContext = nullptr; + AVFrame* m_pFrame = nullptr; ++ AVFrame* m_pFilterFrame = nullptr; + AVFilterGraph* m_pFilterGraph = nullptr; + AVFilterContext* m_pFilterIn = nullptr; + AVFilterContext* m_pFilterOut = nullptr; +-- +2.29.2 + diff --git a/projects/Allwinner/patches/linux/x0001-wip-H6-deinterlace.patch b/projects/Allwinner/patches/linux/x0001-wip-H6-deinterlace.patch new file mode 100644 index 0000000000..008929104b --- /dev/null +++ b/projects/Allwinner/patches/linux/x0001-wip-H6-deinterlace.patch @@ -0,0 +1,1380 @@ +From 70ad6a66d55ec76f7a0595b7c1bc94bca11dd289 Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Mon, 25 May 2020 19:06:07 +0200 +Subject: [PATCH 1/2] wip: H6 deinterlace + +Signed-off-by: Jernej Skrabec +--- + drivers/media/platform/Kconfig | 13 + + drivers/media/platform/sunxi/Makefile | 1 + + .../media/platform/sunxi/sun50i-di/Makefile | 2 + + .../platform/sunxi/sun50i-di/sun50i-di.c | 1134 +++++++++++++++++ + .../platform/sunxi/sun50i-di/sun50i-di.h | 172 +++ + 5 files changed, 1322 insertions(+) + create mode 100644 drivers/media/platform/sunxi/sun50i-di/Makefile + create mode 100644 drivers/media/platform/sunxi/sun50i-di/sun50i-di.c + create mode 100644 drivers/media/platform/sunxi/sun50i-di/sun50i-di.h + +diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig +index c57ee78fa99d..0d3b25d96b10 100644 +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -495,6 +495,19 @@ config VIDEO_QCOM_VENUS + on various Qualcomm SoCs. + To compile this driver as a module choose m here. + ++config VIDEO_SUN50I_DEINTERLACE ++ tristate "Allwinner Deinterlace v2 driver" ++ depends on VIDEO_DEV && VIDEO_V4L2 ++ depends on ARCH_SUNXI || COMPILE_TEST ++ depends on COMMON_CLK && OF ++ depends on PM ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_MEM2MEM_DEV ++ help ++ Support for the Allwinner deinterlace v2 unit found on ++ some SoCs, like H6. ++ To compile this driver as a module choose m here. ++ + config VIDEO_SUN8I_DEINTERLACE + tristate "Allwinner Deinterlace driver" + depends on VIDEO_DEV && VIDEO_V4L2 +diff --git a/drivers/media/platform/sunxi/Makefile b/drivers/media/platform/sunxi/Makefile +index fc537c9f5ca9..b28f3b15329f 100644 +--- a/drivers/media/platform/sunxi/Makefile ++++ b/drivers/media/platform/sunxi/Makefile +@@ -3,4 +3,5 @@ + obj-y += sun4i-csi/ + obj-y += sun6i-csi/ + obj-y += sun8i-di/ ++obj-y += sun50i-di/ + obj-y += sun8i-rotate/ +diff --git a/drivers/media/platform/sunxi/sun50i-di/Makefile b/drivers/media/platform/sunxi/sun50i-di/Makefile +new file mode 100644 +index 000000000000..225b3b808069 +--- /dev/null ++++ b/drivers/media/platform/sunxi/sun50i-di/Makefile +@@ -0,0 +1,2 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_VIDEO_SUN50I_DEINTERLACE) += sun50i-di.o +diff --git a/drivers/media/platform/sunxi/sun50i-di/sun50i-di.c b/drivers/media/platform/sunxi/sun50i-di/sun50i-di.c +new file mode 100644 +index 000000000000..f6b706fb1a9a +--- /dev/null ++++ b/drivers/media/platform/sunxi/sun50i-di/sun50i-di.c +@@ -0,0 +1,1134 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Allwinner sun50i deinterlacer driver ++ * ++ * Copyright (C) 2020 Jernej Skrabec ++ * ++ * Based on vim2m driver. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "sun50i-di.h" ++ ++#define FLAG_SIZE (DEINTERLACE_MAX_WIDTH * DEINTERLACE_MAX_HEIGHT / 4) ++ ++static u32 deinterlace_formats[] = { ++ V4L2_PIX_FMT_NV12, ++ V4L2_PIX_FMT_NV21, ++ V4L2_PIX_FMT_YUV420, ++ V4L2_PIX_FMT_NV16, ++ V4L2_PIX_FMT_NV61, ++ V4L2_PIX_FMT_YUV422P ++}; ++ ++static inline u32 deinterlace_read(struct deinterlace_dev *dev, u32 reg) ++{ ++ return readl(dev->base + reg); ++} ++ ++static inline void deinterlace_write(struct deinterlace_dev *dev, ++ u32 reg, u32 value) ++{ ++ writel(value, dev->base + reg); ++} ++ ++static inline void deinterlace_set_bits(struct deinterlace_dev *dev, ++ u32 reg, u32 bits) ++{ ++ u32 val = readl(dev->base + reg); ++ ++ val |= bits; ++ ++ writel(val, dev->base + reg); ++} ++ ++static inline void deinterlace_clr_set_bits(struct deinterlace_dev *dev, ++ u32 reg, u32 clr, u32 set) ++{ ++ u32 val = readl(dev->base + reg); ++ ++ val &= ~clr; ++ val |= set; ++ ++ writel(val, dev->base + reg); ++} ++ ++static void deinterlace_device_run(void *priv) ++{ ++ u32 width, height, reg, msk, pitch[3], offset[2], fmt; ++ dma_addr_t buf, prev, curr, next, addr[4][3]; ++ struct deinterlace_ctx *ctx = priv; ++ struct deinterlace_dev *dev = ctx->dev; ++ struct vb2_v4l2_buffer *src, *dst; ++ unsigned int val; ++ bool motion; ++ ++ src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); ++ dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); ++ ++ v4l2_m2m_buf_copy_metadata(src, dst, true); ++ ++ fmt = ctx->src_fmt.pixelformat; ++ ++ deinterlace_write(dev, DEINTERLACE_IN_FLAG_ADDR, ctx->flag1_buf_dma); ++ deinterlace_write(dev, DEINTERLACE_OUT_FLAG_ADDR, ctx->flag2_buf_dma); ++ deinterlace_write(dev, DEINTERLACE_FLAG_ADDRH, 0); ++ deinterlace_write(dev, DEINTERLACE_FLAG_PITCH, 0x200); ++ ++ width = ctx->src_fmt.width; ++ height = ctx->src_fmt.height; ++ ++ reg = DEINTERLACE_SIZE_WIDTH(width); ++ reg |= DEINTERLACE_SIZE_HEIGHT(height); ++ deinterlace_write(dev, DEINTERLACE_SIZE, reg); ++ ++ switch (fmt) { ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ reg = DEINTERLACE_FORMAT_YUV420SP; ++ break; ++ case V4L2_PIX_FMT_YUV420: ++ reg = DEINTERLACE_FORMAT_YUV420P; ++ break; ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ reg = DEINTERLACE_FORMAT_YUV422SP; ++ break; ++ case V4L2_PIX_FMT_YUV422P: ++ reg = DEINTERLACE_FORMAT_YUV422P; ++ break; ++ } ++ deinterlace_write(dev, DEINTERLACE_FORMAT, reg); ++ ++ pitch[0] = ctx->src_fmt.bytesperline; ++ switch (fmt) { ++ case V4L2_PIX_FMT_YUV420: ++ case V4L2_PIX_FMT_YUV422P: ++ pitch[1] = pitch[0] / 2; ++ pitch[2] = pitch[1]; ++ break; ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ pitch[1] = pitch[0]; ++ pitch[2] = 0; ++ break; ++ } ++ ++ deinterlace_write(dev, DEINTERLACE_IN_PITCH0, pitch[0] * 2); ++ deinterlace_write(dev, DEINTERLACE_IN_PITCH1, pitch[1] * 2); ++ deinterlace_write(dev, DEINTERLACE_IN_PITCH2, pitch[2] * 2); ++ deinterlace_write(dev, DEINTERLACE_OUT_PITCH0, pitch[0]); ++ deinterlace_write(dev, DEINTERLACE_OUT_PITCH1, pitch[1]); ++ deinterlace_write(dev, DEINTERLACE_OUT_PITCH2, pitch[2]); ++ ++ offset[0] = pitch[0] * height; ++ switch (fmt) { ++ case V4L2_PIX_FMT_YUV420: ++ offset[1] = offset[0] + offset[0] / 4; ++ break; ++ case V4L2_PIX_FMT_YUV422P: ++ offset[1] = offset[0] + offset[0] / 2; ++ break; ++ default: ++ offset[1] = 0; ++ break; ++ } ++ ++ buf = vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0); ++ next = buf; ++ if (ctx->prev[0]) ++ buf = vb2_dma_contig_plane_dma_addr(&ctx->prev[0]->vb2_buf, 0); ++ curr = buf; ++ if (ctx->prev[1]) ++ buf = vb2_dma_contig_plane_dma_addr(&ctx->prev[1]->vb2_buf, 0); ++ prev = buf; ++ ++ if (ctx->first_field == 0) { ++ if (ctx->field == 0) { ++ addr[0][0] = prev; ++ addr[0][1] = prev + offset[0]; ++ addr[0][2] = prev + offset[1]; ++ addr[1][0] = prev + pitch[0]; ++ addr[1][1] = prev + offset[0] + pitch[1]; ++ addr[1][2] = prev + offset[1] + pitch[2]; ++ addr[2][0] = curr; ++ addr[2][1] = curr + offset[0]; ++ addr[2][2] = curr + offset[1]; ++ addr[3][0] = curr + pitch[0]; ++ addr[3][1] = curr + offset[0] + pitch[1]; ++ addr[3][2] = curr + offset[1] + pitch[2]; ++ } else { ++ addr[0][0] = prev + pitch[0]; ++ addr[0][1] = prev + offset[0] + pitch[1]; ++ addr[0][2] = prev + offset[1] + pitch[2]; ++ addr[1][0] = curr; ++ addr[1][1] = curr + offset[0]; ++ addr[1][2] = curr + offset[1]; ++ addr[2][0] = curr + pitch[0]; ++ addr[2][1] = curr + offset[0] + pitch[1]; ++ addr[2][2] = curr + offset[1] + pitch[2]; ++ addr[3][0] = next; ++ addr[3][1] = next + offset[0]; ++ addr[3][2] = next + offset[1]; ++ } ++ } else { ++ if (ctx->field == 0) { ++ addr[0][0] = prev; ++ addr[0][1] = prev + offset[0]; ++ addr[0][2] = prev + offset[1]; ++ addr[1][0] = curr + pitch[0]; ++ addr[1][1] = curr + offset[0] + pitch[1]; ++ addr[1][2] = curr + offset[1] + pitch[2]; ++ addr[2][0] = curr; ++ addr[2][1] = curr + offset[0]; ++ addr[2][2] = curr + offset[1]; ++ addr[3][0] = next + pitch[0]; ++ addr[3][1] = next + offset[0] + pitch[1]; ++ addr[3][2] = next + offset[1] + pitch[2]; ++ } else { ++ addr[0][0] = prev + pitch[0]; ++ addr[0][1] = prev + offset[0] + pitch[1]; ++ addr[0][2] = prev + offset[1] + pitch[2]; ++ addr[1][0] = prev; ++ addr[1][1] = prev + offset[0]; ++ addr[1][2] = prev + offset[1]; ++ addr[2][0] = curr + pitch[0]; ++ addr[2][1] = curr + offset[0] + pitch[1]; ++ addr[2][2] = curr + offset[1] + pitch[2]; ++ addr[3][0] = curr; ++ addr[3][1] = curr + offset[0]; ++ addr[3][2] = curr + offset[1]; ++ } ++ } ++ ++ deinterlace_write(dev, DEINTERLACE_IN0_ADDR0, addr[0][0]); ++ deinterlace_write(dev, DEINTERLACE_IN0_ADDR1, addr[0][1]); ++ deinterlace_write(dev, DEINTERLACE_IN0_ADDR2, addr[0][2]); ++ ++ deinterlace_write(dev, DEINTERLACE_IN1_ADDR0, addr[1][0]); ++ deinterlace_write(dev, DEINTERLACE_IN1_ADDR1, addr[1][1]); ++ deinterlace_write(dev, DEINTERLACE_IN1_ADDR2, addr[1][2]); ++ ++ deinterlace_write(dev, DEINTERLACE_IN2_ADDR0, addr[2][0]); ++ deinterlace_write(dev, DEINTERLACE_IN2_ADDR1, addr[2][1]); ++ deinterlace_write(dev, DEINTERLACE_IN2_ADDR2, addr[2][2]); ++ ++ deinterlace_write(dev, DEINTERLACE_IN3_ADDR0, addr[3][0]); ++ deinterlace_write(dev, DEINTERLACE_IN3_ADDR1, addr[3][1]); ++ deinterlace_write(dev, DEINTERLACE_IN3_ADDR2, addr[3][2]); ++ ++ buf = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0); ++ deinterlace_write(dev, DEINTERLACE_OUT_ADDR0, buf); ++ deinterlace_write(dev, DEINTERLACE_OUT_ADDR1, buf + offset[0]); ++ deinterlace_write(dev, DEINTERLACE_OUT_ADDR2, buf + offset[1]); ++ ++ if (ctx->first_field == 0) ++ val = 4; ++ else ++ val = 5; ++ ++ reg = DEINTERLACE_INTP_PARAM0_LUMA_CUR_FAC_MODE(val); ++ reg |= DEINTERLACE_INTP_PARAM0_CHROMA_CUR_FAC_MODE(val); ++ msk = DEINTERLACE_INTP_PARAM0_LUMA_CUR_FAC_MODE_MSK; ++ msk |= DEINTERLACE_INTP_PARAM0_CHROMA_CUR_FAC_MODE_MSK; ++ deinterlace_clr_set_bits(dev, DEINTERLACE_INTP_PARAM0, msk, reg); ++ ++ reg = DEINTERLACE_POLAR_FIELD(ctx->field); ++ deinterlace_write(dev, DEINTERLACE_POLAR, reg); ++ ++ motion = ctx->prev[0] && ctx->prev[1]; ++ reg = DEINTERLACE_MODE_DEINT_LUMA; ++ if (motion) ++ reg |= DEINTERLACE_MODE_MOTION_EN; ++ reg |= DEINTERLACE_MODE_INTP_EN; ++ reg |= DEINTERLACE_MODE_AUTO_UPD_MODE(ctx->first_field); ++ reg |= DEINTERLACE_MODE_DEINT_CHROMA; ++ if (!motion) ++ reg |= DEINTERLACE_MODE_FIELD_MODE; ++ deinterlace_write(dev, DEINTERLACE_MODE, reg); ++ ++ deinterlace_set_bits(dev, DEINTERLACE_INT_CTRL, ++ DEINTERLACE_INT_EN); ++ ++ deinterlace_set_bits(dev, DEINTERLACE_CTRL, ++ DEINTERLACE_CTRL_START); ++} ++ ++static int deinterlace_job_ready(void *priv) ++{ ++ struct deinterlace_ctx *ctx = priv; ++ ++ return v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) >= 1 && ++ v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) >= 2; ++} ++ ++static void deinterlace_job_abort(void *priv) ++{ ++ struct deinterlace_ctx *ctx = priv; ++ ++ /* Will cancel the transaction in the next interrupt handler */ ++ ctx->aborting = 1; ++} ++ ++static irqreturn_t deinterlace_irq(int irq, void *data) ++{ ++ struct deinterlace_dev *dev = data; ++ struct vb2_v4l2_buffer *src, *dst; ++ struct deinterlace_ctx *ctx; ++ unsigned int val; ++ ++ ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); ++ if (!ctx) { ++ v4l2_err(&dev->v4l2_dev, ++ "Instance released before the end of transaction\n"); ++ return IRQ_NONE; ++ } ++ ++ val = deinterlace_read(dev, DEINTERLACE_STATUS); ++ if (!(val & DEINTERLACE_STATUS_FINISHED)) ++ return IRQ_NONE; ++ ++ deinterlace_write(dev, DEINTERLACE_INT_CTRL, 0); ++ deinterlace_set_bits(dev, DEINTERLACE_STATUS, ++ DEINTERLACE_STATUS_FINISHED); ++ deinterlace_clr_set_bits(dev, DEINTERLACE_CTRL, ++ DEINTERLACE_CTRL_START, 0); ++ ++ dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); ++ v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); ++ ++ if (ctx->field != ctx->first_field || ctx->aborting) { ++ ctx->field = ctx->first_field; ++ ++ src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); ++ if (ctx->prev[1]) ++ v4l2_m2m_buf_done(ctx->prev[1], VB2_BUF_STATE_DONE); ++ ctx->prev[1] = ctx->prev[0]; ++ ctx->prev[0] = src; ++ ++ v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); ++ } else { ++ ctx->field = !ctx->first_field; ++ deinterlace_device_run(ctx); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static void deinterlace_init(struct deinterlace_dev *dev) ++{ ++ u32 reg; ++ ++ deinterlace_write(dev, DEINTERLACE_OUT_PATH, 0); ++ ++ reg = DEINTERLACE_MD_PARAM0_MIN_LUMA_TH(4); ++ reg |= DEINTERLACE_MD_PARAM0_MAX_LUMA_TH(12); ++ reg |= DEINTERLACE_MD_PARAM0_AVG_LUMA_SHIFT(6); ++ reg |= DEINTERLACE_MD_PARAM0_TH_SHIFT(1); ++ deinterlace_write(dev, DEINTERLACE_MD_PARAM0, reg); ++ ++ reg = DEINTERLACE_MD_PARAM1_MOV_FAC_NONEDGE(2); ++ deinterlace_write(dev, DEINTERLACE_MD_PARAM1, reg); ++ ++ reg = DEINTERLACE_MD_PARAM2_CHROMA_SPATIAL_TH(128); ++ reg |= DEINTERLACE_MD_PARAM2_CHROMA_DIFF_TH(5); ++ reg |= DEINTERLACE_MD_PARAM2_PIX_STATIC_TH(3); ++ deinterlace_write(dev, DEINTERLACE_MD_PARAM2, reg); ++ ++ reg = DEINTERLACE_INTP_PARAM0_ANGLE_LIMIT(20); ++ reg |= DEINTERLACE_INTP_PARAM0_ANGLE_CONST_TH(5); ++ reg |= DEINTERLACE_INTP_PARAM0_LUMA_CUR_FAC_MODE(1); ++ reg |= DEINTERLACE_INTP_PARAM0_CHROMA_CUR_FAC_MODE(1); ++ deinterlace_write(dev, DEINTERLACE_INTP_PARAM0, reg); ++ ++ reg = DEINTERLACE_MD_CH_PARAM_BLEND_MODE(1); ++ reg |= DEINTERLACE_MD_CH_PARAM_FONT_PRO_EN; ++ reg |= DEINTERLACE_MD_CH_PARAM_FONT_PRO_TH(48); ++ reg |= DEINTERLACE_MD_CH_PARAM_FONT_PRO_FAC(4); ++ deinterlace_write(dev, DEINTERLACE_MD_CH_PARAM, reg); ++ ++ reg = DEINTERLACE_INTP_PARAM1_A(4); ++ reg |= DEINTERLACE_INTP_PARAM1_EN; ++ reg |= DEINTERLACE_INTP_PARAM1_C(10); ++ reg |= DEINTERLACE_INTP_PARAM1_CMAX(64); ++ reg |= DEINTERLACE_INTP_PARAM1_MAXRAT(2); ++ deinterlace_write(dev, DEINTERLACE_INTP_PARAM1, reg); ++ ++ /* only 32-bit addresses are supported, so high bits are always 0 */ ++ deinterlace_write(dev, DEINTERLACE_IN0_ADDRH, 0); ++ deinterlace_write(dev, DEINTERLACE_IN1_ADDRH, 0); ++ deinterlace_write(dev, DEINTERLACE_IN2_ADDRH, 0); ++ deinterlace_write(dev, DEINTERLACE_IN3_ADDRH, 0); ++ deinterlace_write(dev, DEINTERLACE_OUT_ADDRH, 0); ++} ++ ++static inline struct deinterlace_ctx *deinterlace_file2ctx(struct file *file) ++{ ++ return container_of(file->private_data, struct deinterlace_ctx, fh); ++} ++ ++static bool deinterlace_check_format(u32 pixelformat) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(deinterlace_formats); i++) ++ if (deinterlace_formats[i] == pixelformat) ++ return true; ++ ++ return false; ++} ++ ++static void deinterlace_prepare_format(struct v4l2_pix_format *pix_fmt) ++{ ++ unsigned int bytesperline = pix_fmt->bytesperline; ++ unsigned int height = pix_fmt->height; ++ unsigned int width = pix_fmt->width; ++ unsigned int sizeimage; ++ ++ width = clamp(width, DEINTERLACE_MIN_WIDTH, ++ DEINTERLACE_MAX_WIDTH); ++ height = clamp(height, DEINTERLACE_MIN_HEIGHT, ++ DEINTERLACE_MAX_HEIGHT); ++ ++ /* try to respect userspace wishes about pitch */ ++ bytesperline = ALIGN(bytesperline, 2); ++ if (bytesperline < ALIGN(width, 2)) ++ bytesperline = ALIGN(width, 2); ++ ++ /* luma */ ++ sizeimage = bytesperline * height; ++ /* chroma */ ++ switch (pix_fmt->pixelformat) { ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ case V4L2_PIX_FMT_YUV420: ++ sizeimage += bytesperline * height / 2; ++ break; ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ case V4L2_PIX_FMT_YUV422P: ++ sizeimage += bytesperline * height; ++ break; ++ } ++ ++ if (pix_fmt->sizeimage < sizeimage) ++ pix_fmt->sizeimage = sizeimage; ++ ++ pix_fmt->width = width; ++ pix_fmt->height = height; ++ pix_fmt->bytesperline = bytesperline; ++} ++ ++static int deinterlace_querycap(struct file *file, void *priv, ++ struct v4l2_capability *cap) ++{ ++ strscpy(cap->driver, DEINTERLACE_NAME, sizeof(cap->driver)); ++ strscpy(cap->card, DEINTERLACE_NAME, sizeof(cap->card)); ++ snprintf(cap->bus_info, sizeof(cap->bus_info), ++ "platform:%s", DEINTERLACE_NAME); ++ ++ return 0; ++} ++ ++static int deinterlace_enum_fmt(struct file *file, void *priv, ++ struct v4l2_fmtdesc *f) ++{ ++ if (f->index < ARRAY_SIZE(deinterlace_formats)) { ++ f->pixelformat = deinterlace_formats[f->index]; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int deinterlace_enum_framesizes(struct file *file, void *priv, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ if (fsize->index != 0) ++ return -EINVAL; ++ ++ if (!deinterlace_check_format(fsize->pixel_format)) ++ return -EINVAL; ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; ++ fsize->stepwise.min_width = DEINTERLACE_MIN_WIDTH; ++ fsize->stepwise.min_height = DEINTERLACE_MIN_HEIGHT; ++ fsize->stepwise.max_width = DEINTERLACE_MAX_WIDTH; ++ fsize->stepwise.max_height = DEINTERLACE_MAX_HEIGHT; ++ fsize->stepwise.step_width = 2; ++ ++ switch (fsize->pixel_format) { ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ case V4L2_PIX_FMT_YUV420: ++ fsize->stepwise.step_height = 2; ++ break; ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ case V4L2_PIX_FMT_YUV422P: ++ fsize->stepwise.step_height = 1; ++ break; ++ } ++ ++ return 0; ++} ++ ++static int deinterlace_set_cap_format(struct deinterlace_ctx *ctx, ++ struct v4l2_pix_format *f) ++{ ++ if (!deinterlace_check_format(ctx->src_fmt.pixelformat)) ++ return -EINVAL; ++ ++ f->pixelformat = ctx->src_fmt.pixelformat; ++ f->field = V4L2_FIELD_NONE; ++ f->width = ctx->src_fmt.width; ++ f->height = ctx->src_fmt.height; ++ ++ deinterlace_prepare_format(f); ++ ++ return 0; ++} ++ ++static int deinterlace_g_fmt_vid_cap(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); ++ ++ f->fmt.pix = ctx->dst_fmt; ++ ++ return 0; ++} ++ ++static int deinterlace_g_fmt_vid_out(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); ++ ++ f->fmt.pix = ctx->src_fmt; ++ ++ return 0; ++} ++ ++static int deinterlace_try_fmt_vid_cap(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); ++ ++ return deinterlace_set_cap_format(ctx, &f->fmt.pix); ++} ++ ++static int deinterlace_try_fmt_vid_out(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ if (!deinterlace_check_format(f->fmt.pix.pixelformat)) ++ f->fmt.pix.pixelformat = deinterlace_formats[0]; ++ ++ if (f->fmt.pix.field != V4L2_FIELD_INTERLACED_TB && ++ f->fmt.pix.field != V4L2_FIELD_INTERLACED_BT && ++ f->fmt.pix.field != V4L2_FIELD_INTERLACED) ++ f->fmt.pix.field = V4L2_FIELD_INTERLACED; ++ ++ deinterlace_prepare_format(&f->fmt.pix); ++ ++ return 0; ++} ++ ++static int deinterlace_s_fmt_vid_cap(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); ++ struct vb2_queue *vq; ++ int ret; ++ ++ ret = deinterlace_try_fmt_vid_cap(file, priv, f); ++ if (ret) ++ return ret; ++ ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ ctx->dst_fmt = f->fmt.pix; ++ ++ return 0; ++} ++ ++static int deinterlace_s_fmt_vid_out(struct file *file, void *priv, ++ struct v4l2_format *f) ++{ ++ struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); ++ struct vb2_queue *vq; ++ int ret; ++ ++ ret = deinterlace_try_fmt_vid_out(file, priv, f); ++ if (ret) ++ return ret; ++ ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ /* ++ * Capture queue has to be also checked, because format and size ++ * depends on output format and size. ++ */ ++ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ ctx->src_fmt = f->fmt.pix; ++ ++ /* Propagate colorspace 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; ++ ++ return deinterlace_set_cap_format(ctx, &ctx->dst_fmt); ++} ++ ++static const struct v4l2_ioctl_ops deinterlace_ioctl_ops = { ++ .vidioc_querycap = deinterlace_querycap, ++ ++ .vidioc_enum_framesizes = deinterlace_enum_framesizes, ++ ++ .vidioc_enum_fmt_vid_cap = deinterlace_enum_fmt, ++ .vidioc_g_fmt_vid_cap = deinterlace_g_fmt_vid_cap, ++ .vidioc_try_fmt_vid_cap = deinterlace_try_fmt_vid_cap, ++ .vidioc_s_fmt_vid_cap = deinterlace_s_fmt_vid_cap, ++ ++ .vidioc_enum_fmt_vid_out = deinterlace_enum_fmt, ++ .vidioc_g_fmt_vid_out = deinterlace_g_fmt_vid_out, ++ .vidioc_try_fmt_vid_out = deinterlace_try_fmt_vid_out, ++ .vidioc_s_fmt_vid_out = deinterlace_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, ++}; ++ ++static int deinterlace_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, ++ unsigned int *nplanes, unsigned int sizes[], ++ struct device *alloc_devs[]) ++{ ++ struct deinterlace_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 int deinterlace_buf_prepare(struct vb2_buffer *vb) ++{ ++ struct vb2_queue *vq = vb->vb2_queue; ++ struct deinterlace_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; ++ ++ vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); ++ ++ return 0; ++} ++ ++static void deinterlace_buf_queue(struct vb2_buffer *vb) ++{ ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct deinterlace_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); ++ ++ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); ++} ++ ++static void deinterlace_queue_cleanup(struct vb2_queue *vq, u32 state) ++{ ++ struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); ++ struct vb2_v4l2_buffer *vbuf; ++ ++ do { ++ 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) ++ v4l2_m2m_buf_done(vbuf, state); ++ } while (vbuf); ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ++ if (ctx->prev[0]) ++ v4l2_m2m_buf_done(ctx->prev[0], state); ++ if (ctx->prev[1]) ++ v4l2_m2m_buf_done(ctx->prev[1], state); ++ } ++} ++ ++static int deinterlace_start_streaming(struct vb2_queue *vq, unsigned int count) ++{ ++ struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); ++ struct device *dev = ctx->dev->dev; ++ int ret; ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ++ ret = pm_runtime_get_sync(dev); ++ if (ret < 0) { ++ dev_err(dev, "Failed to enable module\n"); ++ ++ goto err_runtime_get; ++ } ++ ++ ctx->first_field = ++ ctx->src_fmt.field == V4L2_FIELD_INTERLACED_BT; ++ ctx->field = ctx->first_field; ++ ++ ctx->prev[0] = NULL; ++ ctx->prev[1] = NULL; ++ ctx->aborting = 0; ++ ++ ctx->flag1_buf = dma_alloc_coherent(dev, FLAG_SIZE, ++ &ctx->flag1_buf_dma, ++ GFP_KERNEL); ++ if (!ctx->flag1_buf) { ++ ret = -ENOMEM; ++ ++ goto err_no_mem1; ++ } ++ ++ ctx->flag2_buf = dma_alloc_coherent(dev, FLAG_SIZE, ++ &ctx->flag2_buf_dma, ++ GFP_KERNEL); ++ if (!ctx->flag2_buf) { ++ ret = -ENOMEM; ++ ++ goto err_no_mem2; ++ } ++ } ++ ++ return 0; ++ ++err_no_mem2: ++ dma_free_coherent(dev, FLAG_SIZE, ctx->flag1_buf, ++ ctx->flag1_buf_dma); ++err_no_mem1: ++ pm_runtime_put(dev); ++err_runtime_get: ++ deinterlace_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); ++ ++ return ret; ++} ++ ++static void deinterlace_stop_streaming(struct vb2_queue *vq) ++{ ++ struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); ++ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ++ struct device *dev = ctx->dev->dev; ++ ++ dma_free_coherent(dev, FLAG_SIZE, ctx->flag1_buf, ++ ctx->flag1_buf_dma); ++ dma_free_coherent(dev, FLAG_SIZE, ctx->flag2_buf, ++ ctx->flag2_buf_dma); ++ ++ pm_runtime_put(dev); ++ } ++ ++ deinterlace_queue_cleanup(vq, VB2_BUF_STATE_ERROR); ++} ++ ++static const struct vb2_ops deinterlace_qops = { ++ .queue_setup = deinterlace_queue_setup, ++ .buf_prepare = deinterlace_buf_prepare, ++ .buf_queue = deinterlace_buf_queue, ++ .start_streaming = deinterlace_start_streaming, ++ .stop_streaming = deinterlace_stop_streaming, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++}; ++ ++static int deinterlace_queue_init(void *priv, struct vb2_queue *src_vq, ++ struct vb2_queue *dst_vq) ++{ ++ struct deinterlace_ctx *ctx = priv; ++ int ret; ++ ++ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ++ src_vq->io_modes = VB2_MMAP | VB2_DMABUF; ++ src_vq->drv_priv = ctx; ++ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); ++ src_vq->min_buffers_needed = 1; ++ src_vq->ops = &deinterlace_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; ++ ++ 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 v4l2_m2m_buffer); ++ dst_vq->min_buffers_needed = 2; ++ dst_vq->ops = &deinterlace_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; ++ ++ ret = vb2_queue_init(dst_vq); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int deinterlace_open(struct file *file) ++{ ++ struct deinterlace_dev *dev = video_drvdata(file); ++ struct deinterlace_ctx *ctx = NULL; ++ int ret; ++ ++ if (mutex_lock_interruptible(&dev->dev_mutex)) ++ return -ERESTARTSYS; ++ ++ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) { ++ mutex_unlock(&dev->dev_mutex); ++ return -ENOMEM; ++ } ++ ++ /* default output format */ ++ ctx->src_fmt.pixelformat = deinterlace_formats[0]; ++ ctx->src_fmt.field = V4L2_FIELD_INTERLACED; ++ ctx->src_fmt.width = 640; ++ ctx->src_fmt.height = 480; ++ deinterlace_prepare_format(&ctx->src_fmt); ++ ++ /* default capture format */ ++ ctx->dst_fmt.pixelformat = deinterlace_formats[0]; ++ ctx->dst_fmt.field = V4L2_FIELD_NONE; ++ ctx->dst_fmt.width = 640; ++ ctx->dst_fmt.height = 480; ++ deinterlace_prepare_format(&ctx->dst_fmt); ++ ++ v4l2_fh_init(&ctx->fh, video_devdata(file)); ++ file->private_data = &ctx->fh; ++ ctx->dev = dev; ++ ++ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, ++ &deinterlace_queue_init); ++ if (IS_ERR(ctx->fh.m2m_ctx)) { ++ ret = PTR_ERR(ctx->fh.m2m_ctx); ++ goto err_free; ++ } ++ ++ v4l2_fh_add(&ctx->fh); ++ ++ mutex_unlock(&dev->dev_mutex); ++ ++ return 0; ++ ++err_free: ++ kfree(ctx); ++ mutex_unlock(&dev->dev_mutex); ++ ++ return ret; ++} ++ ++static int deinterlace_release(struct file *file) ++{ ++ struct deinterlace_dev *dev = video_drvdata(file); ++ struct deinterlace_ctx *ctx = container_of(file->private_data, ++ struct deinterlace_ctx, fh); ++ ++ mutex_lock(&dev->dev_mutex); ++ ++ v4l2_fh_del(&ctx->fh); ++ v4l2_fh_exit(&ctx->fh); ++ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); ++ ++ kfree(ctx); ++ ++ mutex_unlock(&dev->dev_mutex); ++ ++ return 0; ++} ++ ++static const struct v4l2_file_operations deinterlace_fops = { ++ .owner = THIS_MODULE, ++ .open = deinterlace_open, ++ .release = deinterlace_release, ++ .poll = v4l2_m2m_fop_poll, ++ .unlocked_ioctl = video_ioctl2, ++ .mmap = v4l2_m2m_fop_mmap, ++}; ++ ++static const struct video_device deinterlace_video_device = { ++ .name = DEINTERLACE_NAME, ++ .vfl_dir = VFL_DIR_M2M, ++ .fops = &deinterlace_fops, ++ .ioctl_ops = &deinterlace_ioctl_ops, ++ .minor = -1, ++ .release = video_device_release_empty, ++ .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, ++}; ++ ++static const struct v4l2_m2m_ops deinterlace_m2m_ops = { ++ .device_run = deinterlace_device_run, ++ .job_ready = deinterlace_job_ready, ++ .job_abort = deinterlace_job_abort, ++}; ++ ++static int deinterlace_probe(struct platform_device *pdev) ++{ ++ struct deinterlace_dev *dev; ++ struct video_device *vfd; ++ struct resource *res; ++ int irq, ret; ++ ++ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return -ENOMEM; ++ ++ dev->vfd = deinterlace_video_device; ++ dev->dev = &pdev->dev; ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq <= 0) ++ return irq; ++ ++ ret = devm_request_irq(dev->dev, irq, deinterlace_irq, ++ 0, dev_name(dev->dev), dev); ++ if (ret) { ++ dev_err(dev->dev, "Failed to request IRQ\n"); ++ ++ return ret; ++ } ++ ++ ret = of_dma_configure(dev->dev, dev->dev->of_node, true); ++ if (ret) ++ return ret; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ dev->base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(dev->base)) ++ return PTR_ERR(dev->base); ++ ++ dev->bus_clk = devm_clk_get(dev->dev, "bus"); ++ if (IS_ERR(dev->bus_clk)) { ++ dev_err(dev->dev, "Failed to get bus clock\n"); ++ ++ return PTR_ERR(dev->bus_clk); ++ } ++ ++ dev->mod_clk = devm_clk_get(dev->dev, "mod"); ++ if (IS_ERR(dev->mod_clk)) { ++ dev_err(dev->dev, "Failed to get mod clock\n"); ++ ++ return PTR_ERR(dev->mod_clk); ++ } ++ ++ dev->ram_clk = devm_clk_get(dev->dev, "ram"); ++ if (IS_ERR(dev->ram_clk)) { ++ dev_err(dev->dev, "Failed to get ram clock\n"); ++ ++ return PTR_ERR(dev->ram_clk); ++ } ++ ++ dev->rstc = devm_reset_control_get(dev->dev, NULL); ++ if (IS_ERR(dev->rstc)) { ++ dev_err(dev->dev, "Failed to get reset control\n"); ++ ++ return PTR_ERR(dev->rstc); ++ } ++ ++ mutex_init(&dev->dev_mutex); ++ ++ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); ++ if (ret) { ++ dev_err(dev->dev, "Failed to register V4L2 device\n"); ++ ++ return ret; ++ } ++ ++ vfd = &dev->vfd; ++ vfd->lock = &dev->dev_mutex; ++ vfd->v4l2_dev = &dev->v4l2_dev; ++ ++ snprintf(vfd->name, sizeof(vfd->name), "%s", ++ deinterlace_video_device.name); ++ video_set_drvdata(vfd, dev); ++ ++ ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); ++ if (ret) { ++ v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); ++ ++ goto err_v4l2; ++ } ++ ++ v4l2_info(&dev->v4l2_dev, ++ "Device registered as /dev/video%d\n", vfd->num); ++ ++ dev->m2m_dev = v4l2_m2m_init(&deinterlace_m2m_ops); ++ if (IS_ERR(dev->m2m_dev)) { ++ v4l2_err(&dev->v4l2_dev, ++ "Failed to initialize V4L2 M2M device\n"); ++ ret = PTR_ERR(dev->m2m_dev); ++ ++ goto err_video; ++ } ++ ++ platform_set_drvdata(pdev, dev); ++ ++ pm_runtime_enable(dev->dev); ++ ++ return 0; ++ ++err_video: ++ video_unregister_device(&dev->vfd); ++err_v4l2: ++ v4l2_device_unregister(&dev->v4l2_dev); ++ ++ return ret; ++} ++ ++static int deinterlace_remove(struct platform_device *pdev) ++{ ++ struct deinterlace_dev *dev = platform_get_drvdata(pdev); ++ ++ v4l2_m2m_release(dev->m2m_dev); ++ video_unregister_device(&dev->vfd); ++ v4l2_device_unregister(&dev->v4l2_dev); ++ ++ pm_runtime_force_suspend(&pdev->dev); ++ ++ return 0; ++} ++ ++static int deinterlace_runtime_resume(struct device *device) ++{ ++ struct deinterlace_dev *dev = dev_get_drvdata(device); ++ int ret; ++ ++ ret = clk_prepare_enable(dev->bus_clk); ++ if (ret) { ++ dev_err(dev->dev, "Failed to enable bus clock\n"); ++ ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(dev->mod_clk); ++ if (ret) { ++ dev_err(dev->dev, "Failed to enable mod clock\n"); ++ ++ goto err_bus_clk; ++ } ++ ++ ret = clk_prepare_enable(dev->ram_clk); ++ if (ret) { ++ dev_err(dev->dev, "Failed to enable ram clock\n"); ++ ++ goto err_mod_clk; ++ } ++ ++ ret = reset_control_deassert(dev->rstc); ++ if (ret) { ++ dev_err(dev->dev, "Failed to apply reset\n"); ++ ++ goto err_ram_clk; ++ } ++ ++ deinterlace_init(dev); ++ ++ return 0; ++ ++err_ram_clk: ++ clk_disable_unprepare(dev->ram_clk); ++err_mod_clk: ++ clk_disable_unprepare(dev->mod_clk); ++err_bus_clk: ++ clk_disable_unprepare(dev->bus_clk); ++ ++ return ret; ++} ++ ++static int deinterlace_runtime_suspend(struct device *device) ++{ ++ struct deinterlace_dev *dev = dev_get_drvdata(device); ++ ++ reset_control_assert(dev->rstc); ++ ++ clk_disable_unprepare(dev->ram_clk); ++ clk_disable_unprepare(dev->mod_clk); ++ clk_disable_unprepare(dev->bus_clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id deinterlace_dt_match[] = { ++ { .compatible = "allwinner,sun50i-h6-deinterlace" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, deinterlace_dt_match); ++ ++static const struct dev_pm_ops deinterlace_pm_ops = { ++ .runtime_resume = deinterlace_runtime_resume, ++ .runtime_suspend = deinterlace_runtime_suspend, ++}; ++ ++static struct platform_driver deinterlace_driver = { ++ .probe = deinterlace_probe, ++ .remove = deinterlace_remove, ++ .driver = { ++ .name = DEINTERLACE_NAME, ++ .of_match_table = deinterlace_dt_match, ++ .pm = &deinterlace_pm_ops, ++ }, ++}; ++module_platform_driver(deinterlace_driver); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Jernej Skrabec "); ++MODULE_DESCRIPTION("Allwinner Deinterlace driver"); +diff --git a/drivers/media/platform/sunxi/sun50i-di/sun50i-di.h b/drivers/media/platform/sunxi/sun50i-di/sun50i-di.h +new file mode 100644 +index 000000000000..8cb19955ef06 +--- /dev/null ++++ b/drivers/media/platform/sunxi/sun50i-di/sun50i-di.h +@@ -0,0 +1,172 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Allwinner Deinterlace driver ++ * ++ * Copyright (C) 2020 Jernej Skrabec ++ */ ++ ++#ifndef _SUN8I_DEINTERLACE_H_ ++#define _SUN8I_DEINTERLACE_H_ ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define DEINTERLACE_NAME "sun50i-di" ++ ++#define DEINTERLACE_CTRL 0x00 ++#define DEINTERLACE_CTRL_START BIT(0) ++#define DEINTERLACE_CTRL_IOMMU_EN BIT(16) ++#define DEINTERLACE_CTRL_RESET BIT(31) ++ ++#define DEINTERLACE_INT_CTRL 0x04 ++#define DEINTERLACE_INT_EN BIT(0) ++ ++#define DEINTERLACE_STATUS 0x08 ++#define DEINTERLACE_STATUS_FINISHED BIT(0) ++#define DEINTERLACE_STATUS_BUSY BIT(8) ++ ++#define DEINTERLACE_SIZE 0x10 ++#define DEINTERLACE_SIZE_WIDTH(w) \ ++ (((w) - 1) & 0x7ff) ++#define DEINTERLACE_SIZE_HEIGHT(h) \ ++ ((((h) - 1) & 0x7ff) << 16) ++ ++#define DEINTERLACE_FORMAT 0x14 ++#define DEINTERLACE_FORMAT_YUV420P 0 ++#define DEINTERLACE_FORMAT_YUV420SP 1 ++#define DEINTERLACE_FORMAT_YUV422P 2 ++#define DEINTERLACE_FORMAT_YUV422SP 3 ++ ++#define DEINTERLACE_POLAR 0x18 ++#define DEINTERLACE_POLAR_FIELD(x) ((x) & 1) ++ ++/* all pitch registers accept 16-bit values */ ++#define DEINTERLACE_IN_PITCH0 0x20 ++#define DEINTERLACE_IN_PITCH1 0x24 ++#define DEINTERLACE_IN_PITCH2 0x28 ++#define DEINTERLACE_OUT_PITCH0 0x30 ++#define DEINTERLACE_OUT_PITCH1 0x34 ++#define DEINTERLACE_OUT_PITCH2 0x38 ++#define DEINTERLACE_FLAG_PITCH 0x40 ++#define DEINTERLACE_IN0_ADDR0 0x50 ++#define DEINTERLACE_IN0_ADDR1 0x54 ++#define DEINTERLACE_IN0_ADDR2 0x58 ++#define DEINTERLACE_IN0_ADDRH 0x5c ++#define DEINTERLACE_IN1_ADDR0 0x60 ++#define DEINTERLACE_IN1_ADDR1 0x64 ++#define DEINTERLACE_IN1_ADDR2 0x68 ++#define DEINTERLACE_IN1_ADDRH 0x6c ++#define DEINTERLACE_IN2_ADDR0 0x70 ++#define DEINTERLACE_IN2_ADDR1 0x74 ++#define DEINTERLACE_IN2_ADDR2 0x78 ++#define DEINTERLACE_IN2_ADDRH 0x7c ++#define DEINTERLACE_IN3_ADDR0 0x80 ++#define DEINTERLACE_IN3_ADDR1 0x84 ++#define DEINTERLACE_IN3_ADDR2 0x88 ++#define DEINTERLACE_IN3_ADDRH 0x8c ++#define DEINTERLACE_OUT_ADDR0 0x90 ++#define DEINTERLACE_OUT_ADDR1 0x94 ++#define DEINTERLACE_OUT_ADDR2 0x98 ++#define DEINTERLACE_OUT_ADDRH 0x9c ++#define DEINTERLACE_IN_FLAG_ADDR 0xa0 ++#define DEINTERLACE_OUT_FLAG_ADDR 0xa4 ++#define DEINTERLACE_FLAG_ADDRH 0xa8 ++ ++#define DEINTERLACE_ADDRH0(x) ((x) & 0xff) ++#define DEINTERLACE_ADDRH1(x) (((x) & 0xff) << 8) ++#define DEINTERLACE_ADDRH2(x) (((x) & 0xff) << 16) ++ ++#define DEINTERLACE_MODE 0xb0 ++#define DEINTERLACE_MODE_DEINT_LUMA BIT(0) ++#define DEINTERLACE_MODE_MOTION_EN BIT(4) ++#define DEINTERLACE_MODE_INTP_EN BIT(5) ++#define DEINTERLACE_MODE_AUTO_UPD_MODE(x) (((x) & 3) << 12) ++#define DEINTERLACE_MODE_DEINT_CHROMA BIT(16) ++#define DEINTERLACE_MODE_FIELD_MODE BIT(31) ++ ++#define DEINTERLACE_MD_PARAM0 0xb4 ++#define DEINTERLACE_MD_PARAM0_MIN_LUMA_TH(x) ((x) & 0xff) ++#define DEINTERLACE_MD_PARAM0_MAX_LUMA_TH(x) (((x) & 0xff) << 8) ++#define DEINTERLACE_MD_PARAM0_AVG_LUMA_SHIFT(x) (((x) & 0xf) << 16) ++#define DEINTERLACE_MD_PARAM0_TH_SHIFT(x) (((x) & 0xf) << 24) ++ ++#define DEINTERLACE_MD_PARAM1 0xb8 ++#define DEINTERLACE_MD_PARAM1_MOV_FAC_NONEDGE(x) (((x) & 0x3) << 28) ++ ++#define DEINTERLACE_MD_PARAM2 0xbc ++#define DEINTERLACE_MD_PARAM2_CHROMA_SPATIAL_TH(x) (((x) & 0xff) << 8) ++#define DEINTERLACE_MD_PARAM2_CHROMA_DIFF_TH(x) (((x) & 0xff) << 16) ++#define DEINTERLACE_MD_PARAM2_PIX_STATIC_TH(x) (((x) & 0x3) << 28) ++ ++#define DEINTERLACE_INTP_PARAM0 0xc0 ++#define DEINTERLACE_INTP_PARAM0_ANGLE_LIMIT(x) ((x) & 0x1f) ++#define DEINTERLACE_INTP_PARAM0_ANGLE_CONST_TH(x) (((x) & 7) << 8) ++#define DEINTERLACE_INTP_PARAM0_LUMA_CUR_FAC_MODE(x) (((x) & 7) << 16) ++#define DEINTERLACE_INTP_PARAM0_LUMA_CUR_FAC_MODE_MSK (7 << 16) ++#define DEINTERLACE_INTP_PARAM0_CHROMA_CUR_FAC_MODE(x) (((x) & 7) << 20) ++#define DEINTERLACE_INTP_PARAM0_CHROMA_CUR_FAC_MODE_MSK (7 << 20) ++ ++#define DEINTERLACE_MD_CH_PARAM 0xc4 ++#define DEINTERLACE_MD_CH_PARAM_BLEND_MODE(x) ((x) & 0xf) ++#define DEINTERLACE_MD_CH_PARAM_FONT_PRO_EN BIT(8) ++#define DEINTERLACE_MD_CH_PARAM_FONT_PRO_TH(x) (((x) & 0xff) << 16) ++#define DEINTERLACE_MD_CH_PARAM_FONT_PRO_FAC(x) (((x) & 0x1f) << 24) ++ ++#define DEINTERLACE_INTP_PARAM1 0xc8 ++#define DEINTERLACE_INTP_PARAM1_A(x) ((x) & 7) ++#define DEINTERLACE_INTP_PARAM1_EN BIT(3) ++#define DEINTERLACE_INTP_PARAM1_C(x) (((x) & 0xf) << 4) ++#define DEINTERLACE_INTP_PARAM1_CMAX(x) (((x) & 0xff) << 8) ++#define DEINTERLACE_INTP_PARAM1_MAXRAT(x) (((x) & 3) << 16) ++ ++#define DEINTERLACE_OUT_PATH 0x200 ++ ++#define DEINTERLACE_MIN_WIDTH 2U ++#define DEINTERLACE_MIN_HEIGHT 2U ++#define DEINTERLACE_MAX_WIDTH 2048U ++#define DEINTERLACE_MAX_HEIGHT 1100U ++ ++struct deinterlace_ctx { ++ struct v4l2_fh fh; ++ struct deinterlace_dev *dev; ++ ++ struct v4l2_pix_format src_fmt; ++ struct v4l2_pix_format dst_fmt; ++ ++ void *flag1_buf; ++ dma_addr_t flag1_buf_dma; ++ ++ void *flag2_buf; ++ dma_addr_t flag2_buf_dma; ++ ++ struct vb2_v4l2_buffer *prev[2]; ++ ++ unsigned int first_field; ++ unsigned int field; ++ ++ int aborting; ++}; ++ ++struct deinterlace_dev { ++ struct v4l2_device v4l2_dev; ++ struct video_device vfd; ++ struct device *dev; ++ struct v4l2_m2m_dev *m2m_dev; ++ ++ /* Device file mutex */ ++ struct mutex dev_mutex; ++ ++ void __iomem *base; ++ ++ struct clk *bus_clk; ++ struct clk *mod_clk; ++ struct clk *ram_clk; ++ ++ struct reset_control *rstc; ++}; ++ ++#endif +-- +2.27.0 + diff --git a/projects/Allwinner/patches/linux/x0002-arm64-dts-h6-deinterlace.patch b/projects/Allwinner/patches/linux/x0002-arm64-dts-h6-deinterlace.patch new file mode 100644 index 0000000000..509bbbd6b7 --- /dev/null +++ b/projects/Allwinner/patches/linux/x0002-arm64-dts-h6-deinterlace.patch @@ -0,0 +1,35 @@ +From 559f5716ee5e45e87e3bb7f7e5ded5c6116d194b Mon Sep 17 00:00:00 2001 +From: Jernej Skrabec +Date: Tue, 26 May 2020 20:08:27 +0200 +Subject: [PATCH 2/2] arm64: dts: h6 deinterlace + +Signed-off-by: Jernej Skrabec +--- + arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +index 78b1361dfbb9..d45893b7b7b8 100644 +--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi +@@ -152,6 +152,17 @@ mixer0_out_tcon_top_mixer0: endpoint { + }; + }; + ++ deinterlace: deinterlace@1420000 { ++ compatible = "allwinner,sun50i-h6-deinterlace"; ++ reg = <0x01420000 0x2000>; ++ clocks = <&ccu CLK_BUS_DEINTERLACE>, ++ <&ccu CLK_DEINTERLACE>, ++ <&ccu CLK_MBUS_DEINTERLACE>; ++ clock-names = "bus", "mod", "ram"; ++ resets = <&ccu RST_BUS_DEINTERLACE>; ++ interrupts = ; ++ }; ++ + video-codec@1c0e000 { + compatible = "allwinner,sun50i-h6-video-engine"; + reg = <0x01c0e000 0x2000>; +-- +2.27.0 +