Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[PATCH 0/8] Qualcomm video decoder/encoder driver

408 views
Skip to first unread message

Stanimir Varbanov

unread,
Aug 22, 2016, 9:20:06 AM8/22/16
to
This patchset introduces a basic support for Qualcomm video
acceleration hardware used for video stream decoding/encoding.
The video IP can found on various qcom SoCs like apq8084, msm8916
and msm8996, hence it is widly distributed but the driver is
missing in the mainline.

The v4l2 driver is something like a wrapper over Host Firmware
Interface. The HFI itself is a set of command and message packets
send/received through shared memory, and its purpose is to
comunicate with the firmware which is run on remote processor.
The Venus is the name of the video hardware IP that doing the
video acceleration.

From the software point of view the HFI interface is implemented
in the files with prefix hfi_xxx. It acts as a translation layer
between HFI and v4l2 layer. There is one special file in the
driver called hfi_venus which doing most of the driver
orchestration work. Something more it setups Venus core, run it
and handle commands and messages from low-level point of view with
the help of provided functions by HFI interface.

I think that the driver is in good shape for mainline kernel, and
I hope the review comments will help to improve it, so please
do review and make comments.

The driver depends on:
- venus remoteproc driver posted at [1].
- out-of-tree qcom IOMMU driver and IOMMU probe deferral support
at [2].

The driver has been tested on db410c (with apq8016 SoC) with simple
v4l2 test applications and with gstreamer v4l2 videodec plugin,
and v4l2 h264 out-of-tree gstreamer videoenc plugin.

The output of v4l2-compliance test looks like:

root@dragonboard-410c:/home/linaro# ./v4l2-compliance -d /dev/video0
v4l2-compliance SHA : ee1ab491019f80052834d14c76bdd1c1b46f2158

Driver Info:
Driver name : vidc
Card type : video decoder
Bus info : platform:vidc
Driver version: 4.8.0
Capabilities : 0x84204000
Video Memory-to-Memory Multiplanar
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04204000
Video Memory-to-Memory Multiplanar
Streaming
Extended Pix Format

Compliance test for device /dev/video0 (not using libv4l2):

Required ioctls:
test VIDIOC_QUERYCAP: OK

Allow for multiple opens:
test second video open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK

Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 7 Private Controls: 0

Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK

Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test VIDIOC_EXPBUF: OK

Test input 0:

Total: 43, Succeeded: 43, Failed: 0, Warnings: 0

root@dragonboard-410c:/home/linaro# ./v4l2-compliance -d /dev/video1
v4l2-compliance SHA : ee1ab491019f80052834d14c76bdd1c1b46f2158

Driver Info:
Driver name : vidc
Card type : video encoder
Bus info : platform:vidc
Driver version: 4.8.0
Capabilities : 0x84204000
Video Memory-to-Memory Multiplanar
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04204000
Video Memory-to-Memory Multiplanar
Streaming
Extended Pix Format

Compliance test for device /dev/video1 (not using libv4l2):

Required ioctls:
test VIDIOC_QUERYCAP: OK

Allow for multiple opens:
test second video open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK
test for unlimited opens: OK

Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK (Not Supported)

Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0

Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0

Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)

Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 32 Private Controls: 0

Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK
test Composing: OK (Not Supported)
test Scaling: OK

Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
test VIDIOC_EXPBUF: OK

Test input 0:

Total: 43, Succeeded: 43, Failed: 0, Warnings: 0

regards,
Stan

[1] https://lkml.org/lkml/2016/8/19/570
[2] https://www.spinics.net/lists/arm-kernel/msg522505.html

Stanimir Varbanov (8):
doc: DT: vidc: binding document for Qualcomm video driver
media: vidc: adding core part and helper functions
media: vidc: decoder: add video decoder files
media: vidc: encoder: add video encoder files
media: vidc: add Host Firmware Interface (HFI)
media: vidc: add Venus HFI files
media: vidc: add Makefiles and Kconfig files
media: vidc: enable building of the video codec driver

.../devicetree/bindings/media/qcom,vidc.txt | 61 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/qcom/Kconfig | 8 +
drivers/media/platform/qcom/Makefile | 6 +
drivers/media/platform/qcom/vidc/Makefile | 19 +
drivers/media/platform/qcom/vidc/core.c | 548 +++++++
drivers/media/platform/qcom/vidc/core.h | 196 +++
drivers/media/platform/qcom/vidc/helpers.c | 394 +++++
drivers/media/platform/qcom/vidc/helpers.h | 43 +
drivers/media/platform/qcom/vidc/hfi.c | 622 ++++++++
drivers/media/platform/qcom/vidc/hfi.h | 272 ++++
drivers/media/platform/qcom/vidc/hfi_cmds.c | 1261 ++++++++++++++++
drivers/media/platform/qcom/vidc/hfi_cmds.h | 338 +++++
drivers/media/platform/qcom/vidc/hfi_helper.h | 1143 +++++++++++++++
drivers/media/platform/qcom/vidc/hfi_msgs.c | 1072 ++++++++++++++
drivers/media/platform/qcom/vidc/hfi_msgs.h | 298 ++++
drivers/media/platform/qcom/vidc/hfi_venus.c | 1539 ++++++++++++++++++++
drivers/media/platform/qcom/vidc/hfi_venus.h | 25 +
drivers/media/platform/qcom/vidc/hfi_venus_io.h | 98 ++
drivers/media/platform/qcom/vidc/int_bufs.c | 325 +++++
drivers/media/platform/qcom/vidc/int_bufs.h | 23 +
drivers/media/platform/qcom/vidc/load.c | 104 ++
drivers/media/platform/qcom/vidc/load.h | 22 +
drivers/media/platform/qcom/vidc/mem.c | 64 +
drivers/media/platform/qcom/vidc/mem.h | 32 +
drivers/media/platform/qcom/vidc/resources.c | 46 +
drivers/media/platform/qcom/vidc/resources.h | 46 +
drivers/media/platform/qcom/vidc/vdec.c | 1100 ++++++++++++++
drivers/media/platform/qcom/vidc/vdec.h | 27 +
drivers/media/platform/qcom/vidc/vdec_ctrls.c | 200 +++
drivers/media/platform/qcom/vidc/vdec_ctrls.h | 21 +
drivers/media/platform/qcom/vidc/venc.c | 1261 ++++++++++++++++
drivers/media/platform/qcom/vidc/venc.h | 27 +
drivers/media/platform/qcom/vidc/venc_ctrls.c | 396 +++++
drivers/media/platform/qcom/vidc/venc_ctrls.h | 23 +
36 files changed, 11662 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,vidc.txt
create mode 100644 drivers/media/platform/qcom/Kconfig
create mode 100644 drivers/media/platform/qcom/Makefile
create mode 100644 drivers/media/platform/qcom/vidc/Makefile
create mode 100644 drivers/media/platform/qcom/vidc/core.c
create mode 100644 drivers/media/platform/qcom/vidc/core.h
create mode 100644 drivers/media/platform/qcom/vidc/helpers.c
create mode 100644 drivers/media/platform/qcom/vidc/helpers.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_cmds.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_cmds.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_helper.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_msgs.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_msgs.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus_io.h
create mode 100644 drivers/media/platform/qcom/vidc/int_bufs.c
create mode 100644 drivers/media/platform/qcom/vidc/int_bufs.h
create mode 100644 drivers/media/platform/qcom/vidc/load.c
create mode 100644 drivers/media/platform/qcom/vidc/load.h
create mode 100644 drivers/media/platform/qcom/vidc/mem.c
create mode 100644 drivers/media/platform/qcom/vidc/mem.h
create mode 100644 drivers/media/platform/qcom/vidc/resources.c
create mode 100644 drivers/media/platform/qcom/vidc/resources.h
create mode 100644 drivers/media/platform/qcom/vidc/vdec.c
create mode 100644 drivers/media/platform/qcom/vidc/vdec.h
create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.h
create mode 100644 drivers/media/platform/qcom/vidc/venc.c
create mode 100644 drivers/media/platform/qcom/vidc/venc.h
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.h

--
2.7.4

Stanimir Varbanov

unread,
Aug 22, 2016, 9:20:07 AM8/22/16
to
This adds encoder part of the driver plus encoder controls.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/venc.c | 1261 +++++++++++++++++++++++++
drivers/media/platform/qcom/vidc/venc.h | 27 +
drivers/media/platform/qcom/vidc/venc_ctrls.c | 396 ++++++++
drivers/media/platform/qcom/vidc/venc_ctrls.h | 23 +
4 files changed, 1707 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/venc.c
create mode 100644 drivers/media/platform/qcom/vidc/venc.h
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.h

diff --git a/drivers/media/platform/qcom/vidc/venc.c b/drivers/media/platform/qcom/vidc/venc.c
new file mode 100644
index 000000000000..bc44f419089e
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc.c
@@ -0,0 +1,1261 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <media/videobuf2-dma-sg.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ctrls.h>
+
+#include "core.h"
+#include "helpers.h"
+#include "load.h"
+#include "venc_ctrls.h"
+
+#define NUM_B_FRAMES_MAX 4
+
+static u32 get_framesize_nv12(int plane, u32 height, u32 width)
+{
+ u32 y_stride, uv_stride, y_plane;
+ u32 y_sclines, uv_sclines, uv_plane;
+ u32 size;
+
+ y_stride = ALIGN(width, 128);
+ uv_stride = ALIGN(width, 128);
+ y_sclines = ALIGN(height, 32);
+ uv_sclines = ALIGN(((height + 1) >> 1), 16);
+
+ y_plane = y_stride * y_sclines;
+ uv_plane = uv_stride * uv_sclines + SZ_4K;
+ size = y_plane + uv_plane + SZ_8K;
+ size = ALIGN(size, SZ_4K);
+
+ return size;
+}
+
+static u32 get_framesize_compressed(u32 height, u32 width)
+{
+ u32 sz = ALIGN(height, 32) * ALIGN(width, 32) * 3 / 2;
+
+ return ALIGN(sz, SZ_4K);
+}
+
+static const struct vidc_format venc_formats[] = {
+ {
+ .pixfmt = V4L2_PIX_FMT_NV12,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG4,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_H263,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_H264,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_VP8,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ },
+};
+
+static const struct vidc_format *find_format(u32 pixfmt, int type)
+{
+ const struct vidc_format *fmt = venc_formats;
+ unsigned int size = ARRAY_SIZE(venc_formats);
+ unsigned int i;
+
+ for (i = 0; i < size; i++) {
+ if (fmt[i].pixfmt == pixfmt)
+ break;
+ }
+
+ if (i == size || fmt[i].type != type)
+ return NULL;
+
+ return &fmt[i];
+}
+
+static const struct vidc_format *find_format_by_index(int index, int type)
+{
+ const struct vidc_format *fmt = venc_formats;
+ unsigned int size = ARRAY_SIZE(venc_formats);
+ int i, k = 0;
+
+ if (index < 0 || index > size)
+ return NULL;
+
+ for (i = 0; i < size; i++) {
+ if (fmt[i].type != type)
+ continue;
+ if (k == index)
+ break;
+ k++;
+ }
+
+ if (i == size)
+ return NULL;
+
+ return &fmt[i];
+}
+
+static int venc_v4l2_to_hfi(int id, int value)
+{
+ switch (id) {
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ switch (value) {
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_0:
+ default:
+ return HFI_MPEG4_LEVEL_0;
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B:
+ return HFI_MPEG4_LEVEL_0b;
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_1:
+ return HFI_MPEG4_LEVEL_1;
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_2:
+ return HFI_MPEG4_LEVEL_2;
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_3:
+ return HFI_MPEG4_LEVEL_3;
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_4:
+ return HFI_MPEG4_LEVEL_4;
+ case V4L2_MPEG_VIDEO_MPEG4_LEVEL_5:
+ return HFI_MPEG4_LEVEL_5;
+ }
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ switch (value) {
+ case V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE:
+ default:
+ return HFI_MPEG4_PROFILE_SIMPLE;
+ case V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE:
+ return HFI_MPEG4_PROFILE_ADVANCEDSIMPLE;
+ }
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ switch (value) {
+ case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
+ return HFI_H264_PROFILE_BASELINE;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
+ return HFI_H264_PROFILE_CONSTRAINED_BASE;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
+ return HFI_H264_PROFILE_MAIN;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
+ default:
+ return HFI_H264_PROFILE_HIGH;
+ }
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ switch (value) {
+ case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
+ return HFI_H264_LEVEL_1;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_1B:
+ return HFI_H264_LEVEL_1b;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_1_1:
+ return HFI_H264_LEVEL_11;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_1_2:
+ return HFI_H264_LEVEL_12;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_1_3:
+ return HFI_H264_LEVEL_13;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
+ return HFI_H264_LEVEL_2;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_2_1:
+ return HFI_H264_LEVEL_21;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_2_2:
+ return HFI_H264_LEVEL_22;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_3_0:
+ return HFI_H264_LEVEL_3;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
+ return HFI_H264_LEVEL_31;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
+ return HFI_H264_LEVEL_32;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
+ return HFI_H264_LEVEL_4;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
+ return HFI_H264_LEVEL_41;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
+ return HFI_H264_LEVEL_42;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
+ default:
+ return HFI_H264_LEVEL_5;
+ case V4L2_MPEG_VIDEO_H264_LEVEL_5_1:
+ return HFI_H264_LEVEL_51;
+ }
+ case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
+ switch (value) {
+ case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC:
+ default:
+ return HFI_H264_ENTROPY_CAVLC;
+ case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC:
+ return HFI_H264_ENTROPY_CABAC;
+ }
+ case V4L2_CID_MPEG_VIDEO_VPX_PROFILE:
+ switch (value) {
+ case 0:
+ default:
+ return HFI_VPX_PROFILE_VERSION_0;
+ case 1:
+ return HFI_VPX_PROFILE_VERSION_1;
+ case 2:
+ return HFI_VPX_PROFILE_VERSION_2;
+ case 3:
+ return HFI_VPX_PROFILE_VERSION_3;
+ }
+ }
+
+ return 0;
+}
+
+static int venc_set_properties(struct vidc_inst *inst)
+{
+ struct venc_controls *ctr = &inst->controls.enc;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_intra_period intra_period;
+ struct hfi_profile_level pl;
+ struct hfi_framerate frate;
+ struct hfi_bitrate brate;
+ struct hfi_idr_period idrp;
+ u32 ptype, rate_control, bitrate, profile = 0, level = 0;
+ int ret;
+
+ ptype = HFI_PROPERTY_CONFIG_FRAME_RATE;
+ frate.buffer_type = HFI_BUFFER_OUTPUT;
+ frate.framerate = inst->fps * (1 << 16);
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &frate);
+ if (ret)
+ return ret;
+
+ if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) {
+ struct hfi_h264_vui_timing_info info;
+
+ ptype = HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO;
+ info.enable = 1;
+ info.fixed_framerate = 1;
+ info.time_scale = NSEC_PER_SEC;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &info);
+ if (ret)
+ return ret;
+ }
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD;
+ idrp.idr_period = ctr->gop_size;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &idrp);
+ if (ret)
+ return ret;
+
+ if (ctr->num_b_frames) {
+ u32 max_num_b_frames = NUM_B_FRAMES_MAX;
+
+ ptype = HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst,
+ ptype, &max_num_b_frames);
+ if (ret)
+ return ret;
+ }
+
+ /* intra_period = pframes + bframes + 1 */
+ if (!ctr->num_p_frames)
+ ctr->num_p_frames = 2 * 15 - 1,
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD;
+ intra_period.pframes = ctr->num_p_frames;
+ intra_period.bframes = ctr->num_b_frames;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &intra_period);
+ if (ret)
+ return ret;
+
+ if (ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR)
+ rate_control = HFI_RATE_CONTROL_VBR_CFR;
+ else
+ rate_control = HFI_RATE_CONTROL_CBR_CFR;
+
+ ptype = HFI_PROPERTY_PARAM_VENC_RATE_CONTROL;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &rate_control);
+ if (ret)
+ return ret;
+
+ if (!ctr->bitrate)
+ bitrate = 64000;
+ else
+ bitrate = ctr->bitrate;
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE;
+ brate.bitrate = bitrate;
+ brate.layer_id = 0;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &brate);
+ if (ret)
+ return ret;
+
+ if (!ctr->bitrate_peak)
+ bitrate *= 2;
+ else
+ bitrate = ctr->bitrate_peak;
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE;
+ brate.bitrate = bitrate;
+ brate.layer_id = 0;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &brate);
+ if (ret)
+ return ret;
+
+ if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) {
+ profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ ctr->profile);
+ level = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ ctr->level);
+ } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_VP8) {
+ profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_VPX_PROFILE,
+ ctr->profile);
+ level = 0;
+ } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_MPEG4) {
+ profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+ ctr->profile);
+ level = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+ ctr->level);
+ } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H263) {
+ profile = 0;
+ level = 0;
+ }
+
+ ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT;
+ pl.profile = profile;
+ pl.level = level;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &pl);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+venc_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+ strlcpy(cap->driver, VIDC_DRV_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, "video encoder", sizeof(cap->card));
+ strlcpy(cap->bus_info, "platform:vidc", sizeof(cap->bus_info));
+
+ cap->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int venc_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+ const struct vidc_format *fmt;
+
+ fmt = find_format_by_index(f->index, f->type);
+
+ memset(f->reserved, 0, sizeof(f->reserved));
+
+ if (!fmt)
+ return -EINVAL;
+
+ f->pixelformat = fmt->pixfmt;
+
+ return 0;
+}
+
+static const struct vidc_format *
+venc_try_fmt_common(struct vidc_inst *inst, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt;
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+ const struct vidc_format *fmt;
+ unsigned int p;
+
+ memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved));
+ memset(pixmp->reserved, 0, sizeof(pixmp->reserved));
+
+ fmt = find_format(pixmp->pixelformat, f->type);
+ if (!fmt) {
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ pixmp->pixelformat = V4L2_PIX_FMT_H264;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ pixmp->pixelformat = V4L2_PIX_FMT_NV12;
+ else
+ return NULL;
+ fmt = find_format(pixmp->pixelformat, f->type);
+ pixmp->width = 1280;
+ pixmp->height = 720;
+ }
+
+ pixmp->height = ALIGN(pixmp->height, 32);
+
+ pixmp->width = clamp(pixmp->width, hfi_inst->width.min,
+ hfi_inst->width.max);
+ pixmp->height = clamp(pixmp->height, hfi_inst->height.min,
+ hfi_inst->height.max);
+ if (pixmp->field == V4L2_FIELD_ANY)
+ pixmp->field = V4L2_FIELD_NONE;
+ pixmp->num_planes = fmt->num_planes;
+ pixmp->flags = 0;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ for (p = 0; p < pixmp->num_planes; p++) {
+ pfmt[p].sizeimage = get_framesize_nv12(p, pixmp->height,
+ pixmp->width);
+
+ pfmt[p].bytesperline = ALIGN(pixmp->width, 128);
+ }
+ } else {
+ pfmt[0].sizeimage = get_framesize_compressed(pixmp->height,
+ pixmp->width);
+ pfmt[0].bytesperline = 0;
+ }
+
+ return fmt;
+}
+
+static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ const struct vidc_format *fmt;
+
+ fmt = venc_try_fmt_common(inst, f);
+ if (!fmt)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_pix_format_mplane orig_pixmp;
+ const struct vidc_format *fmt;
+ struct v4l2_format format;
+ u32 pixfmt_out = 0, pixfmt_cap = 0;
+
+ orig_pixmp = *pixmp;
+
+ fmt = venc_try_fmt_common(inst, f);
+ if (!fmt)
+ return -EINVAL;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixfmt_out = pixmp->pixelformat;
+ pixfmt_cap = inst->fmt_cap->pixfmt;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixfmt_cap = pixmp->pixelformat;
+ pixfmt_out = inst->fmt_out->pixfmt;
+ }
+
+ memset(&format, 0, sizeof(format));
+
+ format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ format.fmt.pix_mp.pixelformat = pixfmt_out;
+ format.fmt.pix_mp.width = orig_pixmp.width;
+ format.fmt.pix_mp.height = orig_pixmp.height;
+ venc_try_fmt_common(inst, &format);
+ inst->out_width = format.fmt.pix_mp.width;
+ inst->out_height = format.fmt.pix_mp.height;
+ inst->colorspace = pixmp->colorspace;
+ inst->ycbcr_enc = pixmp->ycbcr_enc;
+ inst->quantization = pixmp->quantization;
+ inst->xfer_func = pixmp->xfer_func;
+
+ memset(&format, 0, sizeof(format));
+
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ format.fmt.pix_mp.pixelformat = pixfmt_cap;
+ format.fmt.pix_mp.width = orig_pixmp.width;
+ format.fmt.pix_mp.height = orig_pixmp.height;
+ venc_try_fmt_common(inst, &format);
+ inst->width = format.fmt.pix_mp.width;
+ inst->height = format.fmt.pix_mp.height;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ inst->fmt_out = fmt;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ inst->fmt_cap = fmt;
+
+ return 0;
+}
+
+static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct vidc_inst *inst = to_inst(file);
+ const struct vidc_format *fmt;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ fmt = inst->fmt_cap;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ fmt = inst->fmt_out;
+ else
+ return -EINVAL;
+
+ pixmp->pixelformat = fmt->pixfmt;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixmp->width = inst->width;
+ pixmp->height = inst->height;
+ pixmp->colorspace = inst->colorspace;
+ pixmp->ycbcr_enc = inst->ycbcr_enc;
+ pixmp->quantization = inst->quantization;
+ pixmp->xfer_func = inst->xfer_func;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixmp->width = inst->out_width;
+ pixmp->height = inst->out_height;
+ }
+
+ venc_try_fmt_common(inst, f);
+
+ return 0;
+}
+
+static int venc_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ s->r.top = 0;
+ s->r.left = 0;
+ s->r.width = inst->width;
+ s->r.height = inst->height;
+ break;
+ case V4L2_SEL_TGT_CROP:
+ s->r.top = 0;
+ s->r.left = 0;
+ s->r.width = inst->out_width;
+ s->r.height = inst->out_height;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int venc_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ return -EINVAL;
+}
+
+static int
+venc_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ if (!b->count)
+ vb2_core_queue_release(queue);
+
+ return vb2_reqbufs(queue, b);
+}
+
+static int venc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+ unsigned int p;
+ int ret;
+
+ ret = vb2_querybuf(queue, b);
+ if (ret)
+ return ret;
+
+ if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ b->memory == V4L2_MEMORY_MMAP) {
+ for (p = 0; p < b->length; p++)
+ b->m.planes[p].m.mem_offset += DST_QUEUE_OFF_BASE;
+ }
+
+ return 0;
+}
+
+static int venc_create_bufs(struct file *file, void *fh,
+ struct v4l2_create_buffers *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->format.type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_create_bufs(queue, b);
+}
+
+static int venc_prepare_buf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_prepare_buf(queue, b);
+}
+
+static int venc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_qbuf(queue, b);
+}
+
+static int
+venc_exportbuf(struct file *file, void *fh, struct v4l2_exportbuffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_expbuf(queue, b);
+}
+
+static int venc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_dqbuf(queue, b, file->f_flags & O_NONBLOCK);
+}
+
+static int venc_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_streamon(queue, type);
+}
+
+static int venc_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_streamoff(queue, type);
+}
+
+static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct v4l2_outputparm *out = &a->parm.output;
+ struct v4l2_fract *timeperframe = &out->timeperframe;
+ u64 us_per_frame, fps;
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ memset(out->reserved, 0, sizeof(out->reserved));
+
+ if (!timeperframe->denominator)
+ timeperframe->denominator = inst->timeperframe.denominator;
+ if (!timeperframe->numerator)
+ timeperframe->numerator = inst->timeperframe.numerator;
+
+ out->capability = V4L2_CAP_TIMEPERFRAME;
+
+ us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC;
+ do_div(us_per_frame, timeperframe->denominator);
+
+ if (!us_per_frame)
+ return -EINVAL;
+
+ fps = (u64)USEC_PER_SEC;
+ do_div(fps, us_per_frame);
+
+ inst->timeperframe = *timeperframe;
+ inst->fps = fps;
+
+ return 0;
+}
+
+static int venc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ a->parm.output.capability |= V4L2_CAP_TIMEPERFRAME;
+ a->parm.output.timeperframe = inst->timeperframe;
+
+ return 0;
+}
+
+static int venc_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct hfi_inst *inst = to_hfi_inst(file);
+ const struct vidc_format *fmt;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+
+ fmt = find_format(fsize->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (!fmt) {
+ fmt = find_format(fsize->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+ if (!fmt)
+ return -EINVAL;
+ }
+
+ if (fsize->index)
+ return -EINVAL;
+
+ fsize->stepwise.min_width = inst->width.min;
+ fsize->stepwise.max_width = inst->width.max;
+ fsize->stepwise.step_width = inst->width.step_size;
+ fsize->stepwise.min_height = inst->height.min;
+ fsize->stepwise.max_height = inst->height.max;
+ fsize->stepwise.step_height = inst->height.step_size;
+
+ return 0;
+}
+
+static int venc_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *fival)
+{
+ struct hfi_inst *inst = to_hfi_inst(file);
+ const struct vidc_format *fmt;
+
+ fival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+
+ fmt = find_format(fival->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (!fmt) {
+ fmt = find_format(fival->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+ if (!fmt)
+ return -EINVAL;
+ }
+
+ if (fival->index)
+ return -EINVAL;
+
+ if (!fival->width || !fival->height)
+ return -EINVAL;
+
+ if (fival->width > inst->width.max ||
+ fival->width < inst->width.min ||
+ fival->height > inst->height.max ||
+ fival->height < inst->height.min)
+ return -EINVAL;
+
+ fival->stepwise.min.numerator = 1;
+ fival->stepwise.min.denominator = inst->framerate.max;
+ fival->stepwise.max.numerator = 1;
+ fival->stepwise.max.denominator = inst->framerate.min;
+ fival->stepwise.step.numerator = 1;
+ fival->stepwise.step.denominator = inst->framerate.max;
+
+ return 0;
+}
+
+static int venc_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_EOS:
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_src_change_event_subscribe(fh, sub);
+ case V4L2_EVENT_CTRL:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_ioctl_ops venc_ioctl_ops = {
+ .vidioc_querycap = venc_querycap,
+ .vidioc_enum_fmt_vid_cap_mplane = venc_enum_fmt,
+ .vidioc_enum_fmt_vid_out_mplane = venc_enum_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = venc_s_fmt,
+ .vidioc_s_fmt_vid_out_mplane = venc_s_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = venc_g_fmt,
+ .vidioc_g_fmt_vid_out_mplane = venc_g_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = venc_try_fmt,
+ .vidioc_try_fmt_vid_out_mplane = venc_try_fmt,
+ .vidioc_g_selection = venc_g_selection,
+ .vidioc_s_selection = venc_s_selection,
+ .vidioc_reqbufs = venc_reqbufs,
+ .vidioc_querybuf = venc_querybuf,
+ .vidioc_create_bufs = venc_create_bufs,
+ .vidioc_prepare_buf = venc_prepare_buf,
+ .vidioc_qbuf = venc_qbuf,
+ .vidioc_expbuf = venc_exportbuf,
+ .vidioc_dqbuf = venc_dqbuf,
+ .vidioc_streamon = venc_streamon,
+ .vidioc_streamoff = venc_streamoff,
+ .vidioc_s_parm = venc_s_parm,
+ .vidioc_g_parm = venc_g_parm,
+ .vidioc_enum_framesizes = venc_enum_framesizes,
+ .vidioc_enum_frameintervals = venc_enum_frameintervals,
+ .vidioc_subscribe_event = venc_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int venc_init_session(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ u32 pixfmt = inst->fmt_cap->pixfmt;
+ struct hfi_framesize fs;
+ u32 ptype;
+ int ret;
+
+ ret = vidc_hfi_session_init(hfi, inst->hfi_inst, pixfmt,
+ VIDC_SESSION_TYPE_ENC);
+ if (ret)
+ return ret;
+
+ ptype = HFI_PROPERTY_PARAM_FRAME_SIZE;
+ fs.buffer_type = HFI_BUFFER_INPUT;
+ fs.width = inst->out_width;
+ fs.height = inst->out_height;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fs);
+ if (ret)
+ goto err;
+
+ fs.buffer_type = HFI_BUFFER_OUTPUT;
+ fs.width = inst->width;
+ fs.height = inst->height;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fs);
+ if (ret)
+ goto err;
+
+ pixfmt = inst->fmt_out->pixfmt;
+
+ ret = vidc_set_color_format(inst, HFI_BUFFER_INPUT, pixfmt);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+ return ret;
+}
+
+static int venc_cap_num_buffers(struct vidc_inst *inst,
+ struct hfi_buffer_requirements *bufreq)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct device *dev = inst->core->dev;
+ int ret, ret2;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = venc_init_session(inst);
+ if (ret)
+ goto put_sync;
+
+ ret = vidc_buf_descs(inst, HFI_BUFFER_OUTPUT, bufreq);
+
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+
+put_sync:
+ ret2 = pm_runtime_put_sync(dev);
+
+ return ret ? ret : ret2;
+}
+
+static int venc_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_buffer_requirements bufreq;
+ unsigned int p;
+ int ret = 0;
+
+ switch (q->type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ *num_planes = inst->fmt_out->num_planes;
+ *num_buffers = clamp_val(*num_buffers, 5, VIDEO_MAX_FRAME);
+ inst->num_input_bufs = *num_buffers;
+
+ for (p = 0; p < *num_planes; ++p) {
+ sizes[p] = get_framesize_nv12(p, inst->height,
+ inst->width);
+ alloc_devs[p] = inst->core->dev;
+ }
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ *num_planes = inst->fmt_cap->num_planes;
+
+ ret = venc_cap_num_buffers(inst, &bufreq);
+ if (ret)
+ break;
+
+ *num_buffers = max(*num_buffers, bufreq.count_actual);
+ inst->num_output_bufs = *num_buffers;
+
+ sizes[0] = get_framesize_compressed(inst->height, inst->width);
+ alloc_devs[0] = inst->core->dev;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int venc_check_configuration(struct vidc_inst *inst)
+{
+ struct hfi_buffer_requirements bufreq;
+ int ret;
+
+ ret = vidc_buf_descs(inst, HFI_BUFFER_OUTPUT, &bufreq);
+ if (ret)
+ return ret;
+
+ if (inst->num_output_bufs < bufreq.count_actual ||
+ inst->num_output_bufs < bufreq.count_min)
+ return -EINVAL;
+
+ ret = vidc_buf_descs(inst, HFI_BUFFER_INPUT, &bufreq);
+ if (ret)
+ return ret;
+
+ if (inst->num_input_bufs < bufreq.count_actual ||
+ inst->num_input_bufs < bufreq.count_min)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int venc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct device *dev = inst->core->dev;
+ struct hfi_buffer_count_actual buf_count;
+ struct vb2_queue *queue;
+ u32 ptype;
+ int ret;
+
+ switch (q->type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ queue = &inst->bufq_cap;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ queue = &inst->bufq_out;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!vb2_is_streaming(queue))
+ return 0;
+
+ inst->sequence = 0;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = venc_init_session(inst);
+ if (ret)
+ goto put_sync;
+
+ ret = venc_set_properties(inst);
+ if (ret)
+ goto deinit_sess;
+
+ ret = venc_check_configuration(inst);
+ if (ret)
+ goto deinit_sess;
+
+ ptype = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL;
+ buf_count.type = HFI_BUFFER_OUTPUT;
+ buf_count.count_actual = inst->num_output_bufs;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &buf_count);
+ if (ret)
+ goto deinit_sess;
+
+ buf_count.type = HFI_BUFFER_INPUT;
+ buf_count.count_actual = inst->num_input_bufs;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &buf_count);
+ if (ret)
+ goto deinit_sess;
+
+ ret = vidc_vb2_start_streaming(inst);
+ if (ret)
+ goto deinit_sess;
+
+ return 0;
+
+deinit_sess:
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+put_sync:
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static const struct vb2_ops venc_vb2_ops = {
+ .queue_setup = venc_queue_setup,
+ .buf_init = vidc_vb2_buf_init,
+ .buf_prepare = vidc_vb2_buf_prepare,
+ .start_streaming = venc_start_streaming,
+ .stop_streaming = vidc_vb2_stop_streaming,
+ .buf_queue = vidc_vb2_buf_queue,
+};
+
+static int venc_empty_buf_done(struct hfi_inst *hfi_inst, u32 addr,
+ u32 bytesused, u32 data_offset, u32 flags)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct vb2_v4l2_buffer *vbuf;
+ enum vb2_buffer_state state;
+ struct vb2_buffer *vb;
+
+ vbuf = vidc_vb2_find_buf(inst, addr);
+ if (!vbuf)
+ return -EINVAL;
+
+ vb = &vbuf->vb2_buf;
+ vb->planes[0].bytesused = bytesused;
+ vb->planes[0].data_offset = data_offset;
+ vbuf->flags = flags;
+ state = VB2_BUF_STATE_DONE;
+
+ if (data_offset > vb->planes[0].length ||
+ bytesused > vb->planes[0].length)
+ state = VB2_BUF_STATE_ERROR;
+
+ vb2_buffer_done(vb, state);
+
+ return 0;
+}
+
+static int venc_fill_buf_done(struct hfi_inst *hfi_inst, u32 addr,
+ u32 bytesused, u32 data_offset, u32 flags,
+ struct timeval *timestamp)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct vb2_v4l2_buffer *vbuf;
+ enum vb2_buffer_state state;
+ struct vb2_buffer *vb;
+
+ vbuf = vidc_vb2_find_buf(inst, addr);
+ if (!vbuf)
+ return -EINVAL;
+
+ vb = &vbuf->vb2_buf;
+ vb->planes[0].bytesused = bytesused;
+ vb->planes[0].data_offset = data_offset;
+ vb->timestamp = timeval_to_ns(timestamp);
+ vbuf->flags = flags;
+ vbuf->sequence = inst->sequence++;
+ state = VB2_BUF_STATE_DONE;
+
+ if (data_offset > vb->planes[0].length ||
+ bytesused > vb->planes[0].length)
+ state = VB2_BUF_STATE_ERROR;
+
+ vb2_buffer_done(vb, state);
+
+ return 0;
+}
+
+static int venc_event_notify(struct hfi_inst *hfi_inst, u32 event,
+ struct hfi_event_data *data)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct device *dev = inst->core->dev;
+
+ switch (event) {
+ case EVT_SESSION_ERROR:
+ if (hfi_inst) {
+ mutex_lock(&hfi_inst->lock);
+ inst->hfi_inst->state = INST_INVALID;
+ mutex_unlock(&hfi_inst->lock);
+ }
+ dev_err(dev, "enc: event session error (inst:%p)\n", hfi_inst);
+ break;
+ case EVT_SYS_EVENT_CHANGE:
+ switch (data->event_type) {
+ case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES:
+ dev_dbg(dev, "event sufficient resources\n");
+ break;
+ case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES:
+ inst->reconfig_height = data->height;
+ inst->reconfig_width = data->width;
+ inst->in_reconfig = true;
+ dev_dbg(dev, "event not sufficient resources\n");
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hfi_inst_ops venc_hfi_ops = {
+ .empty_buf_done = venc_empty_buf_done,
+ .fill_buf_done = venc_fill_buf_done,
+ .event_notify = venc_event_notify,
+};
+
+static void venc_inst_init(struct vidc_inst *inst)
+{
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+
+ inst->fmt_cap = &venc_formats[2];
+ inst->fmt_out = &venc_formats[0];
+ inst->width = 1280;
+ inst->height = ALIGN(720, 32);
+ inst->fps = 15;
+ inst->timeperframe.numerator = 1;
+ inst->timeperframe.denominator = 15;
+
+ hfi_inst->width.min = 64;
+ hfi_inst->width.max = 1920;
+ hfi_inst->width.step_size = 1;
+ hfi_inst->height.min = 64;
+ hfi_inst->height.max = ALIGN(1080, 32);
+ hfi_inst->height.step_size = 1;
+ hfi_inst->framerate.min = 1;
+ hfi_inst->framerate.max = 30;
+ hfi_inst->framerate.step_size = 1;
+ hfi_inst->mbs_per_frame.min = 16;
+ hfi_inst->mbs_per_frame.max = 8160;
+}
+
+int venc_init(struct vidc_core *core, struct video_device *enc)
+{
+ int ret;
+
+ enc->release = video_device_release_empty;
+ enc->fops = &vidc_fops;
+ enc->ioctl_ops = &venc_ioctl_ops;
+ enc->vfl_dir = VFL_DIR_M2M;
+ enc->v4l2_dev = &core->v4l2_dev;
+
+ ret = video_register_device(enc, VFL_TYPE_GRABBER, -1);
+ if (ret)
+ return ret;
+
+ video_set_drvdata(enc, core);
+
+ return 0;
+}
+
+void venc_deinit(struct vidc_core *core, struct video_device *enc)
+{
+ video_unregister_device(enc);
+}
+
+int venc_open(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vb2_queue *q;
+ int ret;
+
+ ret = venc_ctrl_init(inst);
+ if (ret)
+ return ret;
+
+ inst->hfi_inst = vidc_hfi_session_create(hfi, &venc_hfi_ops, inst);
+ if (IS_ERR(inst->hfi_inst)) {
+ ret = PTR_ERR(inst->hfi_inst);
+ goto err_ctrl_deinit;
+ }
+
+ venc_inst_init(inst);
+
+ q = &inst->bufq_cap;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->ops = &venc_vb2_ops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->drv_priv = inst;
+ q->buf_struct_size = sizeof(struct vidc_buffer);
+ q->allow_zero_bytesused = 1;
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_session_destroy;
+
+ q = &inst->bufq_out;
+ q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->ops = &venc_vb2_ops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->drv_priv = inst;
+ q->buf_struct_size = sizeof(struct vidc_buffer);
+ q->allow_zero_bytesused = 1;
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_cap_queue_release;
+
+ return 0;
+
+err_cap_queue_release:
+ vb2_queue_release(&inst->bufq_cap);
+err_session_destroy:
+ vidc_hfi_session_destroy(hfi, inst->hfi_inst);
+ inst->hfi_inst = NULL;
+err_ctrl_deinit:
+ venc_ctrl_deinit(inst);
+ return ret;
+}
+
+void venc_close(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+
+ vb2_queue_release(&inst->bufq_out);
+ vb2_queue_release(&inst->bufq_cap);
+ venc_ctrl_deinit(inst);
+ vidc_hfi_session_destroy(hfi, inst->hfi_inst);
+}
diff --git a/drivers/media/platform/qcom/vidc/venc.h b/drivers/media/platform/qcom/vidc/venc.h
new file mode 100644
index 000000000000..ef839b59def4
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_VENC_H__
+#define __VIDC_VENC_H__
+
+struct vidc_core;
+struct video_device;
+struct vidc_inst;
+
+int venc_init(struct vidc_core *core, struct video_device *enc);
+void venc_deinit(struct vidc_core *core, struct video_device *enc);
+int venc_open(struct vidc_inst *inst);
+void venc_close(struct vidc_inst *inst);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/venc_ctrls.c b/drivers/media/platform/qcom/vidc/venc_ctrls.c
new file mode 100644
index 000000000000..61331f95d54a
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc_ctrls.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/types.h>
+#include <media/v4l2-ctrls.h>
+
+#include "core.h"
+
+#define BITRATE_MIN 32000
+#define BITRATE_MAX 160000000
+#define BITRATE_DEFAULT 1000000
+#define BITRATE_DEFAULT_PEAK (BITRATE_DEFAULT * 2)
+#define BITRATE_STEP 100
+#define SLICE_BYTE_SIZE_MAX 1024
+#define SLICE_BYTE_SIZE_MIN 1024
+#define SLICE_MB_SIZE_MAX 300
+#define INTRA_REFRESH_MBS_MAX 300
+#define AT_SLICE_BOUNDARY \
+ V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY
+static struct vidc_ctrl venc_ctrls[] = {
+ {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
+ .max = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR,
+ .def = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
+ .menu_skip_mask = ~((1 << V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) |
+ (1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = BITRATE_MIN,
+ .max = BITRATE_MAX,
+ .def = BITRATE_DEFAULT,
+ .step = BITRATE_STEP,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = BITRATE_MIN,
+ .max = BITRATE_MAX,
+ .def = BITRATE_DEFAULT_PEAK,
+ .step = BITRATE_STEP,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC,
+ .max = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC,
+ .def = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .max = V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY,
+ .def = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) |
+ (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)
+ ),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ .max = V4L2_MPEG_VIDEO_MPEG4_LEVEL_5,
+ .def = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .max = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH,
+ .def = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)
+ ),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+ .max = V4L2_MPEG_VIDEO_H264_LEVEL_5_1,
+ .def = V4L2_MPEG_VIDEO_H264_LEVEL_5_0,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = 3,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 26,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 28,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 30,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 1,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 51,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE,
+ .max = V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES,
+ .def = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = SLICE_BYTE_SIZE_MIN,
+ .max = SLICE_BYTE_SIZE_MAX,
+ .def = SLICE_BYTE_SIZE_MIN,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = SLICE_MB_SIZE_MAX,
+ .def = 1,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = -6,
+ .max = 6,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = -6,
+ .max = 6,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED,
+ .max = AT_SLICE_BOUNDARY,
+ .def = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_HEADER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE,
+ .max = V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+ .def = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE,
+ .menu_skip_mask =
+ 1 << V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = INTRA_REFRESH_MBS_MAX,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .min = 0,
+ .max = 1,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED,
+ .max = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED,
+ .def = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 16) - 1,
+ .def = 12,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 16) - 1,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .min = 0,
+ .max = 1,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 128,
+ .def = 1,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 128,
+ .def = 128,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_B_FRAMES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = INT_MAX,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 16) - 1,
+ .step = 1,
+ .def = 0,
+ },
+};
+
+#define NUM_CTRLS ARRAY_SIZE(venc_ctrls)
+
+static int venc_op_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vidc_inst *inst = ctrl_to_inst(ctrl);
+ struct venc_controls *ctr = &inst->controls.enc;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
+ ctr->bitrate_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE:
+ ctr->bitrate = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK:
+ ctr->bitrate_peak = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
+ ctr->h264_entropy_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_VPX_PROFILE:
+ ctr->profile = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ ctr->level = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
+ ctr->h264_i_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
+ ctr->h264_p_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP:
+ ctr->h264_b_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_MIN_QP:
+ ctr->h264_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_MAX_QP:
+ ctr->h264_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE:
+ ctr->multi_slice_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES:
+ ctr->multi_slice_max_bytes = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB:
+ ctr->multi_slice_max_mb = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA:
+ ctr->h264_loop_filter_alpha = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA:
+ ctr->h264_loop_filter_beta = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE:
+ ctr->h264_loop_filter_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEADER_MODE:
+ ctr->header_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB:
+ break;
+ case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+ ctr->gop_size = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
+ ctr->h264_i_period = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE:
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC:
+ case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE:
+ case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM:
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP:
+ ctr->vp8_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP:
+ ctr->vp8_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_B_FRAMES:
+ ctr->num_b_frames = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops venc_ctrl_ops = {
+ .s_ctrl = venc_op_s_ctrl,
+};
+
+int venc_ctrl_init(struct vidc_inst *inst)
+{
+ unsigned int i;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, NUM_CTRLS);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < NUM_CTRLS; i++) {
+ struct v4l2_ctrl *ctrl;
+
+ if (venc_ctrls[i].type == V4L2_CTRL_TYPE_MENU) {
+ ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler,
+ &venc_ctrl_ops, venc_ctrls[i].id,
+ venc_ctrls[i].max,
+ venc_ctrls[i].menu_skip_mask,
+ venc_ctrls[i].def);
+ } else {
+ ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler,
+ &venc_ctrl_ops, venc_ctrls[i].id,
+ venc_ctrls[i].min,
+ venc_ctrls[i].max,
+ venc_ctrls[i].step,
+ venc_ctrls[i].def);
+ }
+
+ ret = inst->ctrl_handler.error;
+ if (ret) {
+ v4l2_ctrl_handler_free(&inst->ctrl_handler);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void venc_ctrl_deinit(struct vidc_inst *inst)
+{
+ v4l2_ctrl_handler_free(&inst->ctrl_handler);
+}
diff --git a/drivers/media/platform/qcom/vidc/venc_ctrls.h b/drivers/media/platform/qcom/vidc/venc_ctrls.h
new file mode 100644
index 000000000000..4441f550f57d
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc_ctrls.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_VENC_CTRLS_H__
+#define __VIDC_VENC_CTRLS_H__
+
+struct vidc_inst;
+
+int venc_ctrl_init(struct vidc_inst *inst);
+void venc_ctrl_deinit(struct vidc_inst *inst);
+
+#endif
--
2.7.4

Stanimir Varbanov

unread,
Aug 22, 2016, 9:20:08 AM8/22/16
to
Here is the implementation of Venus video accelerator low-level
functionality. It contanins code which setup the registers and
startup uthe processor, allocate and manipulates with the shared
memory used for sending commands and receiving messages.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/hfi_venus.c | 1539 +++++++++++++++++++++++
drivers/media/platform/qcom/vidc/hfi_venus.h | 25 +
drivers/media/platform/qcom/vidc/hfi_venus_io.h | 98 ++
3 files changed, 1662 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus_io.h

diff --git a/drivers/media/platform/qcom/vidc/hfi_venus.c b/drivers/media/platform/qcom/vidc/hfi_venus.c
new file mode 100644
index 000000000000..1193fa136711
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/hfi_venus.c
@@ -0,0 +1,1539 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/qcom_scm.h>
+#include <linux/slab.h>
+
+#include "hfi_cmds.h"
+#include "hfi_msgs.h"
+#include "mem.h"
+#include "hfi_venus.h"
+#include "hfi_venus_io.h"
+
+#define HFI_MASK_QHDR_TX_TYPE 0xff000000
+#define HFI_MASK_QHDR_RX_TYPE 0x00ff0000
+#define HFI_MASK_QHDR_PRI_TYPE 0x0000ff00
+#define HFI_MASK_QHDR_ID_TYPE 0x000000ff
+
+#define HFI_HOST_TO_CTRL_CMD_Q 0
+#define HFI_CTRL_TO_HOST_MSG_Q 1
+#define HFI_CTRL_TO_HOST_DBG_Q 2
+#define HFI_MASK_QHDR_STATUS 0x000000ff
+
+#define IFACEQ_NUM 3
+#define IFACEQ_CMD_IDX 0
+#define IFACEQ_MSG_IDX 1
+#define IFACEQ_DBG_IDX 2
+#define IFACEQ_MAX_BUF_COUNT 50
+#define IFACEQ_MAX_PARALLEL_CLNTS 16
+#define IFACEQ_DFLT_QHDR 0x01010000
+
+#define POLL_INTERVAL_US 50
+
+#define IFACEQ_MAX_PKT_SIZE 1024
+#define IFACEQ_MED_PKT_SIZE 768
+#define IFACEQ_MIN_PKT_SIZE 8
+#define IFACEQ_VAR_SMALL_PKT_SIZE 100
+#define IFACEQ_VAR_LARGE_PKT_SIZE 512
+#define IFACEQ_VAR_HUGE_PKT_SIZE (1024 * 12)
+
+enum tzbsp_video_state {
+ TZBSP_VIDEO_STATE_SUSPEND = 0,
+ TZBSP_VIDEO_STATE_RESUME
+};
+
+struct hfi_queue_table_header {
+ u32 version;
+ u32 size;
+ u32 qhdr0_offset;
+ u32 qhdr_size;
+ u32 num_q;
+ u32 num_active_q;
+};
+
+struct hfi_queue_header {
+ u32 status;
+ u32 start_addr;
+ u32 type;
+ u32 q_size;
+ u32 pkt_size;
+ u32 pkt_drop_cnt;
+ u32 rx_wm;
+ u32 tx_wm;
+ u32 rx_req;
+ u32 tx_req;
+ u32 rx_irq_status;
+ u32 tx_irq_status;
+ u32 read_idx;
+ u32 write_idx;
+};
+
+#define IFACEQ_TABLE_SIZE \
+ (sizeof(struct hfi_queue_table_header) + \
+ sizeof(struct hfi_queue_header) * IFACEQ_NUM)
+
+#define IFACEQ_QUEUE_SIZE (IFACEQ_MAX_PKT_SIZE * \
+ IFACEQ_MAX_BUF_COUNT * IFACEQ_MAX_PARALLEL_CLNTS)
+
+#define IFACEQ_GET_QHDR_START_ADDR(ptr, i) \
+ (void *)(((ptr) + sizeof(struct hfi_queue_table_header)) + \
+ ((i) * sizeof(struct hfi_queue_header)))
+
+#define QDSS_SIZE SZ_4K
+#define SFR_SIZE SZ_4K
+#define QUEUE_SIZE \
+ (IFACEQ_TABLE_SIZE + (IFACEQ_QUEUE_SIZE * IFACEQ_NUM))
+
+#define ALIGNED_QDSS_SIZE ALIGN(QDSS_SIZE, SZ_4K)
+#define ALIGNED_SFR_SIZE ALIGN(SFR_SIZE, SZ_4K)
+#define ALIGNED_QUEUE_SIZE ALIGN(QUEUE_SIZE, SZ_4K)
+#define SHARED_QSIZE ALIGN(ALIGNED_SFR_SIZE + ALIGNED_QUEUE_SIZE + \
+ ALIGNED_QDSS_SIZE, SZ_1M)
+
+struct mem_desc {
+ u32 da; /* device address */
+ void *kva; /* kernel virtual address */
+ u32 size;
+ struct vidc_mem *mem;
+};
+
+struct iface_queue {
+ struct hfi_queue_header *qhdr;
+ struct mem_desc qmem;
+};
+
+enum venus_state {
+ VENUS_STATE_DEINIT = 1,
+ VENUS_STATE_INIT,
+};
+
+struct venus_hfi_device {
+ struct device *dev;
+ void __iomem *base;
+ u32 irq_status;
+ u32 last_packet_type;
+ bool power_enabled;
+ bool suspended;
+ struct completion pwr_collapse_prep;
+ struct completion release_resource;
+ struct mutex lock;
+ struct mem_desc ifaceq_table;
+ struct mem_desc sfr;
+ struct iface_queue queues[IFACEQ_NUM];
+ const struct vidc_resources *res;
+ enum venus_state state;
+ const struct hfi_packetization_ops *pkt_ops;
+ enum hfi_packetization_type packetization_type;
+ u8 pkt_buf[IFACEQ_VAR_HUGE_PKT_SIZE];
+ u8 dbg_buf[IFACEQ_VAR_HUGE_PKT_SIZE];
+};
+
+#define VENUS_FW_DEBUG_MSG_LOW 0x1
+#define VENUS_FW_DEBUG_MSG_MEDIUM 0x2
+#define VENUS_FW_DEBUG_MSG_HIGH 0x4
+#define VENUS_FW_DEBUG_MSG_ERROR 0x8
+#define VENUS_FW_DEBUG_MSG_FATAL 0x10
+
+static bool venus_pkt_debug;
+static int venus_fw_debug = VENUS_FW_DEBUG_MSG_ERROR | VENUS_FW_DEBUG_MSG_FATAL;
+static bool venus_sys_idle_indicator;
+static bool venus_fw_low_power_mode = true;
+static int venus_hw_rsp_timeout = 1000;
+static bool venus_fw_coverage;
+
+static void venus_set_state(struct venus_hfi_device *hdev,
+ enum venus_state state)
+{
+ mutex_lock(&hdev->lock);
+ hdev->state = state;
+ mutex_unlock(&hdev->lock);
+}
+
+static bool venus_is_valid_state(struct venus_hfi_device *hdev)
+{
+ return hdev->state != VENUS_STATE_DEINIT;
+}
+
+static void venus_dump_packet(struct venus_hfi_device *hdev, u8 *packet)
+{
+ struct device *dev = hdev->dev;
+ u32 c = 0, pkt_size = *(u32 *)packet;
+ const int row_size = 32;
+ /*
+ * row must contain enough for 0xdeadbaad * 8 to be converted into
+ * "de ad ba ab " * 8 + '\0'
+ */
+ char row[3 * row_size];
+
+ if (!venus_pkt_debug)
+ return;
+
+ for (c = 0; c * row_size < pkt_size; ++c) {
+ int bytes_to_read = ((c + 1) * row_size > pkt_size) ?
+ pkt_size % row_size : row_size;
+ hex_dump_to_buffer(packet + c * row_size, bytes_to_read,
+ row_size, 4, row, sizeof(row), false);
+ dev_dbg(dev, "%s\n", row);
+ }
+}
+
+static int venus_write_queue(struct venus_hfi_device *hdev,
+ struct iface_queue *queue,
+ void *packet, u32 *rx_req)
+{
+ struct hfi_queue_header *qhdr;
+ u32 dwords, new_wr_idx;
+ u32 empty_space, rd_idx, wr_idx, qsize;
+ u32 *wr_ptr;
+
+ if (!queue->qmem.kva)
+ return -EINVAL;
+
+ qhdr = queue->qhdr;
+ if (!qhdr)
+ return -EINVAL;
+
+ venus_dump_packet(hdev, packet);
+
+ dwords = (*(u32 *)packet) >> 2;
+ if (!dwords)
+ return -EINVAL;
+
+ rd_idx = qhdr->read_idx;
+ wr_idx = qhdr->write_idx;
+ qsize = qhdr->q_size;
+ /* ensure rd/wr indices's are read from memory */
+ rmb();
+
+ if (wr_idx >= rd_idx)
+ empty_space = qsize - (wr_idx - rd_idx);
+ else
+ empty_space = rd_idx - wr_idx;
+
+ if (empty_space <= dwords) {
+ qhdr->tx_req = 1;
+ /* ensure tx_req is updated in memory */
+ wmb();
+ return -ENOSPC;
+ }
+
+ qhdr->tx_req = 0;
+ /* ensure tx_req is updated in memory */
+ wmb();
+
+ new_wr_idx = wr_idx + dwords;
+ wr_ptr = (u32 *)(queue->qmem.kva + (wr_idx << 2));
+ if (new_wr_idx < qsize) {
+ memcpy(wr_ptr, packet, dwords << 2);
+ } else {
+ size_t len;
+
+ new_wr_idx -= qsize;
+ len = (dwords - new_wr_idx) << 2;
+ memcpy(wr_ptr, packet, len);
+ memcpy(queue->qmem.kva, packet + len, new_wr_idx << 2);
+ }
+
+ /* make sure packet is written before updating the write index */
+ wmb();
+
+ qhdr->write_idx = new_wr_idx;
+ *rx_req = qhdr->rx_req ? 1 : 0;
+
+ /* make sure write index is updated before an interupt is raised */
+ mb();
+
+ return 0;
+}
+
+static int venus_read_queue(struct venus_hfi_device *hdev,
+ struct iface_queue *queue, void *pkt, u32 *tx_req)
+{
+ struct hfi_queue_header *qhdr;
+ u32 dwords, new_rd_idx;
+ u32 rd_idx, wr_idx, type, qsize;
+ u32 *rd_ptr;
+ u32 recv_request = 0;
+ int ret = 0;
+
+ if (!queue->qmem.kva)
+ return -EINVAL;
+
+ qhdr = queue->qhdr;
+ if (!qhdr)
+ return -EINVAL;
+
+ type = qhdr->type;
+ rd_idx = qhdr->read_idx;
+ wr_idx = qhdr->write_idx;
+ qsize = qhdr->q_size;
+
+ /* make sure data is valid before using it */
+ rmb();
+
+ /*
+ * Do not set receive request for debug queue, if set, Venus generates
+ * interrupt for debug messages even when there is no response message
+ * available. In general debug queue will not become full as it is being
+ * emptied out for every interrupt from Venus. Venus will anyway
+ * generates interrupt if it is full.
+ */
+ if (type & HFI_CTRL_TO_HOST_MSG_Q)
+ recv_request = 1;
+
+ if (rd_idx == wr_idx) {
+ qhdr->rx_req = recv_request;
+ *tx_req = 0;
+ /* update rx_req field in memory */
+ wmb();
+ return -ENODATA;
+ }
+
+ rd_ptr = (u32 *)(queue->qmem.kva + (rd_idx << 2));
+ dwords = *rd_ptr >> 2;
+ if (!dwords)
+ return -EINVAL;
+
+ new_rd_idx = rd_idx + dwords;
+ if (((dwords << 2) <= IFACEQ_VAR_HUGE_PKT_SIZE) && rd_idx <= qsize) {
+ if (new_rd_idx < qsize) {
+ memcpy(pkt, rd_ptr, dwords << 2);
+ } else {
+ size_t len;
+
+ new_rd_idx -= qsize;
+ len = (dwords - new_rd_idx) << 2;
+ memcpy(pkt, rd_ptr, len);
+ memcpy(pkt + len, queue->qmem.kva, new_rd_idx << 2);
+ }
+ } else {
+ /* bad packet received, dropping */
+ new_rd_idx = qhdr->write_idx;
+ ret = -EBADMSG;
+ }
+
+ /* ensure the packet is read before updating read index */
+ rmb();
+
+ qhdr->read_idx = new_rd_idx;
+ /* ensure updating read index */
+ wmb();
+
+ rd_idx = qhdr->read_idx;
+ wr_idx = qhdr->write_idx;
+ /* ensure rd/wr indices are read from memory */
+ rmb();
+
+ if (rd_idx != wr_idx)
+ qhdr->rx_req = 0;
+ else
+ qhdr->rx_req = recv_request;
+
+ *tx_req = qhdr->tx_req ? 1 : 0;
+
+ /* ensure rx_req is stored to memory and tx_req is loaded from memory */
+ mb();
+
+ venus_dump_packet(hdev, pkt);
+
+ return ret;
+}
+
+static int venus_alloc(struct venus_hfi_device *hdev, struct mem_desc *desc,
+ u32 size)
+{
+ struct vidc_mem *mem;
+
+ mem = mem_alloc(hdev->dev, size, 1);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ desc->size = mem->size;
+ desc->mem = mem;
+ desc->kva = mem->kvaddr;
+ desc->da = mem->da;
+
+ return 0;
+}
+
+static void venus_free(struct vidc_mem *mem)
+{
+ mem_free(mem);
+}
+
+static void venus_writel(struct venus_hfi_device *hdev, u32 reg, u32 value)
+{
+ writel(value, hdev->base + reg);
+}
+
+static u32 venus_readl(struct venus_hfi_device *hdev, u32 reg)
+{
+ return readl(hdev->base + reg);
+}
+
+static void venus_set_registers(struct venus_hfi_device *hdev)
+{
+ const struct reg_val *tbl = hdev->res->reg_tbl;
+ unsigned int count = hdev->res->reg_tbl_size;
+ int i;
+
+ for (i = 0; i < count; i++)
+ venus_writel(hdev, tbl[i].reg, tbl[i].value);
+}
+
+static void venus_soft_int(struct venus_hfi_device *hdev)
+{
+ venus_writel(hdev, CPU_IC_SOFTINT, BIT(CPU_IC_SOFTINT_H2A_SHIFT));
+}
+
+static int venus_iface_cmdq_write_nolock(struct venus_hfi_device *hdev,
+ void *pkt)
+{
+ struct device *dev = hdev->dev;
+ struct hfi_pkt_hdr *cmd_packet;
+ struct iface_queue *queue;
+ u32 rx_req;
+ int ret;
+
+ WARN(!mutex_is_locked(&hdev->lock),
+ "cmd queue write lock must be acquired");
+
+ if (!venus_is_valid_state(hdev)) {
+ dev_err(dev, "%s: fw not in init state\n", __func__);
+ return -EINVAL;
+ }
+
+ cmd_packet = (struct hfi_pkt_hdr *)pkt;
+ hdev->last_packet_type = cmd_packet->pkt_type;
+
+ queue = &hdev->queues[IFACEQ_CMD_IDX];
+
+ ret = venus_write_queue(hdev, queue, pkt, &rx_req);
+ if (ret) {
+ dev_err(dev, "write to iface cmd queue failed (%d)\n", ret);
+ return ret;
+ }
+
+ if (rx_req)
+ venus_soft_int(hdev);
+
+ return 0;
+}
+
+static int venus_iface_cmdq_write(struct venus_hfi_device *hdev, void *pkt)
+{
+ int ret;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_iface_cmdq_write_nolock(hdev, pkt);
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static int venus_hfi_core_set_resource(struct venus_hfi_device *hdev,
+ u32 id, u32 size, u32 addr, void *cookie)
+{
+ struct hfi_sys_set_resource_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ if (id == VIDC_RESOURCE_NONE)
+ return 0;
+
+ pkt = (struct hfi_sys_set_resource_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, sys_set_resource, pkt, id, size, addr,
+ cookie);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_tzbsp_set_video_state(enum tzbsp_video_state state)
+{
+ return qcom_scm_video_set_state(state, 0);
+}
+
+static int venus_reset_core(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ u32 ctrl_status = 0, count = 0;
+ int max_tries = 100, ret = 0;
+
+ venus_writel(hdev, VIDC_CTRL_INIT, BIT(VIDC_CTRL_INIT_CTRL_SHIFT));
+ venus_writel(hdev, WRAPPER_INTR_MASK, WRAPPER_INTR_MASK_A2HVCODEC_MASK);
+ venus_writel(hdev, CPU_CS_SCIACMDARG3, 1);
+
+ while (!ctrl_status && count < max_tries) {
+ ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0);
+ if ((ctrl_status & 0xfe) == 0x4) {
+ dev_err(dev, "invalid setting for UC_REGION\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ usleep_range(500, 1000);
+ count++;
+ }
+
+ if (count >= max_tries)
+ ret = -ETIMEDOUT;
+
+ return ret;
+}
+
+static u32 venus_hwversion(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ u32 ver = venus_readl(hdev, WRAPPER_HW_VERSION);
+ u32 major, minor, step;
+
+ major = ver & WRAPPER_HW_VERSION_MAJOR_VERSION_MASK;
+ major = major >> WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT;
+ minor = ver & WRAPPER_HW_VERSION_MINOR_VERSION_MASK;
+ minor = minor >> WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT;
+ step = ver & WRAPPER_HW_VERSION_STEP_VERSION_MASK;
+
+ dev_dbg(dev, "venus hw version %d.%d.%d\n", major, minor, step);
+
+ return major;
+}
+
+static int venus_run(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ int ret;
+
+ /*
+ * Re-program all of the registers that get reset as a result of
+ * regulator_disable() and _enable()
+ */
+ venus_set_registers(hdev);
+
+ venus_writel(hdev, UC_REGION_ADDR, hdev->ifaceq_table.da);
+ venus_writel(hdev, UC_REGION_SIZE, SHARED_QSIZE);
+ venus_writel(hdev, CPU_CS_SCIACMDARG2, hdev->ifaceq_table.da);
+ venus_writel(hdev, CPU_CS_SCIACMDARG1, 0x01);
+ if (hdev->sfr.da)
+ venus_writel(hdev, SFR_ADDR, hdev->sfr.da);
+
+ ret = venus_reset_core(hdev);
+ if (ret) {
+ dev_err(dev, "failed to reset venus core\n");
+ return ret;
+ }
+
+ venus_hwversion(hdev);
+
+ return 0;
+}
+
+static int venus_halt_axi(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ void __iomem *base = hdev->base;
+ u32 val;
+ int ret;
+
+ /* Halt AXI and AXI IMEM VBIF Access */
+ val = venus_readl(hdev, VBIF_AXI_HALT_CTRL0);
+ val |= VBIF_AXI_HALT_CTRL0_HALT_REQ;
+ venus_writel(hdev, VBIF_AXI_HALT_CTRL0, val);
+
+ /* Request for AXI bus port halt */
+ ret = readl_poll_timeout(base + VBIF_AXI_HALT_CTRL1, val,
+ val & VBIF_AXI_HALT_CTRL1_HALT_ACK,
+ POLL_INTERVAL_US,
+ VBIF_AXI_HALT_ACK_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "AXI bus port halt timeout\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int venus_power_off(struct venus_hfi_device *hdev)
+{
+ int ret;
+
+ if (!hdev->power_enabled)
+ return 0;
+
+ ret = venus_halt_axi(hdev);
+ if (ret)
+ return ret;
+
+ ret = venus_tzbsp_set_video_state(TZBSP_VIDEO_STATE_SUSPEND);
+ if (ret)
+ return ret;
+
+ hdev->power_enabled = false;
+
+ return 0;
+}
+
+static int venus_power_on(struct venus_hfi_device *hdev)
+{
+ int ret;
+
+ if (hdev->power_enabled)
+ return 0;
+
+ ret = venus_tzbsp_set_video_state(TZBSP_VIDEO_STATE_RESUME);
+ if (ret)
+ goto err;
+
+ ret = venus_run(hdev);
+ if (ret)
+ goto err_suspend;
+
+ hdev->power_enabled = true;
+
+ return 0;
+
+err_suspend:
+ venus_tzbsp_set_video_state(TZBSP_VIDEO_STATE_SUSPEND);
+err:
+ hdev->power_enabled = false;
+ return ret;
+}
+
+static int venus_iface_msgq_read_nolock(struct venus_hfi_device *hdev,
+ void *pkt)
+{
+ struct iface_queue *queue;
+ u32 tx_req;
+ int ret;
+
+ if (!venus_is_valid_state(hdev))
+ return -EINVAL;
+
+ queue = &hdev->queues[IFACEQ_MSG_IDX];
+
+ ret = venus_read_queue(hdev, queue, pkt, &tx_req);
+ if (ret)
+ return ret;
+
+ if (tx_req)
+ venus_soft_int(hdev);
+
+ return 0;
+}
+
+static int venus_iface_msgq_read(struct venus_hfi_device *hdev, void *pkt)
+{
+ int ret;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_iface_msgq_read_nolock(hdev, pkt);
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static int venus_iface_dbgq_read_nolock(struct venus_hfi_device *hdev,
+ void *pkt)
+{
+ struct iface_queue *queue;
+ u32 tx_req;
+ int ret;
+
+ ret = venus_is_valid_state(hdev);
+ if (!ret)
+ return -EINVAL;
+
+ queue = &hdev->queues[IFACEQ_DBG_IDX];
+
+ ret = venus_read_queue(hdev, queue, pkt, &tx_req);
+ if (ret)
+ return ret;
+
+ if (tx_req)
+ venus_soft_int(hdev);
+
+ return 0;
+}
+
+static int venus_iface_dbgq_read(struct venus_hfi_device *hdev, void *pkt)
+{
+ int ret;
+
+ if (!pkt)
+ return -EINVAL;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_iface_dbgq_read_nolock(hdev, pkt);
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static void venus_set_qhdr_defaults(struct hfi_queue_header *qhdr)
+{
+ qhdr->status = 1;
+ qhdr->type = IFACEQ_DFLT_QHDR;
+ qhdr->q_size = IFACEQ_QUEUE_SIZE / 4;
+ qhdr->pkt_size = 0;
+ qhdr->rx_wm = 1;
+ qhdr->tx_wm = 1;
+ qhdr->rx_req = 1;
+ qhdr->tx_req = 0;
+ qhdr->rx_irq_status = 0;
+ qhdr->tx_irq_status = 0;
+ qhdr->read_idx = 0;
+ qhdr->write_idx = 0;
+}
+
+static void venus_interface_queues_release(struct venus_hfi_device *hdev)
+{
+ mutex_lock(&hdev->lock);
+
+ venus_free(hdev->ifaceq_table.mem);
+ venus_free(hdev->sfr.mem);
+
+ memset(hdev->queues, 0, sizeof(hdev->queues));
+ memset(&hdev->ifaceq_table, 0, sizeof(hdev->ifaceq_table));
+ memset(&hdev->sfr, 0, sizeof(hdev->sfr));
+
+ mutex_unlock(&hdev->lock);
+}
+
+static int venus_interface_queues_init(struct venus_hfi_device *hdev)
+{
+ struct hfi_queue_table_header *tbl_hdr;
+ struct iface_queue *queue;
+ struct hfi_sfr *sfr;
+ struct mem_desc desc = {0};
+ unsigned int offset;
+ unsigned int i;
+ int ret;
+
+ ret = venus_alloc(hdev, &desc, ALIGNED_QUEUE_SIZE);
+ if (ret)
+ return ret;
+
+ hdev->ifaceq_table.kva = desc.kva;
+ hdev->ifaceq_table.da = desc.da;
+ hdev->ifaceq_table.size = IFACEQ_TABLE_SIZE;
+ hdev->ifaceq_table.mem = desc.mem;
+ offset = hdev->ifaceq_table.size;
+
+ for (i = 0; i < IFACEQ_NUM; i++) {
+ queue = &hdev->queues[i];
+ queue->qmem.da = desc.da + offset;
+ queue->qmem.kva = desc.kva + offset;
+ queue->qmem.size = IFACEQ_QUEUE_SIZE;
+ queue->qmem.mem = NULL;
+ offset += queue->qmem.size;
+ queue->qhdr =
+ IFACEQ_GET_QHDR_START_ADDR(hdev->ifaceq_table.kva, i);
+
+ venus_set_qhdr_defaults(queue->qhdr);
+
+ queue->qhdr->start_addr = queue->qmem.da;
+
+ if (i == IFACEQ_CMD_IDX)
+ queue->qhdr->type |= HFI_HOST_TO_CTRL_CMD_Q;
+ else if (i == IFACEQ_MSG_IDX)
+ queue->qhdr->type |= HFI_CTRL_TO_HOST_MSG_Q;
+ else if (i == IFACEQ_DBG_IDX)
+ queue->qhdr->type |= HFI_CTRL_TO_HOST_DBG_Q;
+ }
+
+ tbl_hdr = hdev->ifaceq_table.kva;
+ tbl_hdr->version = 0;
+ tbl_hdr->size = IFACEQ_TABLE_SIZE;
+ tbl_hdr->qhdr0_offset = sizeof(struct hfi_queue_table_header);
+ tbl_hdr->qhdr_size = sizeof(struct hfi_queue_header);
+ tbl_hdr->num_q = IFACEQ_NUM;
+ tbl_hdr->num_active_q = IFACEQ_NUM;
+
+ /*
+ * Set receive request to zero on debug queue as there is no
+ * need of interrupt from video hardware for debug messages
+ */
+ queue = &hdev->queues[IFACEQ_DBG_IDX];
+ queue->qhdr->rx_req = 0;
+
+ ret = venus_alloc(hdev, &desc, ALIGNED_SFR_SIZE);
+ if (ret) {
+ hdev->sfr.da = 0;
+ } else {
+ hdev->sfr.da = desc.da;
+ hdev->sfr.kva = desc.kva;
+ hdev->sfr.size = ALIGNED_SFR_SIZE;
+ hdev->sfr.mem = desc.mem;
+ sfr = hdev->sfr.kva;
+ sfr->buf_size = ALIGNED_SFR_SIZE;
+ }
+
+ /* ensure table and queue header structs are settled in memory */
+ wmb();
+
+ return 0;
+}
+
+static int venus_sys_set_debug(struct venus_hfi_device *hdev, u32 debug)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_debug_config, pkt, HFI_DEBUG_MODE_QUEUE,
+ debug);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_sys_set_coverage(struct venus_hfi_device *hdev, u32 mode)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_coverage_config, pkt, mode);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_sys_set_idle_message(struct venus_hfi_device *hdev,
+ bool enable)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ if (!enable)
+ return 0;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_idle_indicator, pkt, enable);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_sys_set_power_control(struct venus_hfi_device *hdev,
+ bool enable)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_power_control, pkt, enable);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_get_queue_size(struct venus_hfi_device *hdev,
+ unsigned int index)
+{
+ struct hfi_queue_header *qhdr;
+
+ if (index >= IFACEQ_NUM)
+ return -EINVAL;
+
+ qhdr = hdev->queues[index].qhdr;
+ if (!qhdr)
+ return -EINVAL;
+
+ return abs(qhdr->read_idx - qhdr->write_idx);
+}
+
+static int venus_sys_set_default_properties(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ int ret;
+
+ ret = venus_sys_set_debug(hdev, venus_fw_debug);
+ if (ret)
+ dev_warn(dev, "setting fw debug msg ON failed (%d)\n", ret);
+
+ ret = venus_sys_set_idle_message(hdev, venus_sys_idle_indicator);
+ if (ret)
+ dev_warn(dev, "setting idle response ON failed (%d)\n", ret);
+
+ ret = venus_sys_set_power_control(hdev, venus_fw_low_power_mode);
+ if (ret)
+ dev_warn(dev, "setting hw power collapse ON failed (%d)\n",
+ ret);
+
+ return ret;
+}
+
+static int venus_session_cmd(struct hfi_inst *inst, u32 pkt_type)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_pkt pkt;
+ int ret;
+
+ call_hfi_pkt_op(hdev, session_cmd, &pkt, pkt_type, inst);
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void venus_flush_debug_queue(struct venus_hfi_device *hdev)
+{
+ void *packet = hdev->dbg_buf;
+ struct device *dev = hdev->dev;
+
+ while (!venus_iface_dbgq_read(hdev, packet)) {
+ struct hfi_msg_sys_coverage_pkt *pkt = packet;
+
+ if (pkt->hdr.pkt_type != HFI_MSG_SYS_COV) {
+ struct hfi_msg_sys_debug_pkt *pkt = packet;
+
+ dev_dbg(dev, "%s", pkt->msg_data);
+ }
+ }
+}
+
+static int venus_prepare_power_collapse(struct venus_hfi_device *hdev)
+{
+ unsigned long timeout = msecs_to_jiffies(venus_hw_rsp_timeout);
+ struct hfi_sys_pc_prep_pkt pkt;
+ int ret;
+
+ init_completion(&hdev->pwr_collapse_prep);
+
+ call_hfi_pkt_op(hdev, sys_pc_prep, &pkt);
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&hdev->pwr_collapse_prep, timeout);
+ if (!ret) {
+ venus_flush_debug_queue(hdev);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int venus_are_queues_empty(struct venus_hfi_device *hdev)
+{
+ int ret1, ret2;
+
+ ret1 = venus_get_queue_size(hdev, IFACEQ_MSG_IDX);
+ if (ret1 < 0)
+ return ret1;
+
+ ret2 = venus_get_queue_size(hdev, IFACEQ_CMD_IDX);
+ if (ret2 < 0)
+ return ret2;
+
+ if (!ret1 && !ret2)
+ return 1;
+
+ return 0;
+}
+
+static void venus_sfr_print(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ struct hfi_sfr *sfr = hdev->sfr.kva;
+ void *p;
+
+ if (!sfr)
+ return;
+
+ p = memchr(sfr->data, '\0', sfr->buf_size);
+ /*
+ * SFR isn't guaranteed to be NULL terminated since SYS_ERROR indicates
+ * that Venus is in the process of crashing.
+ */
+ if (p == NULL)
+ sfr->data[sfr->buf_size - 1] = '\0';
+
+ dev_err(dev, "SFR message from FW: %s\n", sfr->data);
+}
+
+static void venus_process_msg_sys_error(struct venus_hfi_device *hdev,
+ void *packet)
+{
+ struct hfi_msg_event_notify_pkt *event_pkt = packet;
+
+ if (event_pkt->event_id != HFI_EVENT_SYS_ERROR)
+ return;
+
+ venus_set_state(hdev, VENUS_STATE_DEINIT);
+
+ /*
+ * Once SYS_ERROR received from HW, it is safe to halt the AXI.
+ * With SYS_ERROR, Venus FW may have crashed and HW might be
+ * active and causing unnecessary transactions. Hence it is
+ * safe to stop all AXI transactions from venus subsystem.
+ */
+ venus_halt_axi(hdev);
+ venus_sfr_print(hdev);
+}
+
+static irqreturn_t venus_isr_thread(int irq, struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ void *pkt = hdev->pkt_buf;
+ u32 msg_ret;
+
+ if (!hdev)
+ return IRQ_NONE;
+
+ if (hdev->irq_status & WRAPPER_INTR_STATUS_A2HWD_MASK) {
+ venus_sfr_print(hdev);
+ hfi_process_watchdog_timeout(hfi);
+ }
+
+ while (!venus_iface_msgq_read(hdev, pkt)) {
+ msg_ret = hfi_process_msg_packet(hfi, pkt);
+ switch (msg_ret) {
+ case HFI_MSG_EVENT_NOTIFY:
+ venus_process_msg_sys_error(hdev, pkt);
+ break;
+ case HFI_MSG_SYS_INIT:
+ venus_hfi_core_set_resource(hdev, hdev->res->vmem_id,
+ hdev->res->vmem_size,
+ hdev->res->vmem_addr,
+ hdev);
+ break;
+ case HFI_MSG_SYS_RELEASE_RESOURCE:
+ complete(&hdev->release_resource);
+ break;
+ case HFI_MSG_SYS_PC_PREP:
+ complete(&hdev->pwr_collapse_prep);
+ break;
+ default:
+ break;
+ }
+ }
+
+ venus_flush_debug_queue(hdev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t venus_isr(int irq, struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ u32 status;
+
+ if (!hdev)
+ return IRQ_NONE;
+
+ status = venus_readl(hdev, WRAPPER_INTR_STATUS);
+
+ if (status & WRAPPER_INTR_STATUS_A2H_MASK ||
+ status & WRAPPER_INTR_STATUS_A2HWD_MASK ||
+ status & CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK)
+ hdev->irq_status = status;
+
+ venus_writel(hdev, CPU_CS_A2HSOFTINTCLR, 1);
+ venus_writel(hdev, WRAPPER_INTR_CLEAR, status);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int venus_hfi_core_init(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct device *dev = hfi->dev;
+ struct hfi_sys_get_property_pkt version_pkt;
+ struct hfi_sys_init_pkt pkt;
+ int ret;
+
+ call_hfi_pkt_op(hdev, sys_init, &pkt, HFI_VIDEO_ARCH_OX);
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ return ret;
+
+ call_hfi_pkt_op(hdev, sys_image_version, &version_pkt);
+
+ ret = venus_iface_cmdq_write(hdev, &version_pkt);
+ if (ret)
+ dev_warn(dev, "failed to send image version pkt to fw\n");
+
+ venus_set_state(hdev, VENUS_STATE_INIT);
+
+ return 0;
+}
+
+static int venus_hfi_core_deinit(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+
+ venus_set_state(hdev, VENUS_STATE_DEINIT);
+ hdev->suspended = true;
+
+ return 0;
+}
+
+static int venus_hfi_core_ping(struct hfi_core *hfi, u32 cookie)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct hfi_sys_ping_pkt pkt;
+
+ call_hfi_pkt_op(hdev, sys_ping, &pkt, cookie);
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_core_trigger_ssr(struct hfi_core *hfi,
+ u32 trigger_type)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct hfi_sys_test_ssr_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, ssr_cmd, trigger_type, &pkt);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+int venus_hfi_session_init(struct hfi_core *hfi, struct hfi_inst *inst,
+ u32 session_type, u32 codec)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct hfi_session_init_pkt pkt;
+ int ret;
+
+ inst->priv = hdev;
+
+ ret = venus_sys_set_default_properties(hdev);
+ if (ret)
+ return ret;
+
+ ret = call_hfi_pkt_op(hdev, session_init, &pkt, inst, session_type,
+ codec);
+ if (ret)
+ goto err;
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ venus_flush_debug_queue(hdev);
+ return ret;
+}
+
+static int venus_hfi_session_end(struct hfi_inst *inst)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct device *dev = hdev->dev;
+
+ if (venus_fw_coverage) {
+ if (venus_sys_set_coverage(hdev, venus_fw_coverage))
+ dev_warn(dev, "fw coverage msg ON failed\n");
+ }
+
+ return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_END);
+}
+
+static int venus_hfi_session_abort(struct hfi_inst *inst)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+
+ venus_flush_debug_queue(hdev);
+
+ return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_ABORT);
+}
+
+static int venus_hfi_session_flush(struct hfi_inst *inst, u32 flush_mode)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_flush_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, session_flush, &pkt, inst, flush_mode);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_session_start(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_START);
+}
+
+static int venus_hfi_session_stop(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_STOP);
+}
+
+static int venus_hfi_session_etb(struct hfi_inst *inst,
+ struct hfi_frame_data *in_frame)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ u32 session_type = inst->session_type;
+ int ret;
+
+ if (session_type == VIDC_SESSION_TYPE_DEC) {
+ struct hfi_session_empty_buffer_compressed_pkt pkt;
+
+ ret = call_hfi_pkt_op(hdev, session_etb_decoder, &pkt, inst,
+ in_frame);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ } else if (session_type == VIDC_SESSION_TYPE_ENC) {
+ struct hfi_session_empty_buffer_uncompressed_plane0_pkt pkt;
+
+ ret = call_hfi_pkt_op(hdev, session_etb_encoder, &pkt, inst,
+ in_frame);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int venus_hfi_session_ftb(struct hfi_inst *inst,
+ struct hfi_frame_data *out_frame)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_fill_buffer_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, session_ftb, &pkt, inst, out_frame);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_session_set_buffers(struct hfi_inst *inst,
+ struct hfi_buffer_desc *bd)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_set_buffers_pkt *pkt;
+ u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE];
+ int ret;
+
+ if (bd->buffer_type == HFI_BUFFER_INPUT)
+ return 0;
+
+ pkt = (struct hfi_session_set_buffers_pkt *)packet;
+
+ ret = call_hfi_pkt_op(hdev, session_set_buffers, pkt, inst, bd);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_release_buffers(struct hfi_inst *inst,
+ struct hfi_buffer_desc *bd)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_release_buffer_pkt *pkt;
+ u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE];
+ int ret;
+
+ if (bd->buffer_type == HFI_BUFFER_INPUT)
+ return 0;
+
+ pkt = (struct hfi_session_release_buffer_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_release_buffers, pkt, inst, bd);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_load_res(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_LOAD_RESOURCES);
+}
+
+static int venus_hfi_session_release_res(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_RELEASE_RESOURCES);
+}
+
+static int venus_hfi_session_parse_seq_hdr(struct hfi_inst *inst,
+ u32 seq_hdr, u32 seq_hdr_len)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_parse_sequence_header_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_session_parse_sequence_header_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_parse_seq_header,
+ pkt, inst, seq_hdr, seq_hdr_len);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_hfi_session_get_seq_hdr(struct hfi_inst *inst,
+ u32 seq_hdr, u32 seq_hdr_len)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_get_sequence_header_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_session_get_sequence_header_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_get_seq_hdr, pkt, inst, seq_hdr,
+ seq_hdr_len);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_set_property(struct hfi_inst *inst, u32 ptype,
+ void *pdata)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_session_set_property_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_set_property, pkt, inst, ptype,
+ pdata);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_get_property(struct hfi_inst *inst, u32 ptype)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_get_property_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, session_get_property, &pkt, inst, ptype);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_resume(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ int ret = 0;
+
+ mutex_lock(&hdev->lock);
+
+ if (hdev->suspended == false)
+ goto unlock;
+
+ ret = venus_power_on(hdev);
+
+unlock:
+ if (!ret)
+ hdev->suspended = false;
+
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static int venus_hfi_suspend(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct device *dev = hfi->dev;
+ u32 ctrl_status;
+ int ret;
+
+ if (!hdev->power_enabled || hdev->suspended)
+ return 0;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_is_valid_state(hdev);
+ mutex_unlock(&hdev->lock);
+
+ if (!ret) {
+ dev_err(dev, "bad state, cannot suspend\n");
+ return -EINVAL;
+ }
+
+ ret = venus_prepare_power_collapse(hdev);
+ if (ret) {
+ dev_err(dev, "prepare for power collapse fail (%d)\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&hdev->lock);
+
+ if (hdev->last_packet_type != HFI_CMD_SYS_PC_PREP) {
+ mutex_unlock(&hdev->lock);
+ return -EINVAL;
+ }
+
+ ret = venus_are_queues_empty(hdev);
+ if (ret < 0 || !ret) {
+ mutex_unlock(&hdev->lock);
+ return -EINVAL;
+ }
+
+ ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0);
+ if (!(ctrl_status & CPU_CS_SCIACMDARG0_PC_READY)) {
+ mutex_unlock(&hdev->lock);
+ return -EINVAL;
+ }
+
+ ret = venus_power_off(hdev);
+ if (ret) {
+ mutex_unlock(&hdev->lock);
+ return ret;
+ }
+
+ hdev->suspended = true;
+
+ mutex_unlock(&hdev->lock);
+
+ return 0;
+}
+
+static const struct hfi_ops venus_hfi_ops = {
+ .core_init = venus_hfi_core_init,
+ .core_deinit = venus_hfi_core_deinit,
+ .core_ping = venus_hfi_core_ping,
+ .core_trigger_ssr = venus_hfi_core_trigger_ssr,
+
+ .session_init = venus_hfi_session_init,
+ .session_end = venus_hfi_session_end,
+ .session_abort = venus_hfi_session_abort,
+ .session_flush = venus_hfi_session_flush,
+ .session_start = venus_hfi_session_start,
+ .session_stop = venus_hfi_session_stop,
+ .session_etb = venus_hfi_session_etb,
+ .session_ftb = venus_hfi_session_ftb,
+ .session_set_buffers = venus_hfi_session_set_buffers,
+ .session_release_buffers = venus_hfi_session_release_buffers,
+ .session_load_res = venus_hfi_session_load_res,
+ .session_release_res = venus_hfi_session_release_res,
+ .session_parse_seq_hdr = venus_hfi_session_parse_seq_hdr,
+ .session_get_seq_hdr = venus_hfi_session_get_seq_hdr,
+ .session_set_property = venus_hfi_session_set_property,
+ .session_get_property = venus_hfi_session_get_property,
+
+ .resume = venus_hfi_resume,
+ .suspend = venus_hfi_suspend,
+
+ .isr = venus_isr,
+ .isr_thread = venus_isr_thread,
+};
+
+void venus_hfi_destroy(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+
+ venus_interface_queues_release(hdev);
+ mutex_destroy(&hdev->lock);
+ kfree(hdev);
+}
+
+int venus_hfi_create(struct hfi_core *hfi, const struct vidc_resources *res,
+ void __iomem *base)
+{
+ struct venus_hfi_device *hdev;
+ int ret;
+
+ hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
+ if (!hdev)
+ return -ENOMEM;
+
+ mutex_init(&hdev->lock);
+
+ hdev->res = res;
+ hdev->pkt_ops = hfi->pkt_ops;
+ hdev->packetization_type = HFI_PACKETIZATION_LEGACY;
+ hdev->base = base;
+ hdev->dev = hfi->dev;
+ hdev->suspended = true;
+
+ hfi->priv = hdev;
+ hfi->ops = &venus_hfi_ops;
+ hfi->core_caps = VIDC_ENC_ROTATION_CAPABILITY |
+ VIDC_ENC_SCALING_CAPABILITY |
+ VIDC_ENC_DEINTERLACE_CAPABILITY |
+ VIDC_DEC_MULTI_STREAM_CAPABILITY;
+
+ ret = venus_interface_queues_init(hdev);
+ if (ret)
+ goto err_kfree;
+
+ return 0;
+
+err_kfree:
+ kfree(hdev);
+ hfi->priv = NULL;
+ hfi->ops = NULL;
+ return ret;
+}
diff --git a/drivers/media/platform/qcom/vidc/hfi_venus.h b/drivers/media/platform/qcom/vidc/hfi_venus.h
new file mode 100644
index 000000000000..c2e6e4235a0b
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/hfi_venus.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VENUS_HFI_H__
+#define __VENUS_HFI_H__
+
+struct hfi_core;
+struct vidc_resources;
+
+void venus_hfi_destroy(struct hfi_core *hfi);
+int venus_hfi_create(struct hfi_core *hfi, const struct vidc_resources *res,
+ void __iomem *base);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/hfi_venus_io.h b/drivers/media/platform/qcom/vidc/hfi_venus_io.h
new file mode 100644
index 000000000000..7df09fb72011
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/hfi_venus_io.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VENUS_HFI_IO_H__
+#define __VENUS_HFI_IO_H__
+
+#define VBIF_BASE 0x80000
+
+#define VBIF_AXI_HALT_CTRL0 (VBIF_BASE + 0x208)
+#define VBIF_AXI_HALT_CTRL1 (VBIF_BASE + 0x20c)
+
+#define VBIF_AXI_HALT_CTRL0_HALT_REQ BIT(0)
+#define VBIF_AXI_HALT_CTRL1_HALT_ACK BIT(0)
+#define VBIF_AXI_HALT_ACK_TIMEOUT_US 500000
+
+#define CPU_BASE 0xc0000
+#define CPU_CS_BASE (CPU_BASE + 0x12000)
+#define CPU_IC_BASE (CPU_BASE + 0x1f000)
+
+#define CPU_CS_A2HSOFTINTCLR (CPU_CS_BASE + 0x1c)
+
+#define VIDC_CTRL_INIT (CPU_CS_BASE + 0x48)
+#define VIDC_CTRL_INIT_RESERVED_BITS31_1_MASK 0xfffffffe
+#define VIDC_CTRL_INIT_RESERVED_BITS31_1_SHIFT 1
+#define VIDC_CTRL_INIT_CTRL_MASK 0x1
+#define VIDC_CTRL_INIT_CTRL_SHIFT 0
+
+/* HFI control status */
+#define CPU_CS_SCIACMDARG0 (CPU_CS_BASE + 0x4c)
+#define CPU_CS_SCIACMDARG0_MASK 0xff
+#define CPU_CS_SCIACMDARG0_SHIFT 0x0
+#define CPU_CS_SCIACMDARG0_ERROR_STATUS_MASK 0xfe
+#define CPU_CS_SCIACMDARG0_ERROR_STATUS_SHIFT 0x1
+#define CPU_CS_SCIACMDARG0_INIT_STATUS_MASK 0x1
+#define CPU_CS_SCIACMDARG0_INIT_STATUS_SHIFT 0x0
+#define CPU_CS_SCIACMDARG0_PC_READY 0x100
+#define CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK 0x40000000
+
+/* HFI queue table info */
+#define CPU_CS_SCIACMDARG1 (CPU_CS_BASE + 0x50)
+
+/* HFI queue table address */
+#define CPU_CS_SCIACMDARG2 (CPU_CS_BASE + 0x54)
+
+/* vidc cpu */
+#define CPU_CS_SCIACMDARG3 (CPU_CS_BASE + 0x58)
+
+#define SFR_ADDR (CPU_CS_BASE + 0x5c)
+#define MMAP_ADDR (CPU_CS_BASE + 0x60)
+#define UC_REGION_ADDR (CPU_CS_BASE + 0x64)
+#define UC_REGION_SIZE (CPU_CS_BASE + 0x68)
+
+#define CPU_IC_SOFTINT (CPU_IC_BASE + 0x18)
+#define CPU_IC_SOFTINT_H2A_MASK 0x8000
+#define CPU_IC_SOFTINT_H2A_SHIFT 0xf
+
+/* vidc wrapper */
+#define WRAPPER_BASE 0x000e0000
+
+#define WRAPPER_HW_VERSION (WRAPPER_BASE + 0x00)
+#define WRAPPER_HW_VERSION_MAJOR_VERSION_MASK 0x78000000
+#define WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT 28
+#define WRAPPER_HW_VERSION_MINOR_VERSION_MASK 0xfff0000
+#define WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT 16
+#define WRAPPER_HW_VERSION_STEP_VERSION_MASK 0xffff
+
+#define WRAPPER_INTR_STATUS (WRAPPER_BASE + 0x0c)
+#define WRAPPER_INTR_STATUS_A2HWD_MASK 0x10
+#define WRAPPER_INTR_STATUS_A2HWD_SHIFT 0x4
+#define WRAPPER_INTR_STATUS_A2H_MASK 0x4
+#define WRAPPER_INTR_STATUS_A2H_SHIFT 0x2
+
+#define WRAPPER_INTR_MASK (WRAPPER_BASE + 0x10)
+#define WRAPPER_INTR_MASK_A2HWD_BASK 0x10
+#define WRAPPER_INTR_MASK_A2HWD_SHIFT 0x4
+#define WRAPPER_INTR_MASK_A2HVCODEC_MASK 0x8
+#define WRAPPER_INTR_MASK_A2HVCODEC_SHIFT 0x3
+#define WRAPPER_INTR_MASK_A2HCPU_MASK 0x4
+#define WRAPPER_INTR_MASK_A2HCPU_SHIFT 0x2
+
+#define WRAPPER_INTR_CLEAR (WRAPPER_BASE + 0x14)
+#define WRAPPER_INTR_CLEAR_A2HWD_MASK 0x10
+#define WRAPPER_INTR_CLEAR_A2HWD_SHIFT 0x4
+#define WRAPPER_INTR_CLEAR_A2H_MASK 0x4
+#define WRAPPER_INTR_CLEAR_A2H_SHIFT 0x2
+
+#endif
--
2.7.4

Stanimir Varbanov

unread,
Aug 22, 2016, 9:20:12 AM8/22/16
to
This adds core part of the vidc driver common helper functions
used by encoder and decoder specific files.

- core.c has implemented the platform dirver methods, file
operations and v4l2 registration.

- helpers.c has implemented common helper functions for
buffer management, vb2_ops and functions for format propagation.

- int_bufs.c implements functions for allocating and freeing
buffers for internal usage. The buffer parameters describing
internal buffers depends on current format, resolution and
codec.

- load.c consists functions for calculation of current load
of the hardware. Depending on the count of instances and
resolutions it selects the best clock rate for the video
core.

- mem.c has two functions for memory allocation, currently
those functions are used for internal buffers and to allocate
the shared memory for communication with firmware via HFI
(Host Firmware Interface) interface commands.

- resources.c exports a structure describing the details
specific to platform and SoC.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/core.c | 548 +++++++++++++++++++++++++++
drivers/media/platform/qcom/vidc/core.h | 196 ++++++++++
drivers/media/platform/qcom/vidc/helpers.c | 394 +++++++++++++++++++
drivers/media/platform/qcom/vidc/helpers.h | 43 +++
drivers/media/platform/qcom/vidc/int_bufs.c | 325 ++++++++++++++++
drivers/media/platform/qcom/vidc/int_bufs.h | 23 ++
drivers/media/platform/qcom/vidc/load.c | 104 +++++
drivers/media/platform/qcom/vidc/load.h | 22 ++
drivers/media/platform/qcom/vidc/mem.c | 64 ++++
drivers/media/platform/qcom/vidc/mem.h | 32 ++
drivers/media/platform/qcom/vidc/resources.c | 46 +++
drivers/media/platform/qcom/vidc/resources.h | 46 +++
12 files changed, 1843 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/core.c
create mode 100644 drivers/media/platform/qcom/vidc/core.h
create mode 100644 drivers/media/platform/qcom/vidc/helpers.c
create mode 100644 drivers/media/platform/qcom/vidc/helpers.h
create mode 100644 drivers/media/platform/qcom/vidc/int_bufs.c
create mode 100644 drivers/media/platform/qcom/vidc/int_bufs.h
create mode 100644 drivers/media/platform/qcom/vidc/load.c
create mode 100644 drivers/media/platform/qcom/vidc/load.h
create mode 100644 drivers/media/platform/qcom/vidc/mem.c
create mode 100644 drivers/media/platform/qcom/vidc/mem.h
create mode 100644 drivers/media/platform/qcom/vidc/resources.c
create mode 100644 drivers/media/platform/qcom/vidc/resources.h

diff --git a/drivers/media/platform/qcom/vidc/core.c b/drivers/media/platform/qcom/vidc/core.c
new file mode 100644
index 000000000000..e005be178fc0
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/core.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/ioctl.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/remoteproc.h>
+#include <linux/pm_runtime.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ioctl.h>
+
+#include "core.h"
+#include "resources.h"
+#include "vdec.h"
+#include "venc.h"
+
+static void vidc_add_inst(struct vidc_core *core, struct vidc_inst *inst)
+{
+ mutex_lock(&core->lock);
+ list_add_tail(&inst->list, &core->instances);
+ mutex_unlock(&core->lock);
+}
+
+static void vidc_del_inst(struct vidc_core *core, struct vidc_inst *inst)
+{
+ struct vidc_inst *pos, *n;
+
+ mutex_lock(&core->lock);
+ list_for_each_entry_safe(pos, n, &core->instances, list) {
+ if (pos == inst)
+ list_del(&inst->list);
+ }
+ mutex_unlock(&core->lock);
+}
+
+static int vidc_rproc_boot(struct vidc_core *core)
+{
+ int ret;
+
+ if (core->rproc_booted)
+ return 0;
+
+ ret = rproc_boot(core->rproc);
+ if (ret)
+ return ret;
+
+ core->rproc_booted = true;
+
+ return 0;
+}
+
+static void vidc_rproc_shutdown(struct vidc_core *core)
+{
+ if (!core->rproc_booted)
+ return;
+
+ rproc_shutdown(core->rproc);
+ core->rproc_booted = false;
+}
+
+struct vidc_sys_error {
+ struct vidc_core *core;
+ struct delayed_work work;
+};
+
+static void vidc_sys_error_handler(struct work_struct *work)
+{
+ struct vidc_sys_error *handler =
+ container_of(work, struct vidc_sys_error, work.work);
+ struct vidc_core *core = handler->core;
+ struct hfi_core *hfi = &core->hfi;
+ struct device *dev = core->dev;
+ int ret;
+
+ mutex_lock(&hfi->lock);
+ if (hfi->state != CORE_INVALID)
+ goto exit;
+
+ mutex_unlock(&hfi->lock);
+
+ ret = vidc_hfi_core_deinit(hfi);
+ if (ret)
+ dev_err(dev, "core: deinit failed (%d)\n", ret);
+
+ mutex_lock(&hfi->lock);
+
+ rproc_report_crash(core->rproc, RPROC_FATAL_ERROR);
+
+ vidc_rproc_shutdown(core);
+
+ ret = vidc_rproc_boot(core);
+ if (ret)
+ goto exit;
+
+ hfi->state = CORE_INIT;
+
+exit:
+ mutex_unlock(&hfi->lock);
+ kfree(handler);
+}
+
+static int vidc_event_notify(struct hfi_core *hfi, u32 event)
+{
+ struct vidc_sys_error *handler;
+ struct hfi_inst *inst;
+
+ switch (event) {
+ case EVT_SYS_WATCHDOG_TIMEOUT:
+ case EVT_SYS_ERROR:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&hfi->lock);
+
+ hfi->state = CORE_INVALID;
+
+ list_for_each_entry(inst, &hfi->instances, list) {
+ mutex_lock(&inst->lock);
+ inst->state = INST_INVALID;
+ mutex_unlock(&inst->lock);
+ }
+
+ mutex_unlock(&hfi->lock);
+
+ handler = kzalloc(sizeof(*handler), GFP_KERNEL);
+ if (!handler)
+ return -ENOMEM;
+
+ handler->core = container_of(hfi, struct vidc_core, hfi);
+ INIT_DELAYED_WORK(&handler->work, vidc_sys_error_handler);
+
+ /*
+ * Sleep for 5 sec to ensure venus has completed any
+ * pending cache operations. Without this sleep, we see
+ * device reset when firmware is unloaded after a sys
+ * error.
+ */
+ schedule_delayed_work(&handler->work, msecs_to_jiffies(5000));
+
+ return 0;
+}
+
+static const struct hfi_core_ops vidc_core_ops = {
+ .event_notify = vidc_event_notify,
+};
+
+static int vidc_open(struct file *file)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct vidc_core *core = video_drvdata(file);
+ struct vidc_inst *inst;
+ int ret = 0;
+
+ inst = kzalloc(sizeof(*inst), GFP_KERNEL);
+ if (!inst)
+ return -ENOMEM;
+
+ mutex_init(&inst->lock);
+
+ INIT_VIDC_LIST(&inst->scratchbufs);
+ INIT_VIDC_LIST(&inst->persistbufs);
+ INIT_VIDC_LIST(&inst->registeredbufs);
+
+ INIT_LIST_HEAD(&inst->bufqueue);
+ mutex_init(&inst->bufqueue_lock);
+
+ if (vdev == &core->vdev_dec)
+ inst->session_type = VIDC_SESSION_TYPE_DEC;
+ else
+ inst->session_type = VIDC_SESSION_TYPE_ENC;
+
+ inst->core = core;
+
+ if (inst->session_type == VIDC_SESSION_TYPE_DEC)
+ ret = vdec_open(inst);
+ else
+ ret = venc_open(inst);
+
+ if (ret)
+ goto err_free_inst;
+
+ if (inst->session_type == VIDC_SESSION_TYPE_DEC)
+ v4l2_fh_init(&inst->fh, &core->vdev_dec);
+ else
+ v4l2_fh_init(&inst->fh, &core->vdev_enc);
+
+ inst->fh.ctrl_handler = &inst->ctrl_handler;
+
+ v4l2_fh_add(&inst->fh);
+
+ file->private_data = &inst->fh;
+
+ vidc_add_inst(core, inst);
+
+ return 0;
+
+err_free_inst:
+ kfree(inst);
+ return ret;
+}
+
+static int vidc_close(struct file *file)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct vidc_core *core = inst->core;
+
+ if (inst->session_type == VIDC_SESSION_TYPE_DEC)
+ vdec_close(inst);
+ else
+ venc_close(inst);
+
+ vidc_del_inst(core, inst);
+
+ mutex_destroy(&inst->bufqueue_lock);
+ mutex_destroy(&inst->scratchbufs.lock);
+ mutex_destroy(&inst->persistbufs.lock);
+ mutex_destroy(&inst->registeredbufs.lock);
+
+ v4l2_fh_del(&inst->fh);
+ v4l2_fh_exit(&inst->fh);
+
+ kfree(inst);
+ return 0;
+}
+
+static unsigned int vidc_poll(struct file *file, struct poll_table_struct *pt)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct vb2_queue *outq = &inst->bufq_out;
+ struct vb2_queue *capq = &inst->bufq_cap;
+ unsigned int ret;
+
+ ret = vb2_poll(outq, file, pt);
+ ret |= vb2_poll(capq, file, pt);
+
+ return ret;
+}
+
+static int vidc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct vidc_inst *inst = to_inst(file);
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ int ret;
+
+ if (offset < DST_QUEUE_OFF_BASE) {
+ ret = vb2_mmap(&inst->bufq_out, vma);
+ } else {
+ vma->vm_pgoff -= DST_QUEUE_OFF_BASE >> PAGE_SHIFT;
+ ret = vb2_mmap(&inst->bufq_cap, vma);
+ }
+
+ return ret;
+}
+
+const struct v4l2_file_operations vidc_fops = {
+ .owner = THIS_MODULE,
+ .open = vidc_open,
+ .release = vidc_close,
+ .unlocked_ioctl = video_ioctl2,
+ .poll = vidc_poll,
+ .mmap = vidc_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+};
+
+static irqreturn_t vidc_isr_thread(int irq, void *dev_id)
+{
+ return vidc_hfi_isr_thread(irq, dev_id);
+}
+
+static irqreturn_t vidc_isr(int irq, void *dev)
+{
+ return vidc_hfi_isr(irq, dev);
+}
+
+static int vidc_clks_get(struct vidc_core *core, unsigned int clks_num,
+ const char * const *clks_id)
+{
+ struct device *dev = core->dev;
+ unsigned int i;
+
+ for (i = 0; i < clks_num; i++) {
+ core->clks[i] = devm_clk_get(dev, clks_id[i]);
+ if (IS_ERR(core->clks[i]))
+ return PTR_ERR(core->clks[i]);
+ }
+
+ return 0;
+}
+
+static int
+vidc_clks_enable(struct vidc_core *core, const struct vidc_resources *res)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < res->clks_num; i++) {
+ ret = clk_prepare_enable(core->clks[i]);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+err:
+ while (--i)
+ clk_disable_unprepare(core->clks[i]);
+
+ return ret;
+}
+
+static void
+vidc_clks_disable(struct vidc_core *core, const struct vidc_resources *res)
+{
+ unsigned int i;
+
+ for (i = 0; i < res->clks_num; i++)
+ clk_disable_unprepare(core->clks[i]);
+}
+
+static const struct of_device_id vidc_dt_match[] = {
+ { .compatible = "qcom,vidc-msm8916", .data = &msm8916_res, },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, vidc_dt_match);
+
+static int vidc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct vidc_core *core;
+ struct device_node *rproc;
+ struct resource *r;
+ int ret;
+
+ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return -ENOMEM;
+
+ core->dev = dev;
+ platform_set_drvdata(pdev, core);
+
+ rproc = of_parse_phandle(dev->of_node, "rproc", 0);
+ if (IS_ERR(rproc))
+ return PTR_ERR(rproc);
+
+ core->rproc = rproc_get_by_phandle(rproc->phandle);
+ if (IS_ERR(core->rproc))
+ return PTR_ERR(core->rproc);
+ else if (!core->rproc)
+ return -EPROBE_DEFER;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ core->base = devm_ioremap_resource(dev, r);
+ if (IS_ERR(core->base))
+ return PTR_ERR(core->base);
+
+ core->irq = platform_get_irq(pdev, 0);
+ if (core->irq < 0)
+ return core->irq;
+
+ core->res = of_device_get_match_data(dev);
+ if (!core->res)
+ return -ENODEV;
+
+ ret = vidc_clks_get(core, core->res->clks_num, core->res->clks);
+ if (ret)
+ return ret;
+
+ ret = dma_set_mask_and_coherent(dev, core->res->dma_mask);
+ if (ret)
+ return ret;
+
+ INIT_LIST_HEAD(&core->instances);
+ mutex_init(&core->lock);
+
+ ret = devm_request_threaded_irq(dev, core->irq, vidc_isr,
+ vidc_isr_thread,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+ "vidc", &core->hfi);
+ if (ret)
+ return ret;
+
+ core->hfi.core_ops = &vidc_core_ops;
+ core->hfi.dev = dev;
+
+ ret = vidc_hfi_create(&core->hfi, core->res, core->base);
+ if (ret)
+ return ret;
+
+ ret = vidc_clks_enable(core, core->res);
+ if (ret)
+ goto err_hfi_destroy;
+
+ ret = vidc_rproc_boot(core);
+ if (ret) {
+ vidc_clks_disable(core, core->res);
+ goto err_hfi_destroy;
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ goto err_runtime_disable;
+
+ ret = vidc_hfi_core_init(&core->hfi);
+ if (ret)
+ goto err_rproc_shutdown;
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret)
+ goto err_core_deinit;
+
+ vidc_clks_disable(core, core->res);
+
+ ret = v4l2_device_register(dev, &core->v4l2_dev);
+ if (ret)
+ goto err_core_deinit;
+
+ ret = vdec_init(core, &core->vdev_dec);
+ if (ret)
+ goto err_dev_unregister;
+
+ ret = venc_init(core, &core->vdev_enc);
+ if (ret)
+ goto err_vdec_deinit;
+
+ return 0;
+
+err_vdec_deinit:
+ vdec_deinit(core, &core->vdev_dec);
+err_dev_unregister:
+ v4l2_device_unregister(&core->v4l2_dev);
+err_core_deinit:
+ vidc_hfi_core_deinit(&core->hfi);
+err_rproc_shutdown:
+ vidc_rproc_shutdown(core);
+err_runtime_disable:
+ pm_runtime_set_suspended(dev);
+ pm_runtime_disable(dev);
+err_hfi_destroy:
+ vidc_hfi_destroy(&core->hfi);
+ return ret;
+}
+
+static int vidc_remove(struct platform_device *pdev)
+{
+ struct vidc_core *core = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = vidc_hfi_core_deinit(&core->hfi);
+ if (ret) {
+ pm_runtime_put_sync(&pdev->dev);
+ return ret;
+ }
+
+ vidc_rproc_shutdown(core);
+
+ ret = pm_runtime_put_sync(&pdev->dev);
+
+ vidc_hfi_destroy(&core->hfi);
+ vdec_deinit(core, &core->vdev_dec);
+ venc_deinit(core, &core->vdev_enc);
+ v4l2_device_unregister(&core->v4l2_dev);
+
+ pm_runtime_disable(core->dev);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int vidc_runtime_suspend(struct device *dev)
+{
+ struct vidc_core *core = dev_get_drvdata(dev);
+ int ret;
+
+ ret = vidc_hfi_core_suspend(&core->hfi);
+
+ vidc_clks_disable(core, core->res);
+
+ return ret;
+}
+
+static int vidc_runtime_resume(struct device *dev)
+{
+ struct vidc_core *core = dev_get_drvdata(dev);
+ int ret;
+
+ ret = vidc_clks_enable(core, core->res);
+ if (ret)
+ return ret;
+
+ return vidc_hfi_core_resume(&core->hfi);
+}
+
+static int vidc_pm_suspend(struct device *dev)
+{
+ return vidc_runtime_suspend(dev);
+}
+
+static int vidc_pm_resume(struct device *dev)
+{
+ return vidc_runtime_resume(dev);
+}
+
+static const struct dev_pm_ops vidc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(vidc_pm_suspend, vidc_pm_resume)
+ SET_RUNTIME_PM_OPS(vidc_runtime_suspend, vidc_runtime_resume, NULL)
+};
+
+static struct platform_driver qcom_vidc_driver = {
+ .probe = vidc_probe,
+ .remove = vidc_remove,
+ .driver = {
+ .name = "qcom-vidc",
+ .of_match_table = vidc_dt_match,
+ .pm = &vidc_pm_ops,
+ },
+};
+
+module_platform_driver(qcom_vidc_driver);
+
+MODULE_ALIAS("platform:qcom-vidc");
+MODULE_DESCRIPTION("Qualcomm video encoder and decoder driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/qcom/vidc/core.h b/drivers/media/platform/qcom/vidc/core.h
new file mode 100644
index 000000000000..5dc8e05f8c36
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/core.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#ifndef __VIDC_CORE_H_
+#define __VIDC_CORE_H_
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+
+#include "resources.h"
+#include "hfi.h"
+
+#define VIDC_DRV_NAME "vidc"
+
+struct vidc_list {
+ struct list_head list;
+ struct mutex lock;
+};
+
+struct vidc_format {
+ u32 pixfmt;
+ int num_planes;
+ u32 type;
+};
+
+struct vidc_core {
+ struct list_head list;
+ void __iomem *base;
+ int irq;
+ struct clk *clks[VIDC_CLKS_NUM_MAX];
+ struct mutex lock;
+ struct hfi_core hfi;
+ struct video_device vdev_dec;
+ struct video_device vdev_enc;
+ struct v4l2_device v4l2_dev;
+ struct list_head instances;
+ const struct vidc_resources *res;
+ struct rproc *rproc;
+ bool rproc_booted;
+ struct device *dev;
+};
+
+struct vdec_controls {
+ u32 post_loop_deb_mode;
+ u32 profile;
+ u32 level;
+};
+
+struct venc_controls {
+ u16 gop_size;
+ u32 idr_period;
+ u32 num_p_frames;
+ u32 num_b_frames;
+ u32 bitrate_mode;
+ u32 bitrate;
+ u32 bitrate_peak;
+
+ u32 h264_i_period;
+ u32 h264_entropy_mode;
+ u32 h264_i_qp;
+ u32 h264_p_qp;
+ u32 h264_b_qp;
+ u32 h264_min_qp;
+ u32 h264_max_qp;
+ u32 h264_loop_filter_mode;
+ u32 h264_loop_filter_alpha;
+ u32 h264_loop_filter_beta;
+
+ u32 vp8_min_qp;
+ u32 vp8_max_qp;
+
+ u32 multi_slice_mode;
+ u32 multi_slice_max_bytes;
+ u32 multi_slice_max_mb;
+
+ u32 header_mode;
+
+ u32 profile;
+ u32 level;
+};
+
+struct vidc_inst {
+ struct list_head list;
+ struct mutex lock;
+ struct vidc_core *core;
+
+ struct vidc_list scratchbufs;
+ struct vidc_list persistbufs;
+ struct vidc_list registeredbufs;
+
+ struct list_head bufqueue;
+ struct mutex bufqueue_lock;
+
+ int streamoff;
+ int streamon;
+ struct vb2_queue bufq_out;
+ struct vb2_queue bufq_cap;
+
+ struct v4l2_ctrl_handler ctrl_handler;
+ union {
+ struct vdec_controls dec;
+ struct venc_controls enc;
+ } controls;
+ struct v4l2_fh fh;
+
+ struct hfi_inst *hfi_inst;
+
+ /* session fields */
+ u32 session_type;
+ u32 width;
+ u32 height;
+ u32 out_width;
+ u32 out_height;
+ u32 colorspace;
+ u8 ycbcr_enc;
+ u8 quantization;
+ u8 xfer_func;
+ u64 fps;
+ struct v4l2_fract timeperframe;
+ const struct vidc_format *fmt_out;
+ const struct vidc_format *fmt_cap;
+ unsigned int num_input_bufs;
+ unsigned int num_output_bufs;
+ bool in_reconfig;
+ u32 reconfig_width;
+ u32 reconfig_height;
+ u64 sequence;
+};
+
+#define ctrl_to_inst(ctrl) \
+ container_of(ctrl->handler, struct vidc_inst, ctrl_handler)
+
+struct vidc_ctrl {
+ u32 id;
+ enum v4l2_ctrl_type type;
+ s32 min;
+ s32 max;
+ s32 def;
+ u32 step;
+ u64 menu_skip_mask;
+ u32 flags;
+ const char * const *qmenu;
+};
+
+/*
+ * Offset base for buffers on the destination queue - used to distinguish
+ * between source and destination buffers when mmapping - they receive the same
+ * offsets but for different queues
+ */
+#define DST_QUEUE_OFF_BASE (1 << 30)
+
+extern const struct v4l2_file_operations vidc_fops;
+
+static inline void INIT_VIDC_LIST(struct vidc_list *mlist)
+{
+ mutex_init(&mlist->lock);
+ INIT_LIST_HEAD(&mlist->list);
+}
+
+static inline struct vidc_inst *to_inst(struct file *filp)
+{
+ return container_of(filp->private_data, struct vidc_inst, fh);
+}
+
+static inline struct hfi_inst *to_hfi_inst(struct file *filp)
+{
+ return to_inst(filp)->hfi_inst;
+}
+
+static inline struct vb2_queue *
+vidc_to_vb2q(struct file *file, enum v4l2_buf_type type)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return &inst->bufq_cap;
+ else if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return &inst->bufq_out;
+
+ return NULL;
+}
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/helpers.c b/drivers/media/platform/qcom/vidc/helpers.c
new file mode 100644
index 000000000000..81079f2b5ed1
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/helpers.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "helpers.h"
+#include "int_bufs.h"
+#include "load.h"
+#include "hfi_helper.h"
+
+static int session_set_buf(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *q = vb->vb2_queue;
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct vidc_core *core = inst->core;
+ struct device *dev = core->dev;
+ struct hfi_core *hfi = &core->hfi;
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ struct hfi_frame_data fdata;
+ int ret;
+
+ memset(&fdata, 0, sizeof(fdata));
+
+ fdata.alloc_len = vb2_plane_size(vb, 0);
+ fdata.device_addr = buf->dma_addr;
+ fdata.timestamp = vb->timestamp;
+ fdata.flags = 0;
+ fdata.clnt_data = buf->dma_addr;
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ fdata.buffer_type = HFI_BUFFER_INPUT;
+ fdata.filled_len = vb2_get_plane_payload(vb, 0);
+ fdata.offset = vb->planes[0].data_offset;
+
+ if (vbuf->flags & V4L2_BUF_FLAG_LAST || !fdata.filled_len)
+ fdata.flags |= HFI_BUFFERFLAG_EOS;
+
+ ret = vidc_hfi_session_etb(hfi, inst->hfi_inst, &fdata);
+ } else if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ fdata.buffer_type = HFI_BUFFER_OUTPUT;
+ fdata.filled_len = 0;
+ fdata.offset = 0;
+
+ ret = vidc_hfi_session_ftb(hfi, inst->hfi_inst, &fdata);
+ } else {
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(dev, "failed to set session buffer (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int session_unregister_bufs(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc *bd;
+ struct vidc_buffer *buf, *tmp;
+ int ret = 0;
+
+ mutex_lock(&inst->registeredbufs.lock);
+ list_for_each_entry_safe(buf, tmp, &inst->registeredbufs.list,
+ hfi_list) {
+ list_del(&buf->hfi_list);
+ bd = &buf->bd;
+ bd->response_required = 1;
+ ret = vidc_hfi_session_unset_buffers(hfi, inst->hfi_inst, bd);
+ if (ret) {
+ dev_err(dev, "%s: session release buffers failed\n",
+ __func__);
+ break;
+ }
+ }
+ mutex_unlock(&inst->registeredbufs.lock);
+
+ return ret;
+}
+
+static int session_register_bufs(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc *bd;
+ struct vidc_buffer *buf, *tmp;
+ int ret = 0;
+
+ mutex_lock(&inst->registeredbufs.lock);
+ list_for_each_entry_safe(buf, tmp, &inst->registeredbufs.list,
+ hfi_list) {
+ bd = &buf->bd;
+ ret = vidc_hfi_session_set_buffers(hfi, inst->hfi_inst, bd);
+ if (ret) {
+ dev_err(dev, "%s: session: set buffer failed\n",
+ __func__);
+ break;
+ }
+ }
+ mutex_unlock(&inst->registeredbufs.lock);
+
+ return ret;
+}
+
+int vidc_buf_descs(struct vidc_inst *inst, u32 type,
+ struct hfi_buffer_requirements *out)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ u32 ptype = HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS;
+ union hfi_get_property hprop;
+ int ret, i;
+
+ if (out)
+ memset(out, 0, sizeof(*out));
+
+ ret = vidc_hfi_session_get_property(hfi, inst->hfi_inst, ptype, &hprop);
+ if (ret)
+ return ret;
+
+ ret = -EINVAL;
+
+ for (i = 0; i < HFI_BUFFER_TYPE_MAX; i++) {
+ if (hprop.bufreq[i].type != type)
+ continue;
+
+ if (out)
+ memcpy(out, &hprop.bufreq[i], sizeof(*out));
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+int vidc_set_color_format(struct vidc_inst *inst, u32 type, u32 pixfmt)
+{
+ struct hfi_uncompressed_format_select fmt;
+ struct hfi_core *hfi = &inst->core->hfi;
+ u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT;
+ int ret;
+
+ fmt.buffer_type = type;
+
+ switch (pixfmt) {
+ case V4L2_PIX_FMT_NV12:
+ fmt.format = HFI_COLOR_FORMAT_NV12;
+ break;
+ case V4L2_PIX_FMT_NV21:
+ fmt.format = HFI_COLOR_FORMAT_NV21;
+ break;
+ default:
+ return -ENOTSUPP;
+ }
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fmt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+struct vb2_v4l2_buffer *
+vidc_vb2_find_buf(struct vidc_inst *inst, dma_addr_t addr)
+{
+ struct vidc_buffer *buf;
+ struct vb2_v4l2_buffer *vb = NULL;
+
+ mutex_lock(&inst->bufqueue_lock);
+
+ list_for_each_entry(buf, &inst->bufqueue, list) {
+ if (buf->dma_addr == addr) {
+ vb = &buf->vb;
+ break;
+ }
+ }
+
+ if (vb)
+ list_del(&buf->list);
+
+ mutex_unlock(&inst->bufqueue_lock);
+
+ return vb;
+}
+
+int vidc_vb2_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *q = vb->vb2_queue;
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ struct hfi_buffer_desc *bd = &buf->bd;
+ struct sg_table *sgt;
+
+ memset(bd, 0, sizeof(*bd));
+
+ if (q->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return 0;
+
+ sgt = vb2_dma_sg_plane_desc(vb, 0);
+ if (!sgt)
+ return -EINVAL;
+
+ bd->buffer_size = vb2_plane_size(vb, 0);
+ bd->buffer_type = HFI_BUFFER_OUTPUT;
+ bd->num_buffers = 1;
+ bd->device_addr = sg_dma_address(sgt->sgl);
+
+ mutex_lock(&inst->registeredbufs.lock);
+ list_add_tail(&buf->hfi_list, &inst->registeredbufs.list);
+ mutex_unlock(&inst->registeredbufs.lock);
+
+ return 0;
+}
+
+int vidc_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ struct sg_table *sgt;
+
+ sgt = vb2_dma_sg_plane_desc(vb, 0);
+ if (!sgt)
+ return -EINVAL;
+
+ buf->dma_addr = sg_dma_address(sgt->sgl);
+
+ return 0;
+}
+
+void vidc_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vidc_inst *inst = vb2_get_drv_priv(vb->vb2_queue);
+ struct vidc_core *core = inst->core;
+ struct device *dev = core->dev;
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ unsigned int state;
+ int ret;
+
+ mutex_lock(&inst->hfi_inst->lock);
+ state = inst->hfi_inst->state;
+ mutex_unlock(&inst->hfi_inst->lock);
+
+ if (state == INST_INVALID || state >= INST_STOP) {
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ dev_dbg(dev, "%s: type:%d, invalid instance state\n", __func__,
+ vb->type);
+ return;
+ }
+
+ mutex_lock(&inst->bufqueue_lock);
+ list_add_tail(&buf->list, &inst->bufqueue);
+ mutex_unlock(&inst->bufqueue_lock);
+
+ if (!vb2_is_streaming(&inst->bufq_cap) ||
+ !vb2_is_streaming(&inst->bufq_out))
+ return;
+
+ ret = session_set_buf(vb);
+ if (ret)
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+}
+
+void vidc_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+ struct vidc_core *core = inst->core;
+ struct device *dev = core->dev;
+ struct hfi_core *hfi = &core->hfi;
+ int ret, streamoff;
+
+ mutex_lock(&inst->lock);
+ streamoff = inst->streamoff;
+ mutex_unlock(&inst->lock);
+
+ if (streamoff)
+ return;
+
+ mutex_lock(&inst->lock);
+ if (inst->streamon == 0) {
+ mutex_unlock(&inst->lock);
+ return;
+ }
+ mutex_unlock(&inst->lock);
+
+ ret = vidc_hfi_session_stop(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: stop failed (%d)\n", ret);
+ goto abort;
+ }
+
+ ret = vidc_hfi_session_unload_res(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: release resources failed (%d)\n", ret);
+ goto abort;
+ }
+
+ ret = session_unregister_bufs(inst);
+ if (ret) {
+ dev_err(dev, "failed to release capture buffers: %d\n", ret);
+ goto abort;
+ }
+
+ ret = internal_bufs_free(inst);
+
+ if (hfi_inst->state == INST_INVALID || hfi->state == CORE_INVALID) {
+ ret = -EINVAL;
+ goto abort;
+ }
+
+abort:
+ if (ret)
+ vidc_hfi_session_abort(hfi, inst->hfi_inst);
+
+ vidc_scale_clocks(inst->core);
+
+ ret = vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+
+ mutex_lock(&inst->lock);
+ inst->streamoff = 1;
+ mutex_unlock(&inst->lock);
+
+ if (ret)
+ dev_err(dev, "stop streaming failed type: %d, ret: %d\n",
+ q->type, ret);
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret < 0)
+ dev_err(dev, "%s: pm_runtime_put_sync (%d)\n", __func__, ret);
+}
+
+int vidc_vb2_start_streaming(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vidc_buffer *buf, *n;
+ int ret;
+
+ ret = session_register_bufs(inst);
+ if (ret)
+ return ret;
+
+ ret = internal_bufs_alloc(inst);
+ if (ret)
+ return ret;
+
+ vidc_scale_clocks(inst->core);
+
+ ret = vidc_hfi_session_load_res(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: load resources (%d)\n", ret);
+ return ret;
+ }
+
+ ret = vidc_hfi_session_start(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: start failed (%d)\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&inst->bufqueue_lock);
+ list_for_each_entry_safe(buf, n, &inst->bufqueue, list) {
+ ret = session_set_buf(&buf->vb.vb2_buf);
+ if (ret)
+ break;
+ }
+ mutex_unlock(&inst->bufqueue_lock);
+
+ if (!ret) {
+ mutex_lock(&inst->lock);
+ inst->streamon = 1;
+ mutex_unlock(&inst->lock);
+ }
+
+ return ret;
+}
diff --git a/drivers/media/platform/qcom/vidc/helpers.h b/drivers/media/platform/qcom/vidc/helpers.h
new file mode 100644
index 000000000000..a151c96bf939
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/helpers.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_COMMON_H__
+#define __VIDC_COMMON_H__
+
+#include <linux/list.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "core.h"
+
+struct vidc_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ dma_addr_t dma_addr;
+ struct list_head hfi_list;
+ struct hfi_buffer_desc bd;
+};
+
+#define to_vidc_buffer(buf) container_of(buf, struct vidc_buffer, vb)
+
+struct vb2_v4l2_buffer *
+vidc_vb2_find_buf(struct vidc_inst *inst, dma_addr_t addr);
+int vidc_vb2_buf_init(struct vb2_buffer *vb);
+int vidc_vb2_buf_prepare(struct vb2_buffer *vb);
+void vidc_vb2_buf_queue(struct vb2_buffer *vb);
+void vidc_vb2_stop_streaming(struct vb2_queue *q);
+int vidc_vb2_start_streaming(struct vidc_inst *inst);
+int vidc_buf_descs(struct vidc_inst *inst, u32 type,
+ struct hfi_buffer_requirements *out);
+int vidc_set_color_format(struct vidc_inst *inst, u32 type, u32 fmt);
+#endif
diff --git a/drivers/media/platform/qcom/vidc/int_bufs.c b/drivers/media/platform/qcom/vidc/int_bufs.c
new file mode 100644
index 000000000000..393d75785d7a
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/int_bufs.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include "helpers.h"
+#include "int_bufs.h"
+#include "mem.h"
+
+struct vidc_internal_buf {
+ struct list_head list;
+ u32 type;
+ struct vidc_mem *mem;
+};
+
+static u32 scratch_buf_sufficient(struct vidc_inst *inst, u32 buffer_type)
+{
+ struct hfi_buffer_requirements bufreq;
+ struct vidc_internal_buf *buf;
+ unsigned int count = 0;
+ int ret;
+
+ ret = vidc_buf_descs(inst, buffer_type, &bufreq);
+ if (ret)
+ return 0;
+
+ /* Check if current scratch buffers are sufficient */
+ mutex_lock(&inst->scratchbufs.lock);
+ list_for_each_entry(buf, &inst->scratchbufs.list, list) {
+ if (buf->type == buffer_type &&
+ buf->mem->size >= bufreq.size)
+ count++;
+ }
+ mutex_unlock(&inst->scratchbufs.lock);
+
+ if (count != bufreq.count_actual)
+ return 0;
+
+ return buffer_type;
+}
+
+static int internal_set_buf_on_fw(struct vidc_inst *inst, u32 buffer_type,
+ struct vidc_mem *mem, bool reuse)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc bd = {0};
+ int ret;
+
+ bd.buffer_size = mem->size;
+ bd.buffer_type = buffer_type;
+ bd.num_buffers = 1;
+ bd.device_addr = mem->da;
+
+ ret = vidc_hfi_session_set_buffers(hfi, inst->hfi_inst, &bd);
+ if (ret) {
+ dev_err(dev, "set session buffers failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int internal_alloc_and_set(struct vidc_inst *inst,
+ struct hfi_buffer_requirements *bufreq,
+ struct vidc_list *buf_list)
+{
+ struct vidc_internal_buf *buf;
+ struct vidc_mem *mem;
+ unsigned int i;
+ int ret = 0;
+
+ if (!bufreq->size)
+ return 0;
+
+ for (i = 0; i < bufreq->count_actual; i++) {
+ mem = mem_alloc(inst->core->dev, bufreq->size, 0);
+ if (IS_ERR(mem)) {
+ ret = PTR_ERR(mem);
+ goto err_no_mem;
+ }
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto fail_kzalloc;
+ }
+
+ buf->mem = mem;
+ buf->type = bufreq->type;
+
+ ret = internal_set_buf_on_fw(inst, bufreq->type, mem, false);
+ if (ret)
+ goto fail_set_buffers;
+
+ mutex_lock(&buf_list->lock);
+ list_add_tail(&buf->list, &buf_list->list);
+ mutex_unlock(&buf_list->lock);
+ }
+
+ return ret;
+
+fail_set_buffers:
+ kfree(buf);
+fail_kzalloc:
+ mem_free(mem);
+err_no_mem:
+ return ret;
+}
+
+static bool scratch_reuse_buffer(struct vidc_inst *inst, u32 buffer_type)
+{
+ struct device *dev = inst->core->dev;
+ struct vidc_internal_buf *buf;
+ bool reused = false;
+ int ret = 0;
+
+ mutex_lock(&inst->scratchbufs.lock);
+ list_for_each_entry(buf, &inst->scratchbufs.list, list) {
+ if (buf->type != buffer_type)
+ continue;
+
+ ret = internal_set_buf_on_fw(inst, buffer_type, buf->mem, true);
+ if (ret) {
+ dev_err(dev, "set internal buffers failed\n");
+ reused = false;
+ break;
+ }
+
+ reused = true;
+ }
+ mutex_unlock(&inst->scratchbufs.lock);
+
+ return reused;
+}
+
+static int scratch_set_buffer(struct vidc_inst *inst, u32 type)
+{
+ struct hfi_buffer_requirements bufreq;
+ int ret;
+
+ ret = vidc_buf_descs(inst, type, &bufreq);
+ if (ret)
+ return 0;
+
+ if (scratch_reuse_buffer(inst, type))
+ return 0;
+
+ return internal_alloc_and_set(inst, &bufreq, &inst->scratchbufs);
+}
+
+static int persist_set_buffer(struct vidc_inst *inst, u32 type)
+{
+ struct hfi_buffer_requirements bufreq;
+ int ret;
+
+ ret = vidc_buf_descs(inst, type, &bufreq);
+ if (ret)
+ return 0;
+
+ mutex_lock(&inst->persistbufs.lock);
+ if (!list_empty(&inst->persistbufs.list)) {
+ mutex_unlock(&inst->persistbufs.lock);
+ return 0;
+ }
+ mutex_unlock(&inst->persistbufs.lock);
+
+ return internal_alloc_and_set(inst, &bufreq, &inst->persistbufs);
+}
+
+static int scratch_unset_buffers(struct vidc_inst *inst, bool reuse)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vidc_internal_buf *buf, *n;
+ struct hfi_buffer_desc bd = {0};
+ u32 sufficient = 0;
+ int ret = 0;
+
+ mutex_lock(&inst->scratchbufs.lock);
+ list_for_each_entry_safe(buf, n, &inst->scratchbufs.list, list) {
+ bd.buffer_size = buf->mem->size;
+ bd.buffer_type = buf->type;
+ bd.num_buffers = 1;
+ bd.device_addr = buf->mem->da;
+ bd.response_required = true;
+
+ ret = vidc_hfi_session_unset_buffers(hfi, inst->hfi_inst, &bd);
+
+ /* If scratch buffers can be reused, do not free the buffers */
+ if (reuse) {
+ sufficient = scratch_buf_sufficient(inst, buf->type);
+ if (sufficient == buf->type)
+ continue;
+ }
+
+ list_del(&buf->list);
+ mem_free(buf->mem);
+ kfree(buf);
+ }
+ mutex_unlock(&inst->scratchbufs.lock);
+
+ return ret;
+}
+
+static int persist_unset_buffers(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vidc_internal_buf *buf, *n;
+ struct hfi_buffer_desc bd = {0};
+ int ret = 0;
+
+ mutex_lock(&inst->persistbufs.lock);
+ list_for_each_entry_safe(buf, n, &inst->persistbufs.list, list) {
+ bd.buffer_size = buf->mem->size;
+ bd.buffer_type = buf->type;
+ bd.num_buffers = 1;
+ bd.device_addr = buf->mem->da;
+ bd.response_required = true;
+
+ ret = vidc_hfi_session_unset_buffers(hfi, inst->hfi_inst, &bd);
+
+ list_del(&buf->list);
+ mem_free(buf->mem);
+ kfree(buf);
+ }
+ mutex_unlock(&inst->persistbufs.lock);
+
+ return ret;
+}
+
+static int scratch_set_buffers(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ int ret;
+
+ ret = scratch_unset_buffers(inst, true);
+ if (ret)
+ dev_warn(dev, "Failed to release scratch buffers\n");
+
+ ret = scratch_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH);
+ if (ret)
+ goto error;
+
+ ret = scratch_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH_1);
+ if (ret)
+ goto error;
+
+ ret = scratch_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH_2);
+ if (ret)
+ goto error;
+
+ return 0;
+error:
+ scratch_unset_buffers(inst, false);
+ return ret;
+}
+
+static int persist_set_buffers(struct vidc_inst *inst)
+{
+ int ret;
+
+ ret = persist_set_buffer(inst, HFI_BUFFER_INTERNAL_PERSIST);
+ if (ret)
+ goto error;
+
+ ret = persist_set_buffer(inst, HFI_BUFFER_INTERNAL_PERSIST_1);
+ if (ret)
+ goto error;
+
+ return 0;
+
+error:
+ persist_unset_buffers(inst);
+ return ret;
+}
+
+int internal_bufs_alloc(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ int ret;
+
+ ret = scratch_set_buffers(inst);
+ if (ret) {
+ dev_err(dev, "set scratch buffers (%d)\n", ret);
+ return ret;
+ }
+
+ ret = persist_set_buffers(inst);
+ if (ret) {
+ dev_err(dev, "set persist buffers (%d)\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ scratch_unset_buffers(inst, false);
+ return ret;
+}
+
+int internal_bufs_free(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ int ret;
+
+ ret = scratch_unset_buffers(inst, false);
+ if (ret)
+ dev_err(dev, "failed to release scratch buffers: %d\n", ret);
+
+ ret = persist_unset_buffers(inst);
+ if (ret)
+ dev_err(dev, "failed to release persist buffers: %d\n", ret);
+
+ return ret;
+}
diff --git a/drivers/media/platform/qcom/vidc/int_bufs.h b/drivers/media/platform/qcom/vidc/int_bufs.h
new file mode 100644
index 000000000000..5f8b2b85839f
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/int_bufs.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_INTERNAL_BUFFERS_H__
+#define __VIDC_INTERNAL_BUFFERS_H__
+
+struct vidc_inst;
+
+int internal_bufs_alloc(struct vidc_inst *inst);
+int internal_bufs_free(struct vidc_inst *inst);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/load.c b/drivers/media/platform/qcom/vidc/load.c
new file mode 100644
index 000000000000..8ae25fc0e8a5
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/load.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include <linux/clk.h>
+
+#include "core.h"
+#include "load.h"
+
+static u32 get_inst_load(struct vidc_inst *inst)
+{
+ int mbs;
+ u32 w = inst->width;
+ u32 h = inst->height;
+
+ if (!inst->hfi_inst || !(inst->hfi_inst->state >= INST_INIT &&
+ inst->hfi_inst->state < INST_STOP))
+ return 0;
+
+ mbs = (ALIGN(w, 16) / 16) * (ALIGN(h, 16) / 16);
+
+ return mbs * inst->fps;
+}
+
+static u32 get_load(struct vidc_core *core, u32 session_type)
+{
+ struct vidc_inst *inst = NULL;
+ u32 mbs_per_sec = 0;
+
+ mutex_lock(&core->lock);
+ list_for_each_entry(inst, &core->instances, list) {
+ if (inst->session_type != session_type)
+ continue;
+
+ mbs_per_sec += get_inst_load(inst);
+ }
+ mutex_unlock(&core->lock);
+
+ return mbs_per_sec;
+}
+
+static int scale_clocks_load(struct vidc_core *core, u32 mbs_per_sec)
+{
+ const struct freq_tbl *table = core->res->freq_tbl;
+ int num_rows = core->res->freq_tbl_size;
+ struct clk *clk = core->clks[0];
+ struct device *dev = core->dev;
+ unsigned long freq = table[0].freq;
+ int ret, i;
+
+ if (!mbs_per_sec && num_rows > 1) {
+ freq = table[num_rows - 1].freq;
+ goto set_freq;
+ }
+
+ for (i = 0; i < num_rows; i++) {
+ if (mbs_per_sec > table[i].load)
+ break;
+ freq = table[i].freq;
+ }
+
+set_freq:
+
+ ret = clk_set_rate(clk, freq);
+ if (ret) {
+ dev_err(dev, "failed to set clock rate %lu (%d)\n", freq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int vidc_scale_clocks(struct vidc_core *core)
+{
+ struct device *dev = core->dev;
+ u32 mbs_per_sec;
+ int ret;
+
+ mbs_per_sec = get_load(core, VIDC_SESSION_TYPE_ENC) +
+ get_load(core, VIDC_SESSION_TYPE_DEC);
+
+ if (mbs_per_sec > core->res->max_load) {
+ dev_warn(dev, "HW is overloaded, needed: %d max: %d\n",
+ mbs_per_sec, core->res->max_load);
+ return -EBUSY;
+ }
+
+ ret = scale_clocks_load(core, mbs_per_sec);
+ if (ret)
+ dev_warn(dev, "failed to scale clocks, performance might be impacted\n");
+
+ return 0;
+}
diff --git a/drivers/media/platform/qcom/vidc/load.h b/drivers/media/platform/qcom/vidc/load.h
new file mode 100644
index 000000000000..b8f8dc57e18f
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/load.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_LOAD_H__
+#define __VIDC_LOAD_H__
+
+struct vidc_core;
+
+int vidc_scale_clocks(struct vidc_core *core);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/mem.c b/drivers/media/platform/qcom/vidc/mem.c
new file mode 100644
index 000000000000..6a83b5784410
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/mem.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#include "mem.h"
+
+struct vidc_mem *mem_alloc(struct device *dev, size_t size, int map_kernel)
+{
+ struct vidc_mem *mem;
+
+ if (!size)
+ return ERR_PTR(-EINVAL);
+
+ if (IS_ERR(dev))
+ return ERR_CAST(dev);
+
+ mem = kzalloc(sizeof(*mem), GFP_KERNEL);
+ if (!mem)
+ return ERR_PTR(-ENOMEM);
+
+ mem->size = ALIGN(size, SZ_4K);
+ mem->iommu_dev = dev;
+
+ mem->attrs = DMA_ATTR_WRITE_COMBINE;
+
+ if (!map_kernel)
+ mem->attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
+
+ mem->kvaddr = dma_alloc_attrs(mem->iommu_dev, mem->size, &mem->da,
+ GFP_KERNEL, mem->attrs);
+ if (!mem->kvaddr) {
+ kfree(mem);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return mem;
+}
+
+void mem_free(struct vidc_mem *mem)
+{
+ if (!mem)
+ return;
+
+ dma_free_attrs(mem->iommu_dev, mem->size, mem->kvaddr,
+ mem->da, mem->attrs);
+ kfree(mem);
+};
diff --git a/drivers/media/platform/qcom/vidc/mem.h b/drivers/media/platform/qcom/vidc/mem.h
new file mode 100644
index 000000000000..cab81aa2f550
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/mem.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#ifndef __VIDC_MEM_H__
+#define __VIDC_MEM_H__
+
+struct device;
+
+struct vidc_mem {
+ size_t size;
+ void *kvaddr;
+ dma_addr_t da;
+ unsigned long attrs;
+ struct device *iommu_dev;
+};
+
+struct vidc_mem *mem_alloc(struct device *dev, size_t size, int map_kernel);
+void mem_free(struct vidc_mem *mem);
+
+#endif /* __VIDC_MEM_H__ */
diff --git a/drivers/media/platform/qcom/vidc/resources.c b/drivers/media/platform/qcom/vidc/resources.c
new file mode 100644
index 000000000000..e00ed1caa824
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/resources.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/bug.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include "hfi.h"
+
+static const struct freq_tbl msm8916_freq_table[] = {
+ { 352800, 228570000 }, /* 1920x1088 @ 30 + 1280x720 @ 30 */
+ { 244800, 160000000 }, /* 1920x1088 @ 30 */
+ { 108000, 100000000 }, /* 1280x720 @ 30 */
+};
+
+static const struct reg_val msm8916_reg_preset[] = {
+ { 0xe0020, 0x05555556 },
+ { 0xe0024, 0x05555556 },
+ { 0x80124, 0x00000003 },
+};
+
+const struct vidc_resources msm8916_res = {
+ .freq_tbl = msm8916_freq_table,
+ .freq_tbl_size = ARRAY_SIZE(msm8916_freq_table),
+ .reg_tbl = msm8916_reg_preset,
+ .reg_tbl_size = ARRAY_SIZE(msm8916_reg_preset),
+ .clks = {"core", "iface", "bus", },
+ .clks_num = 3,
+ .max_load = 352800, /* 720p@30 + 1080p@30 */
+ .hfi_version = 0,
+ .vmem_id = VIDC_RESOURCE_NONE,
+ .vmem_size = 0,
+ .vmem_addr = 0,
+ .dma_mask = 0xddc00000 - 1,
+};
diff --git a/drivers/media/platform/qcom/vidc/resources.h b/drivers/media/platform/qcom/vidc/resources.h
new file mode 100644
index 000000000000..9f2afad78a9f
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/resources.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_RESOURCES_H__
+#define __VIDC_RESOURCES_H__
+
+#define VIDC_CLKS_NUM_MAX 7
+
+struct freq_tbl {
+ unsigned int load;
+ unsigned long freq;
+};
+
+struct reg_val {
+ u32 reg;
+ u32 value;
+};
+
+struct vidc_resources {
+ u64 dma_mask;
+ const struct freq_tbl *freq_tbl;
+ unsigned int freq_tbl_size;
+ const struct reg_val *reg_tbl;
+ unsigned int reg_tbl_size;
+ const char *clks[VIDC_CLKS_NUM_MAX];
+ unsigned int clks_num;
+ unsigned int hfi_version;
+ u32 max_load;
+ unsigned int vmem_id;
+ u32 vmem_size;
+ u32 vmem_addr;
+};
+
+extern const struct vidc_resources msm8916_res;
+#endif
--
2.7.4

Hans Verkuil

unread,
Aug 22, 2016, 9:50:06 AM8/22/16
to
Hi Stanimir,

Thanks for this patch series!

I have some review comments:

On 08/22/2016 03:13 PM, Stanimir Varbanov wrote:

<snip>
I know that many drivers embed struct video_device, but this can cause subtle
refcounting problems. I recommend changing this to a pointer and using video_device_alloc().

I have plans to reorganize the way video_devices are allocated and registered in
the near future, and you might just as well prepare this driver for that by switching
to a pointer.
I'm not really sure how this error code is used, but normally -EINVAL is returned
for invalid pixel formats. -ENOTSUPP is not used by V4L2.
Regards,

Hans

Stanimir Varbanov

unread,
Aug 22, 2016, 12:10:06 PM8/22/16
to
Hi Hans,

Thanks for the express comments!

<cut>

>> +
>> +struct vidc_core {
>> + struct list_head list;
>> + void __iomem *base;
>> + int irq;
>> + struct clk *clks[VIDC_CLKS_NUM_MAX];
>> + struct mutex lock;
>> + struct hfi_core hfi;
>> + struct video_device vdev_dec;
>> + struct video_device vdev_enc;
>
> I know that many drivers embed struct video_device, but this can cause subtle
> refcounting problems. I recommend changing this to a pointer and using video_device_alloc().
>
> I have plans to reorganize the way video_devices are allocated and registered in
> the near future, and you might just as well prepare this driver for that by switching
> to a pointer.

OK, thanks for the info, I will change to pointers.

<cut>

>> +
>> +int vidc_set_color_format(struct vidc_inst *inst, u32 type, u32 pixfmt)
>> +{
>> + struct hfi_uncompressed_format_select fmt;
>> + struct hfi_core *hfi = &inst->core->hfi;
>> + u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT;
>> + int ret;
>> +
>> + fmt.buffer_type = type;
>> +
>> + switch (pixfmt) {
>> + case V4L2_PIX_FMT_NV12:
>> + fmt.format = HFI_COLOR_FORMAT_NV12;
>> + break;
>> + case V4L2_PIX_FMT_NV21:
>> + fmt.format = HFI_COLOR_FORMAT_NV21;
>> + break;
>> + default:
>> + return -ENOTSUPP;
>
> I'm not really sure how this error code is used, but normally -EINVAL is returned
> for invalid pixel formats. -ENOTSUPP is not used by V4L2.
>

you are right, I need to change this to EINVAL.

--
regards,
Stan

Bjorn Andersson

unread,
Aug 22, 2016, 11:00:05 PM8/22/16
to
On Mon 22 Aug 06:13 PDT 2016, Stanimir Varbanov wrote:

Hi Stan,

> This adds core part of the vidc driver common helper functions
> used by encoder and decoder specific files.

I believe "vidc" is short for "video core" and this is not the only
"video core" from Qualcomm. This driver is the v4l2 <-> hfi interface and
uses either two ram based fifos _or_ apr tal for communication with the
implementation.

In the case of apr, the other side is not the venus core but rather the
"VIDC" apr service on the Hexagon DSP. In this case the hfi packets are
encapsulated in apr packets. Although this is not used in 8916 it would
be nice to be able to add this later...


But I think we should call this driver "hfi" - or at least venus, as
it's not compatible with e.g the "blackbird" found in 8064, which is
also called "vidc".

>
> - core.c has implemented the platform dirver methods, file
> operations and v4l2 registration.
>
> - helpers.c has implemented common helper functions for
> buffer management, vb2_ops and functions for format propagation.
>
> - int_bufs.c implements functions for allocating and freeing
> buffers for internal usage. The buffer parameters describing
> internal buffers depends on current format, resolution and
> codec.
>
> - load.c consists functions for calculation of current load
> of the hardware. Depending on the count of instances and
> resolutions it selects the best clock rate for the video
> core.
>
> - mem.c has two functions for memory allocation, currently
> those functions are used for internal buffers and to allocate
> the shared memory for communication with firmware via HFI
> (Host Firmware Interface) interface commands.

Please drop this; see comments on mem_alloc()

>
> - resources.c exports a structure describing the details
> specific to platform and SoC.
>
> Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
> ---

This doesn't compile, as it depends on later patches. Also there are
plenty of functions that are related to later patches and would with be
better to include there, to keep the size of this patch down.
There are two different "instances" lists in this implementation, one
keeping track of vidc instances and one keeping track of hfi instances,
at the same time the vidc instances has a reference to its associated
hfi instance.

It should be possible to drop one of those lists.

> + mutex_unlock(&core->lock);
> +}
> +
> +static void vidc_del_inst(struct vidc_core *core, struct vidc_inst *inst)
> +{
> + struct vidc_inst *pos, *n;
> +
> + mutex_lock(&core->lock);
> + list_for_each_entry_safe(pos, n, &core->instances, list) {
> + if (pos == inst)
> + list_del(&inst->list);
> + }
> + mutex_unlock(&core->lock);
> +}
> +
> +static int vidc_rproc_boot(struct vidc_core *core)
> +{
> + int ret;
> +
> + if (core->rproc_booted)
> + return 0;

rproc_boot()/rproc_shutdown() is reference counted, so there is no
reason (other than this driver being buggy) to keep track of
"rproc_boot". As such, you can drop vidc_rproc_boot() and
vidc_rproc_shutdown() and just call the rproc functions directly.

> +
> + ret = rproc_boot(core->rproc);
> + if (ret)
> + return ret;
> +
> + core->rproc_booted = true;
> +
> + return 0;
> +}
> +
> +static void vidc_rproc_shutdown(struct vidc_core *core)
> +{
> + if (!core->rproc_booted)
> + return;
> +
> + rproc_shutdown(core->rproc);
> + core->rproc_booted = false;
> +}
> +
> +struct vidc_sys_error {
> + struct vidc_core *core;
> + struct delayed_work work;
> +};

This is cool, but during the 5 second delay we should be able to call
remove on the driver and this will dereference a freed hfi instance.

Move the worker to hfi_core and you can cancel it on remove.

> +
> +static void vidc_sys_error_handler(struct work_struct *work)
> +{
> + struct vidc_sys_error *handler =
> + container_of(work, struct vidc_sys_error, work.work);
> + struct vidc_core *core = handler->core;
> + struct hfi_core *hfi = &core->hfi;
> + struct device *dev = core->dev;
> + int ret;
> +
> + mutex_lock(&hfi->lock);
> + if (hfi->state != CORE_INVALID)
> + goto exit;
> +
> + mutex_unlock(&hfi->lock);
> +
> + ret = vidc_hfi_core_deinit(hfi);
> + if (ret)
> + dev_err(dev, "core: deinit failed (%d)\n", ret);
> +
> + mutex_lock(&hfi->lock);
> +
> + rproc_report_crash(core->rproc, RPROC_FATAL_ERROR);

This operation is async, as such I believe this to be fragile. To get
the expected result you should be able to simply call
rproc_shutdown()/rproc_boot() to restart the core...

However, if we at any point would like to be able to get memory dumps
from this core (likely a requirement on the Qualcomm side) we need to
call rproc_report_crash() and let it collect the resources and then
power cycle the core.


As the life cycle of the venus driver goes 1:1 with the rproc driver I
think it would be more suitable to make the v4l driver a child of the
rproc driver and have it probe/remove this driver as the rproc comes and
goes. This would allow us to call rproc_report_crash() here, we will be
removed and when the crash is handled (sometime in the future) we will
be probed again.
This is an overly generic way of calling vidc_sys_error_handler().
There is no need for having the hfi_core_ops indirections for a single
op that will only exist in 1 and only 1 variant.

Just replace the two affected event_notify() calls with a direct call to
this function (and clean it up a bit).

> +
> +static int vidc_open(struct file *file)
> +{
> + struct video_device *vdev = video_devdata(file);
> + struct vidc_core *core = video_drvdata(file);
> + struct vidc_inst *inst;
> + int ret = 0;
> +
> + inst = kzalloc(sizeof(*inst), GFP_KERNEL);
> + if (!inst)
> + return -ENOMEM;
> +
> + mutex_init(&inst->lock);
> +
> + INIT_VIDC_LIST(&inst->scratchbufs);

Please inline the mutex_init() and INIT_LIST_HEAD() here and drop the
custom INIT_VIDC_LIST() wrapper macro.
Here we have three sequential conditionals testing for the same thing,
please join them into one.
Here's a good reason for dropping the INIT_VIDC_LIST() macro
This feels hackish, is this really the way to do this?
These two functions indicates that we're requesting the irq in the wrong
layer.

Also, these two functions arrives in a later patchset, so I assume this
doesn't compile...
As you're using of_device_get_match_data() you can move this table to
the bottom of the file.

> +
> +static int vidc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct vidc_core *core;
> + struct device_node *rproc;
> + struct resource *r;
> + int ret;
> +
> + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
> + if (!core)
> + return -ENOMEM;
> +
> + core->dev = dev;
> + platform_set_drvdata(pdev, core);
> +
> + rproc = of_parse_phandle(dev->of_node, "rproc", 0);
> + if (IS_ERR(rproc))
> + return PTR_ERR(rproc);
> +
> + core->rproc = rproc_get_by_phandle(rproc->phandle);

FYI, We're hoping to land some patches shortly that will replace this
with rproc_get(pdev->dev.of_node), looking up an rproc by the standard
"rprocs" property...

> + if (IS_ERR(core->rproc))
> + return PTR_ERR(core->rproc);
> + else if (!core->rproc)
> + return -EPROBE_DEFER;

We're cleaning up this in the core as well.

You need to rproc_put() the rproc pointer after this point.


My question still stands though, if this driver should be probed as the
remoteproc is booted (or the apr service appearing). I will continue to
look at that.
Drop this IRQF_TRIGGER_HIGH and have this be specified in devicetree.
These operations follow the general pattern of booting other Qualcomm
remoteprocs; acquire and enable some resources, boot the core and
disable the resources. Therefor it looks quite likely that these
operations are related to the life cycle of the venus core, rather than
hfi.
No-one cares about you returning an error here, so you better move
forward and release as much of your resources as possible even though
you didn't get your pm.
Unused

> +
> +struct vidc_list {
> + struct list_head list;
> + struct mutex lock;
> +};

Can't we get away without passing around lockable lists? Does these
lists have to be locked independently and should we really pass around
their lock with them?

> +
> +struct vidc_format {
> + u32 pixfmt;
> + int num_planes;
> + u32 type;
> +};
> +
> +struct vidc_core {
> + struct list_head list;

This list_head seems unused.

> + void __iomem *base;

base is acquired and passed by value to vidc_hfi_create(), so no need to
keep track of it here.

> + int irq;

This irq belongs to hfi, so it should probably be kept there.

> + struct clk *clks[VIDC_CLKS_NUM_MAX];
> + struct mutex lock;

This "lock" seems to be only related the instances list, please name it
more appropriately - and place it next to the instances member.
Just inline the list_head and mutex here, as it's done for bufqueue.
Just pass this to v{dec,enc}_init() rather than back-referencing it
through a global variable. But on the other hand this is unused in this
patchset...

> +
> +static inline void INIT_VIDC_LIST(struct vidc_list *mlist)
> +{
> + mutex_init(&mlist->lock);
> + INIT_LIST_HEAD(&mlist->list);
> +}
> +
> +static inline struct vidc_inst *to_inst(struct file *filp)
> +{
> + return container_of(filp->private_data, struct vidc_inst, fh);
> +}
> +
> +static inline struct hfi_inst *to_hfi_inst(struct file *filp)

Unused

> +{
> + return to_inst(filp)->hfi_inst;
> +}
> +
> +static inline struct vb2_queue *
> +vidc_to_vb2q(struct file *file, enum v4l2_buf_type type)

Unused
So the hfi_list is the list_head for entries in the _vidc_ instance
list?
If you call this vidc_get_buf_requirements() it would actually describe
what's going on. But why is this hfi wrapper in the core, rather than
just have the internal buffer manager call it directly.

The call doesn't seem to depend on the parameters or state, can we
cache the result?
[..]
> +
> +void vidc_vb2_stop_streaming(struct vb2_queue *q)
> +{
> + struct vidc_inst *inst = vb2_get_drv_priv(q);
> + struct hfi_inst *hfi_inst = inst->hfi_inst;
> + struct vidc_core *core = inst->core;
> + struct device *dev = core->dev;
> + struct hfi_core *hfi = &core->hfi;
> + int ret, streamoff;
> +
> + mutex_lock(&inst->lock);
> + streamoff = inst->streamoff;
> + mutex_unlock(&inst->lock);
> +
> + if (streamoff)
> + return;
> +
> + mutex_lock(&inst->lock);
> + if (inst->streamon == 0) {
> + mutex_unlock(&inst->lock);
> + return;
> + }
> + mutex_unlock(&inst->lock);

Why do we keep track of streamon and stream off, why isn't streamoff
ever cleared? Why don't we check both conditions in one critical region?

> +
> + ret = vidc_hfi_session_stop(hfi, inst->hfi_inst);
> + if (ret) {
> + dev_err(dev, "session: stop failed (%d)\n", ret);
> + goto abort;

When are we going to relaim the buffers in these cases?
s/COMMON/HELPERS/

> +
> +#include <linux/list.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "core.h"
> +
> +struct vidc_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> + dma_addr_t dma_addr;
> + struct list_head hfi_list;

This seems to be the list_head used for associating buffers to the
_vidc_ instances.

> + struct hfi_buffer_desc bd;
> +};
> +
> +#define to_vidc_buffer(buf) container_of(buf, struct vidc_buffer, vb)
> +
> +struct vb2_v4l2_buffer *
> +vidc_vb2_find_buf(struct vidc_inst *inst, dma_addr_t addr);
> +int vidc_vb2_buf_init(struct vb2_buffer *vb);
> +int vidc_vb2_buf_prepare(struct vb2_buffer *vb);
> +void vidc_vb2_buf_queue(struct vb2_buffer *vb);
> +void vidc_vb2_stop_streaming(struct vb2_queue *q);
> +int vidc_vb2_start_streaming(struct vidc_inst *inst);
> +int vidc_buf_descs(struct vidc_inst *inst, u32 type,
> + struct hfi_buffer_requirements *out);
> +int vidc_set_color_format(struct vidc_inst *inst, u32 type, u32 fmt);
> +#endif
> diff --git a/drivers/media/platform/qcom/vidc/int_bufs.c b/drivers/media/platform/qcom/vidc/int_bufs.c
[..]
> +
> +static int internal_alloc_and_set(struct vidc_inst *inst,
> + struct hfi_buffer_requirements *bufreq,
> + struct vidc_list *buf_list)
> +{
> + struct vidc_internal_buf *buf;
> + struct vidc_mem *mem;
> + unsigned int i;
> + int ret = 0;
> +
> + if (!bufreq->size)
> + return 0;
> +
> + for (i = 0; i < bufreq->count_actual; i++) {
> + mem = mem_alloc(inst->core->dev, bufreq->size, 0);

Inline mem_alloc here; might need to make sure bufreq->size is 4K
aligned.
[..]
> +
> +static int persist_set_buffer(struct vidc_inst *inst, u32 type)
> +{
> + struct hfi_buffer_requirements bufreq;
> + int ret;
> +
> + ret = vidc_buf_descs(inst, type, &bufreq);
> + if (ret)
> + return 0;
> +
> + mutex_lock(&inst->persistbufs.lock);
> + if (!list_empty(&inst->persistbufs.list)) {

This function is called twice, with type HFI_BUFFER_INTERNAL_PERSIST and
HFI_BUFFER_INTERNAL_PERSIST_1 respectively. Unless the buffer
requirements are missing for HFI_BUFFER_INTERNAL_PERSIST persistbufs
won't be empty and we will skip the later allocation.

> + mutex_unlock(&inst->persistbufs.lock);
> + return 0;
> + }
> + mutex_unlock(&inst->persistbufs.lock);
> +
> + return internal_alloc_and_set(inst, &bufreq, &inst->persistbufs);
> +}
> +
[..]
> +
> +static int scratch_set_buffers(struct vidc_inst *inst)
> +{
> + struct device *dev = inst->core->dev;
> + int ret;
> +
> + ret = scratch_unset_buffers(inst, true);
> + if (ret)
> + dev_warn(dev, "Failed to release scratch buffers\n");

internal_bufs_free() calls scratch_unset_buffers(reuse=false) so we're
coming here with an empty scratchbufs either way - meaning that this
whole file can be greatly simplified.

So instead of trying to fix that I would suggest that you just let
internal_bufs_alloc() acquire the buffer requirements and call
internal_alloc_and_set() directly, storing the result in a single list.

And then inline a free method in internal_bufs_free() as well as drop
all reuse-stuff and unused/dead code.

That would simplify this file quite a bit and if there actually is a
need for the reusing of buffer that can be added at some later time.
Using individual clk pointers instead of this array would make this
"core_clk" easier to follow.

> + struct device *dev = core->dev;
> + unsigned long freq = table[0].freq;
> + int ret, i;
> +
> + if (!mbs_per_sec && num_rows > 1) {
> + freq = table[num_rows - 1].freq;
> + goto set_freq;
> + }

Here we will set freq to the last entry in the freq table, potentially
table[0] if num_rows == 1, so the second part of the conditional doesn't
add any value and you can skip the early initialization above.

And you can put the loop below in an else block instead of using a goto.
> +
> + for (i = 0; i < num_rows; i++) {
> + if (mbs_per_sec > table[i].load)
> + break;
> + freq = table[i].freq;
> + }
> +
> +set_freq:
> +
> + ret = clk_set_rate(clk, freq);
> + if (ret) {
> + dev_err(dev, "failed to set clock rate %lu (%d)\n", freq, ret);
> + return ret;
> + }
> +
> + return 0;

ret will be 0 here, so print the error message conditionally and then
just return ret.

> +}
> +
> +int vidc_scale_clocks(struct vidc_core *core)

This is only called from helpers.c, drop this file and move the
implementation there.

> +{
> + struct device *dev = core->dev;
> + u32 mbs_per_sec;
> + int ret;
> +
> + mbs_per_sec = get_load(core, VIDC_SESSION_TYPE_ENC) +
> + get_load(core, VIDC_SESSION_TYPE_DEC);
> +
> + if (mbs_per_sec > core->res->max_load) {
> + dev_warn(dev, "HW is overloaded, needed: %d max: %d\n",
> + mbs_per_sec, core->res->max_load);
> + return -EBUSY;
> + }
> +
> + ret = scale_clocks_load(core, mbs_per_sec);
> + if (ret)
> + dev_warn(dev, "failed to scale clocks, performance might be impacted\n");
> +
> + return 0;
> +}
[..]
This is a terrible name for a global function.

But I think you can favorably inline this into the two callers. They
both have their own tracking objects. So just drop this entire file.
[..]
Unused

> + .vmem_id = VIDC_RESOURCE_NONE,

Unused

> + .vmem_size = 0,

Unused

> + .vmem_addr = 0,

Unused

> + .dma_mask = 0xddc00000 - 1,
> +};

These tables could with favor be moved next to the of_table in vidc.c

Regards,
Bjorn

Bjorn Andersson

unread,
Aug 22, 2016, 11:30:05 PM8/22/16
to
On Mon 22 Aug 06:13 PDT 2016, Stanimir Varbanov wrote:

> This is the implementation of HFI. It is loaded with the
> responsibility to comunicate with the firmware through an
> interface commands and messages.
>
> - hfi.c has interface functions used by the core, decoder
> and encoder parts to comunicate with the firmware. For example
> there are functions for session and core initialisation.
>

I can't help feeling that the split between core.c and hfi.c is a
remnant of a vidc driver supporting both HFI and pre-HFI with the same
v4l code.

What do you think about merging vidc_core with hfi_core and vidc_inst
with hfi_inst? Both seems to be in a 1:1 relationship.

> - hfi_cmds has packetization operations which preparing
> packets to be send from host to firmware.
>
> - hfi_msgs takes care of messages sent from firmware to the
> host.
>
[..]
> diff --git a/drivers/media/platform/qcom/vidc/hfi_cmds.c b/drivers/media/platform/qcom/vidc/hfi_cmds.c
[..]
> +
> +static const struct hfi_packetization_ops hfi_default = {
> + .sys_init = pkt_sys_init,
> + .sys_pc_prep = pkt_sys_pc_prep,
> + .sys_idle_indicator = pkt_sys_idle_indicator,
> + .sys_power_control = pkt_sys_power_control,
> + .sys_set_resource = pkt_sys_set_resource,
> + .sys_release_resource = pkt_sys_unset_resource,
> + .sys_debug_config = pkt_sys_debug_config,
> + .sys_coverage_config = pkt_sys_coverage_config,
> + .sys_ping = pkt_sys_ping,
> + .sys_image_version = pkt_sys_image_version,
> + .ssr_cmd = pkt_ssr_cmd,
> + .session_init = pkt_session_init,
> + .session_cmd = pkt_session_cmd,
> + .session_set_buffers = pkt_session_set_buffers,
> + .session_release_buffers = pkt_session_release_buffers,
> + .session_etb_decoder = pkt_session_etb_decoder,
> + .session_etb_encoder = pkt_session_etb_encoder,
> + .session_ftb = pkt_session_ftb,
> + .session_parse_seq_header = pkt_session_parse_seq_header,
> + .session_get_seq_hdr = pkt_session_get_seq_hdr,
> + .session_flush = pkt_session_flush,
> + .session_get_property = pkt_session_get_property,
> + .session_set_property = pkt_session_set_property,
> +};
> +
> +static const struct hfi_packetization_ops *get_3xx_ops(void)
> +{
> + static struct hfi_packetization_ops hfi_3xx;
> +
> + hfi_3xx = hfi_default;
> + hfi_3xx.session_set_property = pkt_session_set_property_3xx;
> +
> + return &hfi_3xx;
> +}
> +
> +const struct hfi_packetization_ops *
> +hfi_get_pkt_ops(enum hfi_packetization_type type)

The only reasonable argument I can come up with for not just exposing
these as global functions would be that there are 23 of them... Can we
skip the jump table?

> +{
> + switch (type) {
> + case HFI_PACKETIZATION_LEGACY:
> + return &hfi_default;
> + case HFI_PACKETIZATION_3XX:
> + return get_3xx_ops();
> + }
> +
> + return NULL;
> +}

Regards,
Bjorn

Bjorn Andersson

unread,
Aug 22, 2016, 11:40:05 PM8/22/16
to
On Mon 22 Aug 06:13 PDT 2016, Stanimir Varbanov wrote:

> Here is the implementation of Venus video accelerator low-level
> functionality. It contanins code which setup the registers and
> startup uthe processor, allocate and manipulates with the shared
> memory used for sending commands and receiving messages.
>
> Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
> ---
> drivers/media/platform/qcom/vidc/hfi_venus.c | 1539 +++++++++++++++++++++++
> drivers/media/platform/qcom/vidc/hfi_venus.h | 25 +
> drivers/media/platform/qcom/vidc/hfi_venus_io.h | 98 ++
> 3 files changed, 1662 insertions(+)
> create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.c
> create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.h
> create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus_io.h
>
> diff --git a/drivers/media/platform/qcom/vidc/hfi_venus.c b/drivers/media/platform/qcom/vidc/hfi_venus.c
[..]
Rather than having the core figure out which *_hfi_create() to call I
think this should be the probe() entry point, calling into the core
registering the venus_hfi_ops - a common-probe() in the hfi/vidc core
could still do most of the heavy lifting.

Probing the driver up from the transport rather than for the highest
logical layer allows us to inject a separate hfi_ops for the apr tal
case.
I'll try to find some time to do a more detailed review of the
implementation.

Regards,
Bjorn

Stanimir Varbanov

unread,
Aug 25, 2016, 9:00:06 AM8/25/16
to
Hi Bjorn,

Thanks for the review and comments!

On 08/23/2016 05:50 AM, Bjorn Andersson wrote:
> On Mon 22 Aug 06:13 PDT 2016, Stanimir Varbanov wrote:
>
> Hi Stan,
>
>> This adds core part of the vidc driver common helper functions
>> used by encoder and decoder specific files.
>
> I believe "vidc" is short for "video core" and this is not the only
> "video core" from Qualcomm. This driver is the v4l2 <-> hfi interface and

What other "video core"s do you know?

> uses either two ram based fifos _or_ apr tal for communication with the
> implementation.
>
> In the case of apr, the other side is not the venus core but rather the
> "VIDC" apr service on the Hexagon DSP. In this case the hfi packets are
> encapsulated in apr packets. Although this is not used in 8916 it would
> be nice to be able to add this later...

OK, you are talking about q6_hfi.c which file is found in msm-3.10 and
maybe older kernel versions.

There is a function vidc_hfi_create() which currently creates venus hfi
interface but it aways could be extended to call q6 DSP specific function.

>
>
> But I think we should call this driver "hfi" - or at least venus, as
> it's not compatible with e.g the "blackbird" found in 8064, which is
> also called "vidc".

Do you think that vidc driver for 8064 will ever reach the mainline kernel?

I personally don't like hfi nor venus other suggestions? Does "vidcore"
or "vcore" makes sense?

>
>>
>> - core.c has implemented the platform dirver methods, file
>> operations and v4l2 registration.
>>
>> - helpers.c has implemented common helper functions for
>> buffer management, vb2_ops and functions for format propagation.
>>
>> - int_bufs.c implements functions for allocating and freeing
>> buffers for internal usage. The buffer parameters describing
>> internal buffers depends on current format, resolution and
>> codec.
>>
>> - load.c consists functions for calculation of current load
>> of the hardware. Depending on the count of instances and
>> resolutions it selects the best clock rate for the video
>> core.
>>
>> - mem.c has two functions for memory allocation, currently
>> those functions are used for internal buffers and to allocate
>> the shared memory for communication with firmware via HFI
>> (Host Firmware Interface) interface commands.
>
> Please drop this; see comments on mem_alloc()

OK.
I agree with you. I have thought about this many times during driver
development and it should be possible.
You are right, this checks are redundant.
OK.
OK will remove crash report for now.

>
> However, if we at any point would like to be able to get memory dumps
> from this core (likely a requirement on the Qualcomm side) we need to
> call rproc_report_crash() and let it collect the resources and then
> power cycle the core.
>
>
> As the life cycle of the venus driver goes 1:1 with the rproc driver I
> think it would be more suitable to make the v4l driver a child of the
> rproc driver and have it probe/remove this driver as the rproc comes and
> goes. This would allow us to call rproc_report_crash() here, we will be
> removed and when the crash is handled (sometime in the future) we will
> be probed again.

What's the problem with Kconfig "depends on QCOM_VENUS_PIL", isn't that
enough?
The .event_notify operation is called by hfi part (in hfi_msgs.c) of the
driver and I don't want break the interface. My idea was to have HFI
part and v4l2 part, and each of these parts taking care of their
specifics. The interface between HFI <-> v4l2 should be immutable and
shoudn't be changed when every new version of the hardware IP rise up.

>
> Just replace the two affected event_notify() calls with a direct call to
> this function (and clean it up a bit).
>
>> +
>> +static int vidc_open(struct file *file)
>> +{
>> + struct video_device *vdev = video_devdata(file);
>> + struct vidc_core *core = video_drvdata(file);
>> + struct vidc_inst *inst;
>> + int ret = 0;
>> +
>> + inst = kzalloc(sizeof(*inst), GFP_KERNEL);
>> + if (!inst)
>> + return -ENOMEM;
>> +
>> + mutex_init(&inst->lock);
>> +
>> + INIT_VIDC_LIST(&inst->scratchbufs);
>
> Please inline the mutex_init() and INIT_LIST_HEAD() here and drop the
> custom INIT_VIDC_LIST() wrapper macro.

OK. I thought I made this already, but seems that I forgot it. Also I
think scratchbufs and persistbufs lists can be merged in one common list.
OK.
yes indeed :)
Yes it looks like a hack but there is no other way (to my knowledge),
there are plenty of v4l2 drivers doing like this.
IMO the proper place is platform driver .probe method.

>
> Also, these two functions arrives in a later patchset, so I assume this
> doesn't compile...

That's why I'm adding Makefiles later on patchset. On the other hand I
have splitted the driver by files because I think it is easier for
review. But I might be wrong.
OK.

>
>> +
>> +static int vidc_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct vidc_core *core;
>> + struct device_node *rproc;
>> + struct resource *r;
>> + int ret;
>> +
>> + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
>> + if (!core)
>> + return -ENOMEM;
>> +
>> + core->dev = dev;
>> + platform_set_drvdata(pdev, core);
>> +
>> + rproc = of_parse_phandle(dev->of_node, "rproc", 0);
>> + if (IS_ERR(rproc))
>> + return PTR_ERR(rproc);
>> +
>> + core->rproc = rproc_get_by_phandle(rproc->phandle);
>
> FYI, We're hoping to land some patches shortly that will replace this
> with rproc_get(pdev->dev.of_node), looking up an rproc by the standard
> "rprocs" property...

OK, that looks good. But shoudn't be rproc_get(pdev->dev)?

>
>> + if (IS_ERR(core->rproc))
>> + return PTR_ERR(core->rproc);
>> + else if (!core->rproc)
>> + return -EPROBE_DEFER;
>
> We're cleaning up this in the core as well.
>
> You need to rproc_put() the rproc pointer after this point.

OK, good. When those changes landed I will rework this part.

>
>
> My question still stands though, if this driver should be probed as the
> remoteproc is booted (or the apr service appearing). I will continue to
> look at that.

I might be misunderstood your point here. Is your concern related to
EPROBE_DEFFER or some sort of ordering issue in rproc?

Currently the vidc depends on QCOM_VENUS_PIL in Kconfig, so modprobe
vidc should modprobe remoteproc driver cause it depends on it.
OK can do that, but is there a convention about who is populating the
flag and what is the precedence dt against the driver?
So you saying that
Hmm, I don't agree here. The runtime_resume will enable clocks (for
example venus iface clk) and if it fails the subsequent call to
vidc_rproc_shutdown can crash badly.
used in vdec.c and enc.c. But I can delete this in next submission.

>
>> +
>> +struct vidc_list {
>> + struct list_head list;
>> + struct mutex lock;
>> +};
>
> Can't we get away without passing around lockable lists? Does these
> lists have to be locked independently and should we really pass around
> their lock with them?

I guess it is possible but didn't spent to much time on that (I had more
important problems to solve with downstream driver). So the answer is
yes and I have to re-consider it.

>
>> +
>> +struct vidc_format {
>> + u32 pixfmt;
>> + int num_planes;
>> + u32 type;
>> +};
>> +
>> +struct vidc_core {
>> + struct list_head list;
>
> This list_head seems unused.

Yes, it is.

>
>> + void __iomem *base;
>
> base is acquired and passed by value to vidc_hfi_create(), so no need to
> keep track of it here.
>
>> + int irq;
>
> This irq belongs to hfi, so it should probably be kept there.

Sure, will move those two.

>
>> + struct clk *clks[VIDC_CLKS_NUM_MAX];
>> + struct mutex lock;
>
> This "lock" seems to be only related the instances list, please name it
> more appropriately - and place it next to the instances member.

OK.
OK.
Very nice idea, thanks.

>
>> +
>> +static inline void INIT_VIDC_LIST(struct vidc_list *mlist)
>> +{
>> + mutex_init(&mlist->lock);
>> + INIT_LIST_HEAD(&mlist->list);
>> +}
>> +
>> +static inline struct vidc_inst *to_inst(struct file *filp)
>> +{
>> + return container_of(filp->private_data, struct vidc_inst, fh);
>> +}
>> +
>> +static inline struct hfi_inst *to_hfi_inst(struct file *filp)
>
> Unused

Unsed in this patch, but used by vdec.c and enc.c in subsequent patches.

>
>> +{
>> + return to_inst(filp)->hfi_inst;
>> +}
>> +
>> +static inline struct vb2_queue *
>> +vidc_to_vb2q(struct file *file, enum v4l2_buf_type type)
>
> Unused

Same as above comment.
yes, registeredbufs.list is used to keep track of the all buffers that
will be used during the life-cycle of the current session, i.e. the
firmware wants to know all buffer addresses before calling
session_start. On the other side bufqueue list is used for v4l2
queue/dequeue side.
the original name of this function was similar to what you suggest but I
decided in last-minute cleanup to shorten its name.

> just have the internal buffer manager call it directly.

It is in the core cause I used it on few places to gather buffer count
needed depending on parameters (resolution, codec). Good example is
vb2_ops::queue_setup where I need to return num_buffers depending on
resolution, codec, bitrate, framerate and so on.

>
> The call doesn't seem to depend on the parameters or state, can we
> cache the result?

No, we cannot. see above comment. Something more the scratch and
prersist buffer sizes can also be changed by the firmware depending on
above proparties.
Probably cause its buggy, I will sort it out.

>
>> +
>> + ret = vidc_hfi_session_stop(hfi, inst->hfi_inst);
>> + if (ret) {
>> + dev_err(dev, "session: stop failed (%d)\n", ret);
>> + goto abort;
>
> When are we going to relaim the buffers in these cases?

session_stop will instruct the firmware return buffers to the v4l2
driver through hfi_inst_ops empty_buf_done and fill_buf_done, those
operations will call vb2_buffer_done.
OK, I will give it a try.
Actially I thought about droping the reuse stuff in the past, so I agree
on that cleanup. The thing which worries me is the size of those buffers
(the biggest is 10-15MB) and also the allocation time. Currently those
buffers are allocate on streamon time, but probably the right place is
on request_buf time.
I have decided to use an array of struct clk pointers is that the number
of clocks depends on SoC, for example 8096 have 9 clks and I wanted to
avoid describing each of them as idividual one.

>
>> + struct device *dev = core->dev;
>> + unsigned long freq = table[0].freq;
>> + int ret, i;
>> +
>> + if (!mbs_per_sec num_row&& s > 1) {
>> + freq = table[num_rows - 1].freq;
>> + goto set_freq;
>> + }
>
> Here we will set freq to the last entry in the freq table, potentially
> table[0] if num_rows == 1, so the second part of the conditional doesn't
> add any value and you can skip the early initialization above.

Ok I will reconsider this part.

>
> And you can put the loop below in an else block instead of using a goto.
>> +
>> + for (i = 0; i < num_rows; i++) {
>> + if (mbs_per_sec > table[i].load)
>> + break;
>> + freq = table[i].freq;
>> + }
>> +
>> +set_freq:
>> +
>> + ret = clk_set_rate(clk, freq);
>> + if (ret) {
>> + dev_err(dev, "failed to set clock rate %lu (%d)\n", freq, ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>
> ret will be 0 here, so print the error message conditionally and then
> just return ret.
>
>> +}
>> +
>> +int vidc_scale_clocks(struct vidc_core *core)
>
> This is only called from helpers.c, drop this file and move the
> implementation there.

OK, agreed.
OK, I will delete it.
hfi_version is used from vidc_hfi_create() to decide which packetization
type "3xx" or "legacy" to use. Currently msm8916 use "legacy" but I
guess msm8996 will use "3xx"

>
>> + .vmem_id = VIDC_RESOURCE_NONE,
>
> Unused

Some of the planed SoCs to support with this driver has this fast video
RAM memory, despite that msm8916 has not.

>
>> + .vmem_size = 0,
>
> Unused

this is for next SoCs which we will support.

>
>> + .vmem_addr = 0,
>
> Unused

same comment as above.

>
>> + .dma_mask = 0xddc00000 - 1,
>> +};
>
> These tables could with favor be moved next to the of_table in vidc.c

yes, makes sense.

--
regards,
Stan

Bjorn Andersson

unread,
Aug 25, 2016, 2:30:06 PM8/25/16
to
On Thu 25 Aug 05:59 PDT 2016, Stanimir Varbanov wrote:

> Hi Bjorn,
>
> Thanks for the review and comments!
>
> On 08/23/2016 05:50 AM, Bjorn Andersson wrote:
> > On Mon 22 Aug 06:13 PDT 2016, Stanimir Varbanov wrote:
> >
> > Hi Stan,
> >
> >> This adds core part of the vidc driver common helper functions
> >> used by encoder and decoder specific files.
> >
> > I believe "vidc" is short for "video core" and this is not the only
> > "video core" from Qualcomm. This driver is the v4l2 <-> hfi interface and
>
> What other "video core"s do you know?
>

The blackbird, or "vidc" as we know it in 8064. That one should likely
be named "mfc" though.

> > uses either two ram based fifos _or_ apr tal for communication with the
> > implementation.
> >
> > In the case of apr, the other side is not the venus core but rather the
> > "VIDC" apr service on the Hexagon DSP. In this case the hfi packets are
> > encapsulated in apr packets. Although this is not used in 8916 it would
> > be nice to be able to add this later...
>
> OK, you are talking about q6_hfi.c which file is found in msm-3.10 and
> maybe older kernel versions.
>

That's the one.

> There is a function vidc_hfi_create() which currently creates venus hfi
> interface but it aways could be extended to call q6 DSP specific function.
>

As the ADSP comes up with a VIDC service (on the applicable platform(s))
we get an APR channel, the concept is similar from then on but rather
than putting the messages directly into the venus hfi tx/rx fifos a
header is prepended to each hfi message and it's passed to the APR.

In the Qualcomm code the hfi ops are picked as vidc_hfi_create() is
called, but I think flipping that upside down to have the venus_hfi or
q6_hfi be the probe point and then calling the common probe part with an
ops struct associated would be more natural.

I do however not see a problem with doing such refactoring in the
future, I just wanted to bring it up.

> >
> >
> > But I think we should call this driver "hfi" - or at least venus, as
> > it's not compatible with e.g the "blackbird" found in 8064, which is
> > also called "vidc".
>
> Do you think that vidc driver for 8064 will ever reach the mainline kernel?
>

There are strong wishes for it to be supported, so we should take
reasonable measures to make sure its possible.

> I personally don't like hfi nor venus other suggestions? Does "vidcore"
> or "vcore" makes sense?
>

These names would imply that we intend to have a single driver for all Qualcomm
video encoder/decoders.

As it's really hard to envision the future, or to argue about 8064 vidc,
I would rather see a driver for the hfi based video encoder/decoders.

If we in 8996+1 see a non-hfi chip benefit greatly from reusing
something from this implementation then those better go into the v4l
core or some other common entity.

[..]
> >> diff --git a/drivers/media/platform/qcom/vidc/core.c b/drivers/media/platform/qcom/vidc/core.c
[..]
I mean in runtime, not compile time.

Devices instantiated from this driver does not serve a purpose without
either the venus rproc running or the adsp running. Further more as we
detect an issue with the remote core these resources (the venus or adsp)
will be stopped and restarted - potentially with a long delay inbetween
for ramdumping.

So either the venus driver must be resilient towards the remote being
gone or we should tie the venus driver to the running state of the
remoteproc driver.

One way to do that would be to add a of_platform_populate() in
venus_start() and a of_platform_depopulate() in venus_stop() and
represent the hfi-venus driver as a child of the remoteproc driver.
I'm fine with having the structural split between the two pieces
(although I see it serving little purpose), but I don't think it's
necessary to use a function pointer interface for something that always
only will have a single possible value.

> >
> > Just replace the two affected event_notify() calls with a direct call to
> > this function (and clean it up a bit).
> >
> >> +
[..]
> >> +
> >> +static irqreturn_t vidc_isr_thread(int irq, void *dev_id)
> >> +{
> >> + return vidc_hfi_isr_thread(irq, dev_id);
> >> +}
> >> +
> >> +static irqreturn_t vidc_isr(int irq, void *dev)
> >> +{
> >> + return vidc_hfi_isr(irq, dev);
> >> +}
> >
> > These two functions indicates that we're requesting the irq in the wrong
> > layer.
>
> IMO the proper place is platform driver .probe method.
>

If we squash the vidc-core and hfi-core into one this oddness should go
away, right?

> >
> > Also, these two functions arrives in a later patchset, so I assume this
> > doesn't compile...
>
> That's why I'm adding Makefiles later on patchset. On the other hand I
> have splitted the driver by files because I think it is easier for
> review. But I might be wrong.
>

Ok, I'm afraid I don't have any good suggestion to counter this. It's
hard to introduce a minimal functional driver for this and then add
features - as any "minimal" version is large.


Sorry for all the "unused" comments for later patches, I didn't double
check those with the end result.

> >
> >> +
[..]
> >> +
> >> +static int vidc_probe(struct platform_device *pdev)
> >> +{
> >> + struct device *dev = &pdev->dev;
> >> + struct vidc_core *core;
> >> + struct device_node *rproc;
> >> + struct resource *r;
> >> + int ret;
> >> +
> >> + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
> >> + if (!core)
> >> + return -ENOMEM;
> >> +
> >> + core->dev = dev;
> >> + platform_set_drvdata(pdev, core);
> >> +
> >> + rproc = of_parse_phandle(dev->of_node, "rproc", 0);
> >> + if (IS_ERR(rproc))
> >> + return PTR_ERR(rproc);
> >> +
> >> + core->rproc = rproc_get_by_phandle(rproc->phandle);
> >
> > FYI, We're hoping to land some patches shortly that will replace this
> > with rproc_get(pdev->dev.of_node), looking up an rproc by the standard
> > "rprocs" property...
>
> OK, that looks good. But shoudn't be rproc_get(pdev->dev)?
>

Sorry, the patches in flight introduces of_rproc_get() - not
rproc_get(), which takes a device_node. But it's still pending some DT
discussions.

> >
> >> + if (IS_ERR(core->rproc))
> >> + return PTR_ERR(core->rproc);
> >> + else if (!core->rproc)
> >> + return -EPROBE_DEFER;
> >
> > We're cleaning up this in the core as well.
> >
> > You need to rproc_put() the rproc pointer after this point.
>
> OK, good. When those changes landed I will rework this part.
>
> >
> >
> > My question still stands though, if this driver should be probed as the
> > remoteproc is booted (or the apr service appearing). I will continue to
> > look at that.
>
> I might be misunderstood your point here. Is your concern related to
> EPROBE_DEFFER or some sort of ordering issue in rproc?
>
> Currently the vidc depends on QCOM_VENUS_PIL in Kconfig, so modprobe
> vidc should modprobe remoteproc driver cause it depends on it.
>

If we probe the two drivers independently, then you're good. I just
don't see how we (sanely) will communicate the information about when
the remote goes down and comes up related to a crash.
It seems there's a push lately for moving to getting the trigger from
DT. But I haven't not studied the details or reasoning behind it.
I'm saying that I would like for the rproc driver to be able to boot,
shutdown and handle a crash (not trigger) without having to depend on
the venus driver setting up a bunch of resources for it.

Either that or the venus rproc driver should not be a standalone rproc
driver - with its own probe function.
Sorry for not being clear, that part we care about. What I meant was
that in the device core will dismantle your driver regardless of you
returning an error here. So you will leak the resources not freed below.

> >
> >> +
> >> + ret = vidc_hfi_core_deinit(&core->hfi);
> >> + if (ret) {
> >> + pm_runtime_put_sync(&pdev->dev);
> >> + return ret;
> >> + }
> >> +
> >> + vidc_rproc_shutdown(core);
> >> +
> >> + ret = pm_runtime_put_sync(&pdev->dev);
> >> +
> >> + vidc_hfi_destroy(&core->hfi);
> >> + vdec_deinit(core, &core->vdev_dec);
> >> + venc_deinit(core, &core->vdev_enc);
> >> + v4l2_device_unregister(&core->v4l2_dev);
> >> +
> >> + pm_runtime_disable(core->dev);
> >> +
> >> + return ret < 0 ? ret : 0;
> >> +}
> >> +
[..]
> >> diff --git a/drivers/media/platform/qcom/vidc/core.h b/drivers/media/platform/qcom/vidc/core.h
[..]
>
> >
> >> +
> >> +struct vidc_list {
> >> + struct list_head list;
> >> + struct mutex lock;
> >> +};
> >
> > Can't we get away without passing around lockable lists? Does these
> > lists have to be locked independently and should we really pass around
> > their lock with them?
>
> I guess it is possible but didn't spent to much time on that (I had more
> important problems to solve with downstream driver). So the answer is
> yes and I have to re-consider it.
>

We with rework of internal buffers this should be less of a problem, you
could with favor grab the one lock, allocate all internal buffers and
then unlock the lock again.

[..]
> >> diff --git a/drivers/media/platform/qcom/vidc/helpers.c b/drivers/media/platform/qcom/vidc/helpers.c
[..]
> >> +int vidc_buf_descs(struct vidc_inst *inst, u32 type,
> >> + struct hfi_buffer_requirements *out)
> >
> > If you call this vidc_get_buf_requirements() it would actually describe
> > what's going on. But why is this hfi wrapper in the core, rather than
>
> the original name of this function was similar to what you suggest but I
> decided in last-minute cleanup to shorten its name.
>

vidc_get_bufreq() should be short enough :)

> > just have the internal buffer manager call it directly.
>
> It is in the core cause I used it on few places to gather buffer count
> needed depending on parameters (resolution, codec). Good example is
> vb2_ops::queue_setup where I need to return num_buffers depending on
> resolution, codec, bitrate, framerate and so on.
>

Ok, that makes sense.

> >
> > The call doesn't seem to depend on the parameters or state, can we
> > cache the result?
>
> No, we cannot. see above comment. Something more the scratch and
> prersist buffer sizes can also be changed by the firmware depending on
> above proparties.
>

Okay, I figured that might be the case.
Okay, but does that include the internal buffers as well?
[..]
> >> diff --git a/drivers/media/platform/qcom/vidc/helpers.h b/drivers/media/platform/qcom/vidc/helpers.h
[..]
> >> +static int scratch_set_buffers(struct vidc_inst *inst)
> >> +{
> >> + struct device *dev = inst->core->dev;
> >> + int ret;
> >> +
> >> + ret = scratch_unset_buffers(inst, true);
> >> + if (ret)
> >> + dev_warn(dev, "Failed to release scratch buffers\n");
> >
> > internal_bufs_free() calls scratch_unset_buffers(reuse=false) so we're
> > coming here with an empty scratchbufs either way - meaning that this
> > whole file can be greatly simplified.
> >
> > So instead of trying to fix that I would suggest that you just let
> > internal_bufs_alloc() acquire the buffer requirements and call
> > internal_alloc_and_set() directly, storing the result in a single list.
> >
> > And then inline a free method in internal_bufs_free() as well as drop
> > all reuse-stuff and unused/dead code.
> >
> > That would simplify this file quite a bit and if there actually is a
> > need for the reusing of buffer that can be added at some later time.
> >
>
> Actially I thought about droping the reuse stuff in the past, so I agree
> on that cleanup. The thing which worries me is the size of those buffers
> (the biggest is 10-15MB) and also the allocation time. Currently those
> buffers are allocate on streamon time, but probably the right place is
> on request_buf time.
>

I think this is definitely worth investigating and should be possible to
add incrementally once the driver has landed. As far as I can see the
code doesn't really work as it is now, so rather than fixing that make
it dead-simple and then add to it later.

> >> +
> >> + ret = scratch_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH);
> >> + if (ret)
> >> + goto error;
> >> +
> >> + ret = scratch_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH_1);
> >> + if (ret)
> >> + goto error;
> >> +
> >> + ret = scratch_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH_2);
> >> + if (ret)
> >> + goto error;
> >> +
> >> + return 0;
> >> +error:
> >> + scratch_unset_buffers(inst, false);
> >> + return ret;
> >> +}
> >> +

Regards,
Bjorn

Stanimir Varbanov

unread,
Aug 26, 2016, 10:30:15 AM8/26/16
to
Hi Bjorn,

Thanks for the comments!

On 08/23/2016 06:25 AM, Bjorn Andersson wrote:
> On Mon 22 Aug 06:13 PDT 2016, Stanimir Varbanov wrote:
>
>> This is the implementation of HFI. It is loaded with the
>> responsibility to comunicate with the firmware through an
>> interface commands and messages.
>>
>> - hfi.c has interface functions used by the core, decoder
>> and encoder parts to comunicate with the firmware. For example
>> there are functions for session and core initialisation.
>>
>
> I can't help feeling that the split between core.c and hfi.c is a
> remnant of a vidc driver supporting both HFI and pre-HFI with the same
> v4l code.
>
> What do you think about merging vidc_core with hfi_core and vidc_inst
> with hfi_inst? Both seems to be in a 1:1 relationship.

OK, I can give it a try.
Of course we can, but what will be the benefit, increased readability ?
Let's keep it for now. Once the driver is merged some smarter guy could
came up with better solution ;)

>
>> +{
>> + switch (type) {
>> + case HFI_PACKETIZATION_LEGACY:
>> + return &hfi_default;
>> + case HFI_PACKETIZATION_3XX:
>> + return get_3xx_ops();
>> + }
>> +
>> + return NULL;
>> +}
>
> Regards,
> Bjorn
>

--
regards,
Stan

Hans Verkuil

unread,
Sep 5, 2016, 10:50:06 AM9/5/16
to
On 08/22/2016 03:13 PM, Stanimir Varbanov wrote:
> This patchset introduces a basic support for Qualcomm video
> acceleration hardware used for video stream decoding/encoding.
> The video IP can found on various qcom SoCs like apq8084, msm8916
> and msm8996, hence it is widly distributed but the driver is
> missing in the mainline.
>
> The v4l2 driver is something like a wrapper over Host Firmware
> Interface. The HFI itself is a set of command and message packets
> send/received through shared memory, and its purpose is to
> comunicate with the firmware which is run on remote processor.
> The Venus is the name of the video hardware IP that doing the
> video acceleration.
>
> From the software point of view the HFI interface is implemented
> in the files with prefix hfi_xxx. It acts as a translation layer
> between HFI and v4l2 layer. There is one special file in the
> driver called hfi_venus which doing most of the driver
> orchestration work. Something more it setups Venus core, run it
> and handle commands and messages from low-level point of view with
> the help of provided functions by HFI interface.
>
> I think that the driver is in good shape for mainline kernel, and
> I hope the review comments will help to improve it, so please
> do review and make comments.

I was hoping that I could finish reviewing this patch series today,
but that didn't work out.

I have more time next week, but I wonder if it isn't better if you make a
v2 first, taking my comments into account. Then I can review v2 next week.

Also test with the latest v4l2-compliance (i.e. as of today) since I improved
a few tests relating to g/s_selection and g/s_parm.

Regards,

Hans

Stanimir Varbanov

unread,
Sep 7, 2016, 5:00:05 AM9/7/16
to
Hi Hans,
OK, I have more of your comments addressed plus few of Bjorn's too.

>
> Also test with the latest v4l2-compliance (i.e. as of today) since I improved
> a few tests relating to g/s_selection and g/s_parm.

Sure, I will retest and post the results in cover letter.

--
regards,
Stan

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:06 AM9/7/16
to
This adds changes in v4l2 platform directory to include the
vidc driver and show it in kernel config.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
2 files changed, 2 insertions(+)

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index f25344bc7912..e52640417b0a 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -111,6 +111,7 @@ source "drivers/media/platform/s5p-tv/Kconfig"
source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
+source "drivers/media/platform/qcom/Kconfig"

config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 21771c1a13fb..d8fc75ddcc73 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o
obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/

obj-y += omap/
+obj-y += qcom/

obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x/

--
2.7.4

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:06 AM9/7/16
to
Changes since v1:
- s/ENOTSUPP/EINVAL in vidc_set_color_format()
- use video_device_alloc
- fill struct device pointer in vb2_queue_init instead of .setup_queue
- fill device_caps in struct video_device instead of .vidioc_querycap
- fill colorspace, ycbcr_enc, quantization and xfer_func only when type
is V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
- delete rproc_boot|shutdown wrappers in core.c
- delete unused list_head in struct vidc_core
- delete mem.c|h and inline the function where they were used
- delete int_bufs.c|h files and move the functions in helpers.c, also
cleanup the code by removing buffer reuse mechanism
- inline INIT_VIDC_LIST macro
- rename queue to other_queue in .start_streaming for encoder and
decoder
- renamed vidc_buf_descs -> vidc_get_bufreq
- optimize instance checks in vidc_open
- moved resources structure in core.c and delete resources.c|h
- delete streamon and streamoff flags

The previous v1 can be found at [1].

[1] https://lkml.org/lkml/2016/8/22/308

The output of v4l2-compliance for decoder and encoder are:

root@dragonboard-410c:/home/linaro# ./v4l2-compliance -d /dev/video0
v4l2-compliance SHA : abc1453dfe89f244dccd3460d8e1a2e3091cbadb
v4l2-compliance SHA : abc1453dfe89f244dccd3460d8e1a2e3091cbadb
Stanimir Varbanov (8):
doc: DT: vidc: binding document for Qualcomm video driver
media: vidc: adding core part and helper functions
media: vidc: decoder: add video decoder files
media: vidc: encoder: add video encoder files
media: vidc: add Host Firmware Interface (HFI)
media: vidc: add Venus HFI files
media: vidc: add Makefiles and Kconfig files
media: vidc: enable building of the video codec driver

.../devicetree/bindings/media/qcom,vidc.txt | 61 +
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/qcom/Kconfig | 8 +
drivers/media/platform/qcom/Makefile | 6 +
drivers/media/platform/qcom/vidc/Makefile | 15 +
drivers/media/platform/qcom/vidc/core.c | 559 +++++++
drivers/media/platform/qcom/vidc/core.h | 207 +++
drivers/media/platform/qcom/vidc/helpers.c | 604 ++++++++
drivers/media/platform/qcom/vidc/helpers.h | 43 +
drivers/media/platform/qcom/vidc/hfi.c | 617 ++++++++
drivers/media/platform/qcom/vidc/hfi.h | 272 ++++
drivers/media/platform/qcom/vidc/hfi_cmds.c | 1261 ++++++++++++++++
drivers/media/platform/qcom/vidc/hfi_cmds.h | 338 +++++
drivers/media/platform/qcom/vidc/hfi_helper.h | 1143 +++++++++++++++
drivers/media/platform/qcom/vidc/hfi_msgs.c | 1072 ++++++++++++++
drivers/media/platform/qcom/vidc/hfi_msgs.h | 298 ++++
drivers/media/platform/qcom/vidc/hfi_venus.c | 1534 ++++++++++++++++++++
drivers/media/platform/qcom/vidc/hfi_venus.h | 25 +
drivers/media/platform/qcom/vidc/hfi_venus_io.h | 98 ++
drivers/media/platform/qcom/vidc/vdec.c | 1091 ++++++++++++++
drivers/media/platform/qcom/vidc/vdec.h | 29 +
drivers/media/platform/qcom/vidc/vdec_ctrls.c | 200 +++
drivers/media/platform/qcom/vidc/vdec_ctrls.h | 21 +
drivers/media/platform/qcom/vidc/venc.c | 1252 ++++++++++++++++
drivers/media/platform/qcom/vidc/venc.h | 29 +
drivers/media/platform/qcom/vidc/venc_ctrls.c | 396 +++++
drivers/media/platform/qcom/vidc/venc_ctrls.h | 23 +
28 files changed, 11204 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,vidc.txt
create mode 100644 drivers/media/platform/qcom/Kconfig
create mode 100644 drivers/media/platform/qcom/Makefile
create mode 100644 drivers/media/platform/qcom/vidc/Makefile
create mode 100644 drivers/media/platform/qcom/vidc/core.c
create mode 100644 drivers/media/platform/qcom/vidc/core.h
create mode 100644 drivers/media/platform/qcom/vidc/helpers.c
create mode 100644 drivers/media/platform/qcom/vidc/helpers.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_cmds.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_cmds.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_helper.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_msgs.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_msgs.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus_io.h
create mode 100644 drivers/media/platform/qcom/vidc/vdec.c
create mode 100644 drivers/media/platform/qcom/vidc/vdec.h
create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.h
create mode 100644 drivers/media/platform/qcom/vidc/venc.c
create mode 100644 drivers/media/platform/qcom/vidc/venc.h
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.h

--
2.7.4

Hans Verkuil

unread,
Sep 7, 2016, 7:50:06 AM9/7/16
to
One thing you need to add is a patch for the MAINTAINERS file. It needs
an entry for this new driver.

I think pretty much all new drivers I've reviewed recently forgot to update
that file :-(

Regards,

Hans

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:10 AM9/7/16
to
This adds core part of the vidc driver common helper functions
used by encoder and decoder specific files.

* core.c has implemented the platform dirver methods, file
operations and v4l2 registration.

* helpers.c has implemented common helper functions for:
- buffer management

- vb2_ops and functions for format propagation,

- functions for allocating and freeing buffers for
internal usage. The buffer parameters describing internal
buffers depends on current format, resolution and codec.

- functions for calculation of current load of the
hardware. Depending on the count of instances and
resolutions it selects the best clock rate for the video
core.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/core.c | 559 ++++++++++++++++++++++++++
drivers/media/platform/qcom/vidc/core.h | 207 ++++++++++
drivers/media/platform/qcom/vidc/helpers.c | 604 +++++++++++++++++++++++++++++
drivers/media/platform/qcom/vidc/helpers.h | 43 ++
4 files changed, 1413 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/core.c
create mode 100644 drivers/media/platform/qcom/vidc/core.h
create mode 100644 drivers/media/platform/qcom/vidc/helpers.c
create mode 100644 drivers/media/platform/qcom/vidc/helpers.h

diff --git a/drivers/media/platform/qcom/vidc/core.c b/drivers/media/platform/qcom/vidc/core.c
new file mode 100644
index 000000000000..0e495f456721
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/core.c
@@ -0,0 +1,559 @@
+#include "vdec.h"
+#include "venc.h"
+
+static void vidc_add_inst(struct vidc_core *core, struct vidc_inst *inst)
+{
+ mutex_lock(&core->lock);
+ list_add_tail(&inst->list, &core->instances);
+ mutex_unlock(&core->lock);
+}
+
+static void vidc_del_inst(struct vidc_core *core, struct vidc_inst *inst)
+{
+ struct vidc_inst *pos, *n;
+
+ mutex_lock(&core->lock);
+ list_for_each_entry_safe(pos, n, &core->instances, list) {
+ if (pos == inst)
+ list_del(&inst->list);
+ }
+ mutex_unlock(&core->lock);
+}
+
+struct vidc_sys_error {
+ struct vidc_core *core;
+ struct delayed_work work;
+};
+
+static void vidc_sys_error_handler(struct work_struct *work)
+{
+ struct vidc_sys_error *handler =
+ container_of(work, struct vidc_sys_error, work.work);
+ struct vidc_core *core = handler->core;
+ struct hfi_core *hfi = &core->hfi;
+ struct device *dev = core->dev;
+ int ret;
+
+ mutex_lock(&hfi->lock);
+ if (hfi->state != CORE_INVALID)
+ goto exit;
+
+ mutex_unlock(&hfi->lock);
+
+ ret = vidc_hfi_core_deinit(hfi);
+ if (ret)
+ dev_err(dev, "core: deinit failed (%d)\n", ret);
+
+ mutex_lock(&hfi->lock);
+
+ rproc_report_crash(core->rproc, RPROC_FATAL_ERROR);
+
+ rproc_shutdown(core->rproc);
+
+ ret = rproc_boot(core->rproc);
+}
+
+static const struct hfi_core_ops vidc_core_ops = {
+ .event_notify = vidc_event_notify,
+};
+
+static int vidc_open(struct file *file)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct vidc_core *core = video_drvdata(file);
+ struct vidc_inst *inst;
+ int ret;
+
+ inst = kzalloc(sizeof(*inst), GFP_KERNEL);
+ if (!inst)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&inst->registeredbufs);
+ mutex_init(&inst->registeredbufs_lock);
+
+ INIT_LIST_HEAD(&inst->internalbufs);
+ mutex_init(&inst->internalbufs_lock);
+
+ INIT_LIST_HEAD(&inst->bufqueue);
+ mutex_init(&inst->bufqueue_lock);
+
+ inst->core = core;
+
+ if (vdev == core->vdev_dec) {
+ inst->session_type = VIDC_SESSION_TYPE_DEC;
+ ret = vdec_open(inst);
+ if (ret)
+ goto err_free_inst;
+ v4l2_fh_init(&inst->fh, core->vdev_dec);
+ } else {
+ inst->session_type = VIDC_SESSION_TYPE_ENC;
+ ret = venc_open(inst);
+ if (ret)
+ goto err_free_inst;
+ v4l2_fh_init(&inst->fh, core->vdev_enc);
+ }
+
+ inst->fh.ctrl_handler = &inst->ctrl_handler;
+ v4l2_fh_add(&inst->fh);
+ file->private_data = &inst->fh;
+ vidc_add_inst(core, inst);
+
+ return 0;
+
+err_free_inst:
+ kfree(inst);
+ return ret;
+}
+
+static int vidc_close(struct file *file)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct vidc_core *core = inst->core;
+
+ if (inst->session_type == VIDC_SESSION_TYPE_DEC)
+ vdec_close(inst);
+ else
+ venc_close(inst);
+
+ vidc_del_inst(core, inst);
+
+ mutex_destroy(&inst->bufqueue_lock);
+ mutex_destroy(&inst->registeredbufs_lock);
+ mutex_destroy(&inst->internalbufs_lock);
+
+ v4l2_fh_del(&inst->fh);
+ v4l2_fh_exit(&inst->fh);
+
+ kfree(inst);
+ return 0;
+}
+
+static unsigned int vidc_poll(struct file *file, struct poll_table_struct *pt)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct vb2_queue *outq = &inst->bufq_out;
+ struct vb2_queue *capq = &inst->bufq_cap;
+ unsigned int ret;
+
+ ret = vb2_poll(outq, file, pt);
+ ret |= vb2_poll(capq, file, pt);
+
+ return ret;
+}
+
+static int vidc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct vidc_inst *inst = to_inst(file);
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ int ret;
+
+ if (offset < DST_QUEUE_OFF_BASE) {
+ ret = vb2_mmap(&inst->bufq_out, vma);
+ } else {
+ vma->vm_pgoff -= DST_QUEUE_OFF_BASE >> PAGE_SHIFT;
+ ret = vb2_mmap(&inst->bufq_cap, vma);
+ }
+
+ return ret;
+}
+
+const struct v4l2_file_operations vidc_fops = {
+ .owner = THIS_MODULE,
+ .open = vidc_open,
+ .release = vidc_close,
+ .unlocked_ioctl = video_ioctl2,
+ .poll = vidc_poll,
+ .mmap = vidc_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+};
+
+static irqreturn_t vidc_isr_thread(int irq, void *dev_id)
+{
+ return vidc_hfi_isr_thread(irq, dev_id);
+}
+
+static irqreturn_t vidc_isr(int irq, void *dev)
+{
+ return vidc_hfi_isr(irq, dev);
+}
+
+static int vidc_clks_get(struct vidc_core *core, unsigned int clks_num,
+ const char * const *clks_id)
+{
+ struct device *dev = core->dev;
+ unsigned int i;
+
+ for (i = 0; i < clks_num; i++) {
+ core->clks[i] = devm_clk_get(dev, clks_id[i]);
+ if (IS_ERR(core->clks[i]))
+ return PTR_ERR(core->clks[i]);
+ }
+
+ return 0;
+}
+
+static int
+vidc_clks_enable(struct vidc_core *core, const struct vidc_resources *res)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < res->clks_num; i++) {
+ ret = clk_prepare_enable(core->clks[i]);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+err:
+ while (--i)
+ clk_disable_unprepare(core->clks[i]);
+
+ return ret;
+}
+
+static void
+vidc_clks_disable(struct vidc_core *core, const struct vidc_resources *res)
+{
+ unsigned int i;
+
+ for (i = 0; i < res->clks_num; i++)
+ clk_disable_unprepare(core->clks[i]);
+}
+
+static int vidc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct vidc_core *core;
+ struct device_node *rproc;
+ struct resource *r;
+ int ret;
+
+ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return -ENOMEM;
+
+ core->dev = dev;
+ platform_set_drvdata(pdev, core);
+
+ rproc = of_parse_phandle(dev->of_node, "rproc", 0);
+ if (IS_ERR(rproc))
+ return PTR_ERR(rproc);
+
+ core->rproc = rproc_get_by_phandle(rproc->phandle);
+ if (IS_ERR(core->rproc))
+ return PTR_ERR(core->rproc);
+ else if (!core->rproc)
+ return -EPROBE_DEFER;
+ "vidc", &core->hfi);
+ if (ret)
+ return ret;
+
+ core->hfi.core_ops = &vidc_core_ops;
+ core->hfi.dev = dev;
+
+ ret = vidc_hfi_create(&core->hfi, core->res, core->base);
+ if (ret)
+ return ret;
+
+ ret = vidc_clks_enable(core, core->res);
+ if (ret)
+ goto err_hfi_destroy;
+
+ ret = rproc_boot(core->rproc);
+ if (ret) {
+ vidc_clks_disable(core, core->res);
+ goto err_hfi_destroy;
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ goto err_runtime_disable;
+
+ ret = vidc_hfi_core_init(&core->hfi);
+ if (ret)
+ goto err_rproc_shutdown;
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret)
+ goto err_core_deinit;
+
+ vidc_clks_disable(core, core->res);
+
+ ret = v4l2_device_register(dev, &core->v4l2_dev);
+ if (ret)
+ goto err_core_deinit;
+
+ core->vdev_dec = video_device_alloc();
+ if (!core->vdev_dec) {
+ ret = -ENOMEM;
+ goto err_dev_unregister;
+ }
+
+ core->vdev_enc = video_device_alloc();
+ if (!core->vdev_enc) {
+ ret = -ENOMEM;
+ goto err_dec_release;
+ }
+
+ ret = vdec_init(core, core->vdev_dec, &vidc_fops);
+ if (ret)
+ goto err_enc_release;
+
+ ret = venc_init(core, core->vdev_enc, &vidc_fops);
+ if (ret)
+ goto err_vdec_deinit;
+
+ return 0;
+
+err_vdec_deinit:
+ vdec_deinit(core, core->vdev_dec);
+err_enc_release:
+ video_device_release(core->vdev_enc);
+err_dec_release:
+ video_device_release(core->vdev_dec);
+err_dev_unregister:
+ v4l2_device_unregister(&core->v4l2_dev);
+err_core_deinit:
+ vidc_hfi_core_deinit(&core->hfi);
+err_rproc_shutdown:
+ rproc_shutdown(core->rproc);
+err_runtime_disable:
+ pm_runtime_set_suspended(dev);
+ pm_runtime_disable(dev);
+err_hfi_destroy:
+ vidc_hfi_destroy(&core->hfi);
+ return ret;
+}
+
+static int vidc_remove(struct platform_device *pdev)
+{
+ struct vidc_core *core = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = vidc_hfi_core_deinit(&core->hfi);
+ if (ret) {
+ pm_runtime_put_sync(&pdev->dev);
+ return ret;
+ }
+
+ rproc_shutdown(core->rproc);
+
+ ret = pm_runtime_put_sync(&pdev->dev);
+
+ vidc_hfi_destroy(&core->hfi);
+ vdec_deinit(core, core->vdev_dec);
+ venc_deinit(core, core->vdev_enc);
+ v4l2_device_unregister(&core->v4l2_dev);
+ video_device_release(core->vdev_enc);
+ video_device_release(core->vdev_dec);
+
+ pm_runtime_disable(core->dev);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int vidc_runtime_suspend(struct device *dev)
+{
+ struct vidc_core *core = dev_get_drvdata(dev);
+ int ret;
+
+ ret = vidc_hfi_core_suspend(&core->hfi);
+
+ vidc_clks_disable(core, core->res);
+
+ return ret;
+}
+
+static int vidc_runtime_resume(struct device *dev)
+{
+ struct vidc_core *core = dev_get_drvdata(dev);
+ int ret;
+
+ ret = vidc_clks_enable(core, core->res);
+ if (ret)
+ return ret;
+
+ return vidc_hfi_core_resume(&core->hfi);
+}
+
+static int vidc_pm_suspend(struct device *dev)
+{
+ return vidc_runtime_suspend(dev);
+}
+
+static int vidc_pm_resume(struct device *dev)
+{
+ return vidc_runtime_resume(dev);
+}
+
+static const struct dev_pm_ops vidc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(vidc_pm_suspend, vidc_pm_resume)
+ SET_RUNTIME_PM_OPS(vidc_runtime_suspend, vidc_runtime_resume, NULL)
+};
+
+static const struct freq_tbl msm8916_freq_table[] = {
+ { 352800, 228570000 }, /* 1920x1088 @ 30 + 1280x720 @ 30 */
+ { 244800, 160000000 }, /* 1920x1088 @ 30 */
+ { 108000, 100000000 }, /* 1280x720 @ 30 */
+};
+
+static const struct reg_val msm8916_reg_preset[] = {
+ { 0xe0020, 0x05555556 },
+ { 0xe0024, 0x05555556 },
+ { 0x80124, 0x00000003 },
+};
+
+static const struct vidc_resources msm8916_res = {
+ .freq_tbl = msm8916_freq_table,
+ .freq_tbl_size = ARRAY_SIZE(msm8916_freq_table),
+ .reg_tbl = msm8916_reg_preset,
+ .reg_tbl_size = ARRAY_SIZE(msm8916_reg_preset),
+ .clks = {"core", "iface", "bus", },
+ .clks_num = 3,
+ .max_load = 352800, /* 720p@30 + 1080p@30 */
+ .hfi_version = 0,
+ .vmem_id = VIDC_RESOURCE_NONE,
+ .vmem_size = 0,
+ .vmem_addr = 0,
+ .dma_mask = 0xddc00000 - 1,
+};
+
+static const struct of_device_id vidc_dt_match[] = {
+ { .compatible = "qcom,vidc-msm8916", .data = &msm8916_res, },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, vidc_dt_match);
+
+static struct platform_driver qcom_vidc_driver = {
+ .probe = vidc_probe,
+ .remove = vidc_remove,
+ .driver = {
+ .name = "qcom-vidc",
+ .of_match_table = vidc_dt_match,
+ .pm = &vidc_pm_ops,
+ },
+};
+
+module_platform_driver(qcom_vidc_driver);
+
+MODULE_ALIAS("platform:qcom-vidc");
+MODULE_DESCRIPTION("Qualcomm video encoder and decoder driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/qcom/vidc/core.h b/drivers/media/platform/qcom/vidc/core.h
new file mode 100644
index 000000000000..9a65c7fb469c
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/core.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#ifndef __VIDC_CORE_H_
+#define __VIDC_CORE_H_
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+
+#include "hfi.h"
+
+#define VIDC_DRV_NAME "vidc"
+
+/* structures needed to diferenciate resources per version/SoC */
+
+struct vidc_format {
+ u32 pixfmt;
+ int num_planes;
+ u32 type;
+};
+
+struct vidc_core {
+ void __iomem *base;
+ int irq;
+ struct clk *clks[VIDC_CLKS_NUM_MAX];
+ struct mutex lock;
+ struct hfi_core hfi;
+ struct video_device *vdev_dec;
+ struct video_device *vdev_enc;
+ struct v4l2_device v4l2_dev;
+ struct list_head instances;
+ const struct vidc_resources *res;
+ struct rproc *rproc;
+ struct list_head list;
+ struct vidc_core *core;
+
+ struct list_head internalbufs;
+ struct mutex internalbufs_lock;
+
+ struct list_head registeredbufs;
+ struct mutex registeredbufs_lock;
+
+ struct list_head bufqueue;
+ struct mutex bufqueue_lock;
+
+static inline struct vidc_inst *to_inst(struct file *filp)
+{
+ return container_of(filp->private_data, struct vidc_inst, fh);
+}
+
+static inline struct hfi_inst *to_hfi_inst(struct file *filp)
+{
+ return to_inst(filp)->hfi_inst;
+}
+
+static inline struct vb2_queue *
+vidc_to_vb2q(struct file *file, enum v4l2_buf_type type)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return &inst->bufq_cap;
+ else if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return &inst->bufq_out;
+
+ return NULL;
+}
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/helpers.c b/drivers/media/platform/qcom/vidc/helpers.c
new file mode 100644
index 000000000000..ce2d6b62fcf3
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/helpers.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/clk.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "helpers.h"
+#include "hfi_helper.h"
+
+struct intbuf {
+ struct list_head list;
+ u32 type;
+ size_t size;
+ void *va;
+ dma_addr_t da;
+ unsigned long attrs;
+};
+
+static int intbufs_alloc_and_set(struct vidc_inst *inst,
+ struct hfi_buffer_requirements *bufreq)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc bd;
+ struct intbuf *buf;
+ unsigned int i;
+ int ret;
+
+ if (!bufreq->size)
+ return 0;
+
+ for (i = 0; i < bufreq->count_actual; i++) {
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ buf->type = bufreq->type;
+ buf->size = bufreq->size;
+ buf->attrs = DMA_ATTR_WRITE_COMBINE |
+ DMA_ATTR_NO_KERNEL_MAPPING;
+ buf->va = dma_alloc_attrs(dev, buf->size, &buf->da, GFP_KERNEL,
+ buf->attrs);
+ if (!buf->va) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ memset(&bd, 0, sizeof(bd));
+ bd.buffer_size = buf->size;
+ bd.buffer_type = buf->type;
+ bd.num_buffers = 1;
+ bd.device_addr = buf->da;
+
+ ret = vidc_hfi_session_set_buffers(hfi, inst->hfi_inst, &bd);
+ if (ret) {
+ dev_err(dev, "set session buffers failed\n");
+ goto fail;
+ }
+
+ mutex_lock(&inst->internalbufs_lock);
+ list_add_tail(&buf->list, &inst->internalbufs);
+ mutex_unlock(&inst->internalbufs_lock);
+ }
+
+ return 0;
+
+fail:
+ kfree(buf);
+ return ret;
+}
+
+static int intbufs_set_buffer(struct vidc_inst *inst, u32 type)
+{
+ struct hfi_buffer_requirements bufreq;
+ int ret;
+
+ ret = vidc_get_bufreq(inst, type, &bufreq);
+ if (ret)
+ return 0;
+
+ return intbufs_alloc_and_set(inst, &bufreq);
+}
+
+static int intbufs_unset_buffers(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc bd = {0};
+ struct intbuf *buf, *n;
+ int ret = 0;
+
+ mutex_lock(&inst->internalbufs_lock);
+ list_for_each_entry_safe(buf, n, &inst->internalbufs, list) {
+ bd.buffer_size = buf->size;
+ bd.buffer_type = buf->type;
+ bd.num_buffers = 1;
+ bd.device_addr = buf->da;
+ bd.response_required = true;
+
+ ret = vidc_hfi_session_unset_buffers(hfi, inst->hfi_inst, &bd);
+
+ list_del(&buf->list);
+ dma_free_attrs(inst->core->dev, buf->size, buf->va, buf->da,
+ buf->attrs);
+ kfree(buf);
+ }
+ mutex_unlock(&inst->internalbufs_lock);
+
+ return ret;
+}
+
+static const unsigned int intbuf_types[] = {
+ HFI_BUFFER_INTERNAL_SCRATCH,
+ HFI_BUFFER_INTERNAL_SCRATCH_1,
+ HFI_BUFFER_INTERNAL_SCRATCH_2,
+ HFI_BUFFER_INTERNAL_PERSIST,
+ HFI_BUFFER_INTERNAL_PERSIST_1,
+};
+
+static int internal_bufs_alloc(struct vidc_inst *inst)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(intbuf_types); i++) {
+ ret = intbufs_set_buffer(inst, intbuf_types[i]);
+ if (ret)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ intbufs_unset_buffers(inst);
+ return ret;
+}
+
+static int internal_bufs_free(struct vidc_inst *inst)
+{
+ return intbufs_unset_buffers(inst);
+}
+
+static u32 load_per_instance(struct vidc_inst *inst)
+{
+ u32 w = inst->width;
+ u32 h = inst->height;
+ u32 mbs;
+
+ if (!inst->hfi_inst || !(inst->hfi_inst->state >= INST_INIT &&
+ inst->hfi_inst->state < INST_STOP))
+ return 0;
+
+ mbs = (ALIGN(w, 16) / 16) * (ALIGN(h, 16) / 16);
+
+ return mbs * inst->fps;
+}
+
+static u32 load_per_type(struct vidc_core *core, u32 session_type)
+{
+ struct vidc_inst *inst = NULL;
+ u64 mbs_per_sec = 0;
+
+ mutex_lock(&core->lock);
+ list_for_each_entry(inst, &core->instances, list) {
+ if (inst->session_type != session_type)
+ continue;
+
+ mbs_per_sec += load_per_instance(inst);
+ }
+ mutex_unlock(&core->lock);
+
+ return mbs_per_sec;
+}
+
+static int load_scale_clocks(struct vidc_core *core)
+{
+ const struct freq_tbl *table = core->res->freq_tbl;
+ unsigned int num_rows = core->res->freq_tbl_size;
+ unsigned long freq = table[0].freq;
+ struct clk *clk = core->clks[0];
+ struct device *dev = core->dev;
+ u32 mbs_per_sec;
+ unsigned int i;
+ int ret;
+
+ mbs_per_sec = load_per_type(core, VIDC_SESSION_TYPE_ENC) +
+ load_per_type(core, VIDC_SESSION_TYPE_DEC);
+
+ if (mbs_per_sec > core->res->max_load) {
+ dev_warn(dev, "HW is overloaded, needed: %d max: %d\n",
+ mbs_per_sec, core->res->max_load);
+ return -EBUSY;
+ }
+
+ if (!mbs_per_sec && num_rows > 1) {
+ freq = table[num_rows - 1].freq;
+ goto set_freq;
+ }
+
+ for (i = 0; i < num_rows; i++) {
+ if (mbs_per_sec > table[i].load)
+ break;
+ freq = table[i].freq;
+ }
+
+set_freq:
+
+ ret = clk_set_rate(clk, freq);
+ if (ret) {
+ dev_err(dev, "failed to set clock rate %lu (%d)\n", freq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int session_set_buf(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *q = vb->vb2_queue;
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct vidc_core *core = inst->core;
+ struct device *dev = core->dev;
+ struct hfi_core *hfi = &core->hfi;
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ struct hfi_frame_data fdata;
+ int ret;
+
+ memset(&fdata, 0, sizeof(fdata));
+
+ fdata.alloc_len = vb2_plane_size(vb, 0);
+ fdata.device_addr = buf->dma_addr;
+ fdata.timestamp = vb->timestamp;
+ fdata.flags = 0;
+ fdata.clnt_data = buf->dma_addr;
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ fdata.buffer_type = HFI_BUFFER_INPUT;
+ fdata.filled_len = vb2_get_plane_payload(vb, 0);
+ fdata.offset = vb->planes[0].data_offset;
+
+ if (vbuf->flags & V4L2_BUF_FLAG_LAST || !fdata.filled_len)
+ fdata.flags |= HFI_BUFFERFLAG_EOS;
+
+ ret = vidc_hfi_session_etb(hfi, inst->hfi_inst, &fdata);
+ } else if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ fdata.buffer_type = HFI_BUFFER_OUTPUT;
+ fdata.filled_len = 0;
+ fdata.offset = 0;
+
+ ret = vidc_hfi_session_ftb(hfi, inst->hfi_inst, &fdata);
+ } else {
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(dev, "failed to set session buffer (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int session_unregister_bufs(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc *bd;
+ struct vidc_buffer *buf, *tmp;
+ int ret = 0;
+
+ mutex_lock(&inst->registeredbufs_lock);
+ list_for_each_entry_safe(buf, tmp, &inst->registeredbufs, hfi_list) {
+ list_del(&buf->hfi_list);
+ bd = &buf->bd;
+ bd->response_required = 1;
+ ret = vidc_hfi_session_unset_buffers(hfi, inst->hfi_inst, bd);
+ if (ret) {
+ dev_err(dev, "%s: session release buffers failed\n",
+ __func__);
+ break;
+ }
+ }
+ mutex_unlock(&inst->registeredbufs_lock);
+
+ return ret;
+}
+
+static int session_register_bufs(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_buffer_desc *bd;
+ struct vidc_buffer *buf, *tmp;
+ int ret = 0;
+
+ mutex_lock(&inst->registeredbufs_lock);
+ list_for_each_entry_safe(buf, tmp, &inst->registeredbufs, hfi_list) {
+ bd = &buf->bd;
+ ret = vidc_hfi_session_set_buffers(hfi, inst->hfi_inst, bd);
+ if (ret) {
+ dev_err(dev, "%s: session: set buffer failed\n",
+ __func__);
+ break;
+ }
+ }
+ mutex_unlock(&inst->registeredbufs_lock);
+
+ return ret;
+}
+
+int vidc_get_bufreq(struct vidc_inst *inst, u32 type,
+ struct hfi_buffer_requirements *out)
+int vidc_set_color_format(struct vidc_inst *inst, u32 type, u32 pixfmt)
+{
+ struct hfi_uncompressed_format_select fmt;
+ struct hfi_core *hfi = &inst->core->hfi;
+ u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT;
+ int ret;
+
+ fmt.buffer_type = type;
+
+ switch (pixfmt) {
+ case V4L2_PIX_FMT_NV12:
+ fmt.format = HFI_COLOR_FORMAT_NV12;
+ break;
+ case V4L2_PIX_FMT_NV21:
+ fmt.format = HFI_COLOR_FORMAT_NV21;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fmt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+struct vb2_v4l2_buffer *
+vidc_vb2_find_buf(struct vidc_inst *inst, dma_addr_t addr)
+{
+ struct vidc_buffer *buf;
+ struct vb2_v4l2_buffer *vb = NULL;
+
+ mutex_lock(&inst->bufqueue_lock);
+
+ list_for_each_entry(buf, &inst->bufqueue, list) {
+ if (buf->dma_addr == addr) {
+ vb = &buf->vb;
+ break;
+ }
+ }
+
+ if (vb)
+ list_del(&buf->list);
+
+ mutex_unlock(&inst->bufqueue_lock);
+
+ return vb;
+}
+
+int vidc_vb2_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *q = vb->vb2_queue;
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ struct hfi_buffer_desc *bd = &buf->bd;
+ struct sg_table *sgt;
+
+ memset(bd, 0, sizeof(*bd));
+
+ if (q->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return 0;
+
+ sgt = vb2_dma_sg_plane_desc(vb, 0);
+ if (!sgt)
+ return -EINVAL;
+
+ bd->buffer_size = vb2_plane_size(vb, 0);
+ bd->buffer_type = HFI_BUFFER_OUTPUT;
+ bd->num_buffers = 1;
+ bd->device_addr = sg_dma_address(sgt->sgl);
+
+ mutex_lock(&inst->registeredbufs_lock);
+ list_add_tail(&buf->hfi_list, &inst->registeredbufs);
+ mutex_unlock(&inst->registeredbufs_lock);
+
+ return 0;
+}
+
+int vidc_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ struct sg_table *sgt;
+
+ sgt = vb2_dma_sg_plane_desc(vb, 0);
+ if (!sgt)
+ return -EINVAL;
+
+ buf->dma_addr = sg_dma_address(sgt->sgl);
+
+ return 0;
+}
+
+void vidc_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vidc_inst *inst = vb2_get_drv_priv(vb->vb2_queue);
+ struct vidc_core *core = inst->core;
+ struct device *dev = core->dev;
+ struct vidc_buffer *buf = to_vidc_buffer(vbuf);
+ unsigned int state;
+ int ret;
+
+ mutex_lock(&inst->hfi_inst->lock);
+ state = inst->hfi_inst->state;
+ mutex_unlock(&inst->hfi_inst->lock);
+
+ if (state == INST_INVALID || state >= INST_STOP) {
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ dev_dbg(dev, "%s: type:%d, invalid instance state\n", __func__,
+ vb->type);
+ return;
+ }
+
+ mutex_lock(&inst->bufqueue_lock);
+ list_add_tail(&buf->list, &inst->bufqueue);
+ mutex_unlock(&inst->bufqueue_lock);
+
+ if (!vb2_is_streaming(&inst->bufq_cap) ||
+ !vb2_is_streaming(&inst->bufq_out))
+ return;
+
+ ret = session_set_buf(vb);
+ if (ret)
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+}
+
+void vidc_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+ struct vidc_core *core = inst->core;
+ struct device *dev = core->dev;
+ struct hfi_core *hfi = &core->hfi;
+ struct vb2_queue *other_queue;
+ struct vidc_buffer *buf, *n;
+ enum vb2_buffer_state state;
+ int ret;
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ other_queue = &inst->bufq_cap;
+ else
+ other_queue = &inst->bufq_out;
+
+ if (!vb2_is_streaming(other_queue))
+ return;
+
+ ret = vidc_hfi_session_stop(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: stop failed (%d)\n", ret);
+ goto abort;
+ load_scale_clocks(inst->core);
+
+ ret = vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+ if (ret)
+ dev_err(dev, "stop streaming failed type: %d, ret: %d\n",
+ q->type, ret);
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret < 0)
+ dev_err(dev, "%s: pm_runtime_put_sync (%d)\n", __func__, ret);
+
+ mutex_lock(&inst->bufqueue_lock);
+
+ if (list_empty(&inst->bufqueue)) {
+ mutex_unlock(&inst->bufqueue_lock);
+ return;
+ }
+
+ if (ret)
+ state = VB2_BUF_STATE_ERROR;
+ else
+ state = VB2_BUF_STATE_DONE;
+
+ list_for_each_entry_safe(buf, n, &inst->bufqueue, list) {
+ vb2_buffer_done(&buf->vb.vb2_buf, state);
+ list_del(&buf->list);
+ }
+
+ mutex_unlock(&inst->bufqueue_lock);
+}
+
+int vidc_vb2_start_streaming(struct vidc_inst *inst)
+{
+ struct device *dev = inst->core->dev;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vidc_buffer *buf, *n;
+ int ret;
+
+ ret = session_register_bufs(inst);
+ if (ret)
+ return ret;
+
+ ret = internal_bufs_alloc(inst);
+ if (ret)
+ return ret;
+
+ load_scale_clocks(inst->core);
+
+ ret = vidc_hfi_session_load_res(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: load resources (%d)\n", ret);
+ return ret;
+ }
+
+ ret = vidc_hfi_session_start(hfi, inst->hfi_inst);
+ if (ret) {
+ dev_err(dev, "session: start failed (%d)\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&inst->bufqueue_lock);
+ list_for_each_entry_safe(buf, n, &inst->bufqueue, list) {
+ ret = session_set_buf(&buf->vb.vb2_buf);
+ if (ret)
+ break;
+ }
+ mutex_unlock(&inst->bufqueue_lock);
+
+ return ret;
+}
diff --git a/drivers/media/platform/qcom/vidc/helpers.h b/drivers/media/platform/qcom/vidc/helpers.h
new file mode 100644
index 000000000000..e37d4d5c980b
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/helpers.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_COMMON_H__
+#define __VIDC_COMMON_H__
+
+#include <linux/list.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "core.h"
+
+struct vidc_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ dma_addr_t dma_addr;
+ struct list_head hfi_list;
+ struct hfi_buffer_desc bd;
+};
+
+#define to_vidc_buffer(buf) container_of(buf, struct vidc_buffer, vb)
+
+struct vb2_v4l2_buffer *
+vidc_vb2_find_buf(struct vidc_inst *inst, dma_addr_t addr);
+int vidc_vb2_buf_init(struct vb2_buffer *vb);
+int vidc_vb2_buf_prepare(struct vb2_buffer *vb);
+void vidc_vb2_buf_queue(struct vb2_buffer *vb);
+void vidc_vb2_stop_streaming(struct vb2_queue *q);
+int vidc_vb2_start_streaming(struct vidc_inst *inst);
+int vidc_get_bufreq(struct vidc_inst *inst, u32 type,
+ struct hfi_buffer_requirements *out);
+int vidc_set_color_format(struct vidc_inst *inst, u32 type, u32 fmt);
+#endif
--
2.7.4

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:13 AM9/7/16
to
This consists of video decoder implementation plus decoder
controls.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/vdec.c | 1091 +++++++++++++++++++++++++
drivers/media/platform/qcom/vidc/vdec.h | 29 +
drivers/media/platform/qcom/vidc/vdec_ctrls.c | 200 +++++
drivers/media/platform/qcom/vidc/vdec_ctrls.h | 21 +
4 files changed, 1341 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/vdec.c
create mode 100644 drivers/media/platform/qcom/vidc/vdec.h
create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.h

diff --git a/drivers/media/platform/qcom/vidc/vdec.c b/drivers/media/platform/qcom/vidc/vdec.c
new file mode 100644
index 000000000000..433fe4f697d1
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/vdec.c
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "core.h"
+#include "helpers.h"
+#include "vdec_ctrls.h"
+
+#define MACROBLOCKS_PER_PIXEL (16 * 16)
+
+static u32 get_framesize_nv12(int plane, u32 height, u32 width)
+{
+ u32 y_stride, uv_stride, y_plane;
+ u32 y_sclines, uv_sclines, uv_plane;
+ u32 size;
+
+ y_stride = ALIGN(width, 128);
+ uv_stride = ALIGN(width, 128);
+ y_sclines = ALIGN(height, 32);
+ uv_sclines = ALIGN(((height + 1) >> 1), 16);
+
+ y_plane = y_stride * y_sclines;
+ uv_plane = uv_stride * uv_sclines + SZ_4K;
+ size = y_plane + uv_plane + SZ_8K;
+
+ return ALIGN(size, SZ_4K);
+}
+
+static u32 get_framesize_compressed(u32 mbs_per_frame)
+{
+ return ((mbs_per_frame * MACROBLOCKS_PER_PIXEL * 3 / 2) / 2) + 128;
+}
+
+static const struct vidc_format vdec_formats[] = {
+ {
+ .pixfmt = V4L2_PIX_FMT_NV12,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG4,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG2,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_H263,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_G,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_L,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_H264,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_VP8,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_XVID,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ },
+};
+
+static const struct vidc_format *find_format(u32 pixfmt, u32 type)
+{
+ const struct vidc_format *fmt = vdec_formats;
+ unsigned int size = ARRAY_SIZE(vdec_formats);
+ unsigned int i;
+
+ for (i = 0; i < size; i++) {
+ if (fmt[i].pixfmt == pixfmt)
+ break;
+ }
+
+ if (i == size || fmt[i].type != type)
+ return NULL;
+
+ return &fmt[i];
+}
+
+static const struct vidc_format *find_format_by_index(int index, u32 type)
+{
+ const struct vidc_format *fmt = vdec_formats;
+ unsigned int size = ARRAY_SIZE(vdec_formats);
+ int i, k = 0;
+
+ if (index < 0 || index > size)
+ return NULL;
+
+ for (i = 0; i < size; i++) {
+ if (fmt[i].type != type)
+ continue;
+ if (k == index)
+ break;
+ k++;
+ }
+
+ if (i == size)
+ return NULL;
+
+ return &fmt[i];
+}
+
+static int vdec_set_properties(struct vidc_inst *inst)
+{
+ struct vdec_controls *ctr = &inst->controls.dec;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_enable en = { .enable = 1 };
+ struct hfi_framerate frate;
+ u32 ptype;
+ int ret;
+
+ ptype = HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &en);
+ if (ret)
+ return ret;
+
+ ptype = HFI_PROPERTY_CONFIG_FRAME_RATE;
+ frate.buffer_type = HFI_BUFFER_INPUT;
+ frate.framerate = inst->fps * (1 << 16);
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &frate);
+ if (ret)
+ return ret;
+
+ if (ctr->post_loop_deb_mode) {
+ ptype = HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER;
+ en.enable = 1;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &en);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct vidc_format *
+vdec_try_fmt_common(struct vidc_inst *inst, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt;
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+ const struct vidc_format *fmt;
+ unsigned int p;
+
+ memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved));
+ memset(pixmp->reserved, 0, sizeof(pixmp->reserved));
+
+ fmt = find_format(pixmp->pixelformat, f->type);
+ if (!fmt) {
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ pixmp->pixelformat = V4L2_PIX_FMT_NV12;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ pixmp->pixelformat = V4L2_PIX_FMT_H264;
+ else
+ return NULL;
+ fmt = find_format(pixmp->pixelformat, f->type);
+ pixmp->width = 1280;
+ pixmp->height = 720;
+ }
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ pixmp->height = ALIGN(pixmp->height, 32);
+
+ pixmp->width = clamp(pixmp->width, hfi_inst->width.min,
+ hfi_inst->width.max);
+ pixmp->height = clamp(pixmp->height, hfi_inst->height.min,
+ hfi_inst->height.max);
+ if (pixmp->field == V4L2_FIELD_ANY)
+ pixmp->field = V4L2_FIELD_NONE;
+ pixmp->num_planes = fmt->num_planes;
+ pixmp->flags = 0;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ for (p = 0; p < pixmp->num_planes; p++) {
+ pfmt[p].sizeimage = get_framesize_nv12(p, pixmp->height,
+ pixmp->width);
+
+ pfmt[p].bytesperline = ALIGN(pixmp->width, 128);
+ }
+ } else {
+ u32 mbs = pixmp->width * pixmp->height / MACROBLOCKS_PER_PIXEL;
+
+ pfmt[0].sizeimage = get_framesize_compressed(mbs);
+ pfmt[0].bytesperline = 0;
+ }
+
+ return fmt;
+}
+
+static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ const struct vidc_format *fmt;
+
+ fmt = vdec_try_fmt_common(inst, f);
+ if (!fmt)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ const struct vidc_format *fmt = NULL;
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ fmt = inst->fmt_cap;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ fmt = inst->fmt_out;
+
+ if (inst->in_reconfig) {
+ inst->height = inst->reconfig_height;
+ inst->width = inst->reconfig_width;
+ inst->in_reconfig = false;
+ }
+
+ pixmp->pixelformat = fmt->pixfmt;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixmp->width = inst->width;
+ pixmp->height = inst->height;
+ pixmp->colorspace = inst->colorspace;
+ pixmp->ycbcr_enc = inst->ycbcr_enc;
+ pixmp->quantization = inst->quantization;
+ pixmp->xfer_func = inst->xfer_func;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixmp->width = inst->out_width;
+ pixmp->height = inst->out_height;
+ }
+
+ vdec_try_fmt_common(inst, f);
+
+ return 0;
+}
+
+static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_pix_format_mplane orig_pixmp;
+ const struct vidc_format *fmt;
+ struct v4l2_format format;
+ u32 pixfmt_out = 0, pixfmt_cap = 0;
+
+ orig_pixmp = *pixmp;
+
+ fmt = vdec_try_fmt_common(inst, f);
+ if (!fmt)
+ return -EINVAL;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixfmt_out = pixmp->pixelformat;
+ pixfmt_cap = inst->fmt_cap->pixfmt;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixfmt_cap = pixmp->pixelformat;
+ pixfmt_out = inst->fmt_out->pixfmt;
+ }
+
+ memset(&format, 0, sizeof(format));
+
+ format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ format.fmt.pix_mp.pixelformat = pixfmt_out;
+ format.fmt.pix_mp.width = orig_pixmp.width;
+ format.fmt.pix_mp.height = orig_pixmp.height;
+ vdec_try_fmt_common(inst, &format);
+ inst->out_width = format.fmt.pix_mp.width;
+ inst->out_height = format.fmt.pix_mp.height;
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ inst->colorspace = pixmp->colorspace;
+ inst->ycbcr_enc = pixmp->ycbcr_enc;
+ inst->quantization = pixmp->quantization;
+ inst->xfer_func = pixmp->xfer_func;
+ }
+
+ memset(&format, 0, sizeof(format));
+
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ format.fmt.pix_mp.pixelformat = pixfmt_cap;
+ format.fmt.pix_mp.width = orig_pixmp.width;
+ format.fmt.pix_mp.height = orig_pixmp.height;
+ vdec_try_fmt_common(inst, &format);
+ inst->width = format.fmt.pix_mp.width;
+ inst->height = format.fmt.pix_mp.height;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ inst->fmt_out = fmt;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ inst->fmt_cap = fmt;
+
+ return 0;
+}
+
+static int
+vdec_g_selection(struct file *file, void *priv, struct v4l2_selection *s)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ s->r.top = 0;
+ s->r.left = 0;
+ s->r.width = inst->out_width;
+ s->r.height = inst->out_height;
+
+ return 0;
+}
+
+static int
+vdec_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ if (!b->count)
+ vb2_core_queue_release(queue);
+
+ return vb2_reqbufs(queue, b);
+}
+
+static int
+vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+ strlcpy(cap->driver, VIDC_DRV_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, "video decoder", sizeof(cap->card));
+ strlcpy(cap->bus_info, "platform:vidc", sizeof(cap->bus_info));
+
+ return 0;
+}
+
+static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+ const struct vidc_format *fmt;
+
+ memset(f->reserved, 0, sizeof(f->reserved));
+
+ fmt = find_format_by_index(f->index, f->type);
+ if (!fmt)
+ return -EINVAL;
+
+ f->pixelformat = fmt->pixfmt;
+
+ return 0;
+}
+
+static int vdec_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+ unsigned int p;
+ int ret;
+
+ if (!queue)
+ return -EINVAL;
+
+ ret = vb2_querybuf(queue, b);
+ if (ret)
+ return ret;
+
+ if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ b->memory == V4L2_MEMORY_MMAP) {
+ for (p = 0; p < b->length; p++)
+ b->m.planes[p].m.mem_offset += DST_QUEUE_OFF_BASE;
+ }
+
+ return 0;
+}
+
+static int
+vdec_create_bufs(struct file *file, void *fh, struct v4l2_create_buffers *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->format.type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_create_bufs(queue, b);
+}
+
+static int vdec_prepare_buf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_prepare_buf(queue, b);
+}
+
+static int vdec_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_qbuf(queue, b);
+}
+
+static int
+vdec_exportbuf(struct file *file, void *fh, struct v4l2_exportbuffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_expbuf(queue, b);
+}
+
+static int vdec_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_dqbuf(queue, b, file->f_flags & O_NONBLOCK);
+}
+
+static int vdec_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_streamon(queue, type);
+}
+
+static int vdec_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_streamoff(queue, type);
+}
+
+static int vdec_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct v4l2_captureparm *cap = &a->parm.capture;
+ struct v4l2_fract *timeperframe = &cap->timeperframe;
+ u64 us_per_frame, fps;
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ memset(cap->reserved, 0, sizeof(cap->reserved));
+ if (!timeperframe->denominator)
+ timeperframe->denominator = inst->timeperframe.denominator;
+ if (!timeperframe->numerator)
+ timeperframe->numerator = inst->timeperframe.numerator;
+ cap->readbuffers = 0;
+ cap->extendedmode = 0;
+ cap->capability = V4L2_CAP_TIMEPERFRAME;
+ us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC;
+ do_div(us_per_frame, timeperframe->denominator);
+
+ if (!us_per_frame)
+ return -EINVAL;
+
+ fps = (u64)USEC_PER_SEC;
+ do_div(fps, us_per_frame);
+
+ inst->fps = fps;
+ inst->timeperframe = *timeperframe;
+
+ return 0;
+}
+
+static int vdec_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ a->parm.capture.capability |= V4L2_CAP_TIMEPERFRAME;
+ a->parm.capture.timeperframe = inst->timeperframe;
+
+ return 0;
+}
+
+static int vdec_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct hfi_inst *hfi_inst = to_hfi_inst(file);
+ const struct vidc_format *fmt;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+
+ fmt = find_format(fsize->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (!fmt) {
+ fmt = find_format(fsize->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+ if (!fmt)
+ return -EINVAL;
+ }
+
+ if (fsize->index)
+ return -EINVAL;
+
+ fsize->stepwise.min_width = hfi_inst->width.min;
+ fsize->stepwise.max_width = hfi_inst->width.max;
+ fsize->stepwise.step_width = hfi_inst->width.step_size;
+ fsize->stepwise.min_height = hfi_inst->height.min;
+ fsize->stepwise.max_height = hfi_inst->height.max;
+ fsize->stepwise.step_height = hfi_inst->height.step_size;
+
+ return 0;
+}
+
+static int vdec_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *fival)
+{
+ struct hfi_inst *hfi_inst = to_hfi_inst(file);
+ const struct vidc_format *fmt;
+
+ fival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+
+ fmt = find_format(fival->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (!fmt)
+ return -EINVAL;
+
+ if (fival->index)
+ return -EINVAL;
+
+ if (!fival->width || !fival->height)
+ return -EINVAL;
+
+ if (fival->width > hfi_inst->width.max ||
+ fival->width < hfi_inst->width.min ||
+ fival->height > hfi_inst->height.max ||
+ fival->height < hfi_inst->height.min)
+ return -EINVAL;
+
+ fival->stepwise.min.numerator = hfi_inst->framerate.min;
+ fival->stepwise.min.denominator = 1;
+ fival->stepwise.max.numerator = hfi_inst->framerate.max;
+ fival->stepwise.max.denominator = 1;
+ fival->stepwise.step.numerator = hfi_inst->framerate.step_size;
+ fival->stepwise.step.denominator = 1;
+
+ return 0;
+}
+
+static int vdec_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_EOS:
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_src_change_event_subscribe(fh, sub);
+ case V4L2_EVENT_CTRL:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_ioctl_ops vdec_ioctl_ops = {
+ .vidioc_querycap = vdec_querycap,
+ .vidioc_enum_fmt_vid_cap_mplane = vdec_enum_fmt,
+ .vidioc_enum_fmt_vid_out_mplane = vdec_enum_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt,
+ .vidioc_s_fmt_vid_out_mplane = vdec_s_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt,
+ .vidioc_g_fmt_vid_out_mplane = vdec_g_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt,
+ .vidioc_try_fmt_vid_out_mplane = vdec_try_fmt,
+ .vidioc_g_selection = vdec_g_selection,
+ .vidioc_reqbufs = vdec_reqbufs,
+ .vidioc_querybuf = vdec_querybuf,
+ .vidioc_create_bufs = vdec_create_bufs,
+ .vidioc_prepare_buf = vdec_prepare_buf,
+ .vidioc_qbuf = vdec_qbuf,
+ .vidioc_expbuf = vdec_exportbuf,
+ .vidioc_dqbuf = vdec_dqbuf,
+ .vidioc_streamon = vdec_streamon,
+ .vidioc_streamoff = vdec_streamoff,
+ .vidioc_s_parm = vdec_s_parm,
+ .vidioc_g_parm = vdec_g_parm,
+ .vidioc_enum_framesizes = vdec_enum_framesizes,
+ .vidioc_enum_frameintervals = vdec_enum_frameintervals,
+ .vidioc_subscribe_event = vdec_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int vdec_init_session(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ u32 pixfmt = inst->fmt_out->pixfmt;
+ struct hfi_framesize fs;
+ u32 ptype;
+ int ret;
+
+ ret = vidc_hfi_session_init(hfi, inst->hfi_inst, pixfmt,
+ VIDC_SESSION_TYPE_DEC);
+ if (ret)
+ return ret;
+
+ ptype = HFI_PROPERTY_PARAM_FRAME_SIZE;
+ fs.buffer_type = HFI_BUFFER_INPUT;
+ fs.width = inst->out_width;
+ fs.height = inst->out_height;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fs);
+ if (ret)
+ goto err;
+
+ fs.buffer_type = HFI_BUFFER_OUTPUT;
+ fs.width = inst->width;
+ fs.height = inst->height;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fs);
+ if (ret)
+ goto err;
+
+ pixfmt = inst->fmt_cap->pixfmt;
+
+ ret = vidc_set_color_format(inst, HFI_BUFFER_OUTPUT, pixfmt);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+ return ret;
+}
+
+static int vdec_cap_num_buffers(struct vidc_inst *inst,
+ struct hfi_buffer_requirements *bufreq)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct device *dev = inst->core->dev;
+ int ret, ret2;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = vdec_init_session(inst);
+ if (ret)
+ goto put_sync;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_OUTPUT, bufreq);
+
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+
+put_sync:
+ ret2 = pm_runtime_put_sync(dev);
+
+ return ret ? ret : ret2;
+}
+
+static int vdec_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_buffer_requirements bufreq;
+ unsigned int p;
+ int ret = 0;
+ u32 mbs;
+
+ switch (q->type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ *num_planes = inst->fmt_out->num_planes;
+
+ *num_buffers = clamp_val(*num_buffers, 4, VIDEO_MAX_FRAME);
+
+ mbs = inst->out_width * inst->out_height /
+ MACROBLOCKS_PER_PIXEL;
+ for (p = 0; p < *num_planes; p++)
+ sizes[p] = get_framesize_compressed(mbs);
+
+ inst->num_input_bufs = *num_buffers;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ *num_planes = inst->fmt_cap->num_planes;
+
+ ret = vdec_cap_num_buffers(inst, &bufreq);
+ if (ret)
+ break;
+
+ *num_buffers = max(*num_buffers, bufreq.count_actual);
+
+ for (p = 0; p < *num_planes; p++)
+ sizes[p] = get_framesize_nv12(p, inst->height,
+ inst->width);
+
+ inst->num_output_bufs = *num_buffers;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int vdec_check_configuration(struct vidc_inst *inst)
+{
+ struct hfi_buffer_requirements bufreq;
+ int ret;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq);
+ if (ret)
+ return ret;
+
+ if (inst->num_output_bufs < bufreq.count_actual ||
+ inst->num_output_bufs < bufreq.count_min)
+ return -EINVAL;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq);
+ if (ret)
+ return ret;
+
+ if (inst->num_input_bufs < bufreq.count_min)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct device *dev = inst->core->dev;
+ struct hfi_buffer_requirements bufreq;
+ struct hfi_buffer_count_actual buf_count;
+ struct vb2_queue *other_queue;
+ u32 ptype;
+ int ret;
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ other_queue = &inst->bufq_cap;
+ else
+ other_queue = &inst->bufq_out;
+
+ if (!vb2_is_streaming(other_queue))
+ return 0;
+
+ inst->in_reconfig = false;
+ inst->sequence = 0;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = vdec_init_session(inst);
+ if (ret)
+ goto put_sync;
+
+ ret = vdec_set_properties(inst);
+ if (ret)
+ goto deinit_sess;
+
+ ret = vdec_check_configuration(inst);
+ if (ret)
+ goto deinit_sess;
+
+ ptype = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL;
+ buf_count.type = HFI_BUFFER_INPUT;
+ buf_count.count_actual = inst->num_input_bufs;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst,
+ ptype, &buf_count);
+ if (ret)
+ goto deinit_sess;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq);
+ if (ret)
+ goto deinit_sess;
+
+ ptype = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL;
+ buf_count.type = HFI_BUFFER_OUTPUT;
+ buf_count.count_actual = inst->num_output_bufs;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst,
+ ptype, &buf_count);
+ if (ret)
+ goto deinit_sess;
+
+ if (inst->num_output_bufs != bufreq.count_actual) {
+ struct hfi_buffer_display_hold_count_actual display;
+
+ ptype = HFI_PROPERTY_PARAM_BUFFER_DISPLAY_HOLD_COUNT_ACTUAL;
+ display.type = HFI_BUFFER_OUTPUT;
+ display.hold_count = inst->num_output_bufs -
+ bufreq.count_actual;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst,
+ ptype, &display);
+ if (ret)
+ goto deinit_sess;
+ }
+
+ ret = vidc_vb2_start_streaming(inst);
+ if (ret)
+ goto deinit_sess;
+
+ return 0;
+
+deinit_sess:
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+put_sync:
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static const struct vb2_ops vdec_vb2_ops = {
+ .queue_setup = vdec_queue_setup,
+ .buf_init = vidc_vb2_buf_init,
+ .buf_prepare = vidc_vb2_buf_prepare,
+ .start_streaming = vdec_start_streaming,
+ .stop_streaming = vidc_vb2_stop_streaming,
+ .buf_queue = vidc_vb2_buf_queue,
+};
+
+static int vdec_empty_buf_done(struct hfi_inst *hfi_inst, u32 addr,
+ u32 bytesused, u32 data_offset, u32 flags)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct vb2_v4l2_buffer *vbuf;
+ struct vb2_buffer *vb;
+
+ vbuf = vidc_vb2_find_buf(inst, addr);
+ if (!vbuf)
+ return -EINVAL;
+
+ vb = &vbuf->vb2_buf;
+ vbuf->flags = flags;
+
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+
+ return 0;
+}
+
+static int vdec_fill_buf_done(struct hfi_inst *hfi_inst, u32 addr,
+ u32 bytesused, u32 data_offset, u32 flags,
+ struct timeval *timestamp)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct vb2_v4l2_buffer *vbuf;
+ struct vb2_buffer *vb;
+
+ vbuf = vidc_vb2_find_buf(inst, addr);
+ if (!vbuf)
+ return -EINVAL;
+
+ vb = &vbuf->vb2_buf;
+ vb->planes[0].bytesused = bytesused;
+ vb->planes[0].data_offset = data_offset;
+ vb->timestamp = timeval_to_ns(timestamp);
+ vbuf->flags = flags;
+ vbuf->sequence = inst->sequence++;
+
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+
+ if (vbuf->flags & V4L2_BUF_FLAG_LAST) {
+ const struct v4l2_event ev = {
+ .type = V4L2_EVENT_EOS
+ };
+
+ v4l2_event_queue_fh(&inst->fh, &ev);
+ }
+
+ return 0;
+}
+
+static int vdec_event_notify(struct hfi_inst *hfi_inst, u32 event,
+ struct hfi_event_data *data)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct device *dev = inst->core->dev;
+ const struct v4l2_event ev = { .type = V4L2_EVENT_SOURCE_CHANGE };
+
+ switch (event) {
+ case EVT_SESSION_ERROR:
+ if (hfi_inst) {
+ mutex_lock(&hfi_inst->lock);
+ inst->hfi_inst->state = INST_INVALID;
+ mutex_unlock(&hfi_inst->lock);
+ }
+ dev_err(dev, "dec: event session error (inst:%p)\n", hfi_inst);
+ break;
+ case EVT_SYS_EVENT_CHANGE:
+ switch (data->event_type) {
+ case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES:
+ dev_dbg(dev, "event sufficient resources\n");
+ break;
+ case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES:
+ inst->reconfig_height = data->height;
+ inst->reconfig_width = data->width;
+ inst->in_reconfig = true;
+
+ v4l2_event_queue_fh(&inst->fh, &ev);
+
+ dev_dbg(dev, "event not sufficient resources (%ux%u)\n",
+ data->width, data->height);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hfi_inst_ops vdec_hfi_ops = {
+ .empty_buf_done = vdec_empty_buf_done,
+ .fill_buf_done = vdec_fill_buf_done,
+ .event_notify = vdec_event_notify,
+};
+
+static void vdec_inst_init(struct vidc_inst *inst)
+{
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+
+ inst->fmt_out = &vdec_formats[6];
+ inst->fmt_cap = &vdec_formats[0];
+ inst->width = 1280;
+ inst->height = ALIGN(720, 32);
+ inst->out_width = 1280;
+ inst->out_height = 720;
+ inst->fps = 30;
+ inst->timeperframe.numerator = 1;
+ inst->timeperframe.denominator = 30;
+
+ hfi_inst->width.min = 64;
+ hfi_inst->width.max = 1920;
+ hfi_inst->width.step_size = 1;
+ hfi_inst->height.min = 64;
+ hfi_inst->height.max = ALIGN(1080, 32);
+ hfi_inst->height.step_size = 1;
+ hfi_inst->framerate.min = 1;
+ hfi_inst->framerate.max = 30;
+ hfi_inst->framerate.step_size = 1;
+ hfi_inst->mbs_per_frame.min = 16;
+ hfi_inst->mbs_per_frame.max = 8160;
+}
+
+int vdec_init(struct vidc_core *core, struct video_device *dec,
+ const struct v4l2_file_operations *fops)
+{
+ int ret;
+
+ dec->release = video_device_release_empty;
+ dec->fops = fops;
+ dec->ioctl_ops = &vdec_ioctl_ops;
+ dec->vfl_dir = VFL_DIR_M2M;
+ dec->v4l2_dev = &core->v4l2_dev;
+ dec->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+
+ ret = video_register_device(dec, VFL_TYPE_GRABBER, -1);
+ if (ret)
+ return ret;
+
+ video_set_drvdata(dec, core);
+
+ return 0;
+}
+
+void vdec_deinit(struct vidc_core *core, struct video_device *dec)
+{
+ video_unregister_device(dec);
+}
+
+int vdec_open(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vb2_queue *q;
+ int ret;
+
+ ret = vdec_ctrl_init(inst);
+ if (ret)
+ return ret;
+
+ inst->hfi_inst = vidc_hfi_session_create(hfi, &vdec_hfi_ops, inst);
+ if (IS_ERR(inst->hfi_inst)) {
+ ret = PTR_ERR(inst->hfi_inst);
+ goto err_ctrl_deinit;
+ }
+
+ vdec_inst_init(inst);
+
+ q = &inst->bufq_cap;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->ops = &vdec_vb2_ops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->drv_priv = inst;
+ q->buf_struct_size = sizeof(struct vidc_buffer);
+ q->allow_zero_bytesused = 1;
+ q->dev = inst->core->dev;
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_session_destroy;
+
+ q = &inst->bufq_out;
+ q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->ops = &vdec_vb2_ops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->drv_priv = inst;
+ q->buf_struct_size = sizeof(struct vidc_buffer);
+ q->allow_zero_bytesused = 1;
+ q->dev = inst->core->dev;
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_cap_queue_release;
+
+ return 0;
+
+err_cap_queue_release:
+ vb2_queue_release(&inst->bufq_cap);
+err_session_destroy:
+ vidc_hfi_session_destroy(hfi, inst->hfi_inst);
+ inst->hfi_inst = NULL;
+err_ctrl_deinit:
+ vdec_ctrl_deinit(inst);
+ return ret;
+}
+
+void vdec_close(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+
+ vb2_queue_release(&inst->bufq_out);
+ vb2_queue_release(&inst->bufq_cap);
+ vdec_ctrl_deinit(inst);
+ vidc_hfi_session_destroy(hfi, inst->hfi_inst);
+}
diff --git a/drivers/media/platform/qcom/vidc/vdec.h b/drivers/media/platform/qcom/vidc/vdec.h
new file mode 100644
index 000000000000..ddc3613f71e2
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/vdec.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_VDEC_H__
+#define __VIDC_VDEC_H__
+
+struct vidc_core;
+struct video_device;
+struct vidc_inst;
+struct v4l2_file_operations;
+
+int vdec_init(struct vidc_core *core, struct video_device *dec,
+ const struct v4l2_file_operations *fops);
+void vdec_deinit(struct vidc_core *core, struct video_device *dec);
+int vdec_open(struct vidc_inst *inst);
+void vdec_close(struct vidc_inst *inst);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/vdec_ctrls.c b/drivers/media/platform/qcom/vidc/vdec_ctrls.c
new file mode 100644
index 000000000000..59225d8f1fd9
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/vdec_ctrls.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/types.h>
+#include <media/v4l2-ctrls.h>
+
+#include "core.h"
+
+static struct vidc_ctrl vdec_ctrls[] = {
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .max = V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY,
+ .def = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .flags = V4L2_CTRL_FLAG_VOLATILE,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) |
+ (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)
+ ),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ .max = V4L2_MPEG_VIDEO_MPEG4_LEVEL_5,
+ .def = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ .flags = V4L2_CTRL_FLAG_VOLATILE,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .max = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH,
+ .def = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .flags = V4L2_CTRL_FLAG_VOLATILE,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)
+ ),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+ .max = V4L2_MPEG_VIDEO_H264_LEVEL_5_1,
+ .def = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+ .flags = V4L2_CTRL_FLAG_VOLATILE,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = 3,
+ .step = 1,
+ .def = 0,
+ .flags = V4L2_CTRL_FLAG_VOLATILE,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .def = 0,
+ },
+};
+
+#define NUM_CTRLS ARRAY_SIZE(vdec_ctrls)
+
+static int vdec_op_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vidc_inst *inst = ctrl_to_inst(ctrl);
+ struct vdec_controls *ctr = &inst->controls.dec;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER:
+ ctr->post_loop_deb_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_VPX_PROFILE:
+ ctr->profile = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ ctr->level = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vdec_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vidc_inst *inst = ctrl_to_inst(ctrl);
+ struct vdec_controls *ctr = &inst->controls.dec;
+ struct hfi_core *hfi = &inst->core->hfi;
+ union hfi_get_property hprop;
+ u32 ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT;
+ int ret;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_VPX_PROFILE:
+ ret = vidc_hfi_session_get_property(hfi, inst->hfi_inst, ptype,
+ &hprop);
+ if (!ret)
+ ctr->profile = hprop.profile_level.profile;
+ ctrl->val = ctr->profile;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ ret = vidc_hfi_session_get_property(hfi, inst->hfi_inst, ptype,
+ &hprop);
+ if (!ret)
+ ctr->level = hprop.profile_level.level;
+ ctrl->val = ctr->level;
+ break;
+ case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER:
+ ctrl->val = ctr->post_loop_deb_mode;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops vdec_ctrl_ops = {
+ .s_ctrl = vdec_op_s_ctrl,
+ .g_volatile_ctrl = vdec_op_g_volatile_ctrl,
+};
+
+int vdec_ctrl_init(struct vidc_inst *inst)
+{
+ unsigned int i;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, NUM_CTRLS);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < NUM_CTRLS; i++) {
+ struct v4l2_ctrl *ctrl;
+
+ if (vdec_ctrls[i].type == V4L2_CTRL_TYPE_MENU) {
+ ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler,
+ &vdec_ctrl_ops,
+ vdec_ctrls[i].id,
+ vdec_ctrls[i].max,
+ vdec_ctrls[i].menu_skip_mask,
+ vdec_ctrls[i].def);
+ } else {
+ ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler,
+ &vdec_ctrl_ops,
+ vdec_ctrls[i].id,
+ vdec_ctrls[i].min,
+ vdec_ctrls[i].max,
+ vdec_ctrls[i].step,
+ vdec_ctrls[i].def);
+ }
+
+ if (!ctrl)
+ return -EINVAL;
+
+ switch (vdec_ctrls[i].id) {
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ case V4L2_CID_MPEG_VIDEO_VPX_PROFILE:
+ ctrl->flags |= vdec_ctrls[i].flags;
+ break;
+ }
+
+ ret = inst->ctrl_handler.error;
+ if (ret) {
+ v4l2_ctrl_handler_free(&inst->ctrl_handler);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+void vdec_ctrl_deinit(struct vidc_inst *inst)
+{
+ v4l2_ctrl_handler_free(&inst->ctrl_handler);
+}
diff --git a/drivers/media/platform/qcom/vidc/vdec_ctrls.h b/drivers/media/platform/qcom/vidc/vdec_ctrls.h
new file mode 100644
index 000000000000..18f14d06deed
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/vdec_ctrls.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_VDEC_CTRLS_H__
+#define __VIDC_VDEC_CTRLS_H__
+
+int vdec_ctrl_init(struct vidc_inst *inst);
+void vdec_ctrl_deinit(struct vidc_inst *inst);
+
+#endif
--
2.7.4

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:19 AM9/7/16
to
Here is the implementation of Venus video accelerator low-level
functionality. It contanins code which setup the registers and
startup uthe processor, allocate and manipulates with the shared
memory used for sending commands and receiving messages.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/hfi_venus.c | 1534 +++++++++++++++++++++++
drivers/media/platform/qcom/vidc/hfi_venus.h | 25 +
drivers/media/platform/qcom/vidc/hfi_venus_io.h | 98 ++
3 files changed, 1657 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.c
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus.h
create mode 100644 drivers/media/platform/qcom/vidc/hfi_venus_io.h

diff --git a/drivers/media/platform/qcom/vidc/hfi_venus.c b/drivers/media/platform/qcom/vidc/hfi_venus.c
new file mode 100644
index 000000000000..0799a2f13f8b
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/hfi_venus.c
@@ -0,0 +1,1534 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/qcom_scm.h>
+#include <linux/slab.h>
+
+#include "core.h"
+#include "hfi_cmds.h"
+#include "hfi_msgs.h"
+ dma_addr_t da; /* device address */
+ void *kva; /* kernel virtual address */
+ u32 size;
+ unsigned long attrs;
+};
+
+struct iface_queue {
+ struct hfi_queue_header *qhdr;
+ struct mem_desc qmem;
+};
+
+enum venus_state {
+ VENUS_STATE_DEINIT = 1,
+ VENUS_STATE_INIT,
+};
+
+struct venus_hfi_device {
+ struct device *dev;
+ void __iomem *base;
+ u32 irq_status;
+ u32 last_packet_type;
+ bool power_enabled;
+ bool suspended;
+ struct completion pwr_collapse_prep;
+ struct completion release_resource;
+ struct mutex lock;
+ struct mem_desc ifaceq_table;
+ struct mem_desc sfr;
+ struct iface_queue queues[IFACEQ_NUM];
+ const struct vidc_resources *res;
+ return -EINVAL;
+
+ qhdr = queue->qhdr;
+ if (!qhdr)
+ return -EINVAL;
+
+ venus_dump_packet(hdev, packet);
+
+ dwords = (*(u32 *)packet) >> 2;
+ if (!dwords)
+ return -EINVAL;
+
+
+ return 0;
+}
+
+static int venus_read_queue(struct venus_hfi_device *hdev,
+ struct iface_queue *queue, void *pkt, u32 *tx_req)
+{
+ struct hfi_queue_header *qhdr;
+ u32 dwords, new_rd_idx;
+ u32 rd_idx, wr_idx, type, qsize;
+ u32 *rd_ptr;
+ u32 recv_request = 0;
+ int ret = 0;
+
+ if (!queue->qmem.kva)
+ return -EINVAL;
+
+ qhdr = queue->qhdr;
+ if (!qhdr)
+ return -EINVAL;
+
+ type = qhdr->type;
+ rd_idx = qhdr->read_idx;
+ wr_idx = qhdr->write_idx;
+ qsize = qhdr->q_size;
+
+ /* make sure data is valid before using it */
+ rmb();
+
+ /*
+ * Do not set receive request for debug queue, if set, Venus generates
+ * interrupt for debug messages even when there is no response message
+ * available. In general debug queue will not become full as it is being
+ * emptied out for every interrupt from Venus. Venus will anyway
+ * generates interrupt if it is full.
+ */
+ if (type & HFI_CTRL_TO_HOST_MSG_Q)
+ recv_request = 1;
+
+ if (rd_idx == wr_idx) {
+ qhdr->rx_req = recv_request;
+ *tx_req = 0;
+ /* update rx_req field in memory */
+ wmb();
+ return -ENODATA;
+ }
+
+ rd_ptr = (u32 *)(queue->qmem.kva + (rd_idx << 2));
+ dwords = *rd_ptr >> 2;
+ if (!dwords)
+ return -EINVAL;
+
+
+ return ret;
+}
+
+static int venus_alloc(struct venus_hfi_device *hdev, struct mem_desc *desc,
+ u32 size)
+{
+ desc->attrs = DMA_ATTR_WRITE_COMBINE;
+ desc->size = ALIGN(size, SZ_4K);
+
+ desc->kva = dma_alloc_attrs(hdev->dev, size, &desc->da, GFP_KERNEL,
+ desc->attrs);
+ if (!desc->kva)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void venus_free(struct venus_hfi_device *hdev, struct mem_desc *mem)
+{
+ dma_free_attrs(hdev->dev, mem->size, mem->kva, mem->da, mem->attrs);
+}
+
+static void venus_writel(struct venus_hfi_device *hdev, u32 reg, u32 value)
+{
+ writel(value, hdev->base + reg);
+}
+
+static u32 venus_readl(struct venus_hfi_device *hdev, u32 reg)
+{
+ return readl(hdev->base + reg);
+}
+
+static void venus_set_registers(struct venus_hfi_device *hdev)
+{
+ const struct reg_val *tbl = hdev->res->reg_tbl;
+ unsigned int count = hdev->res->reg_tbl_size;
+ int i;
+
+ for (i = 0; i < count; i++)
+ venus_writel(hdev, tbl[i].reg, tbl[i].value);
+}
+
+static void venus_soft_int(struct venus_hfi_device *hdev)
+{
+ venus_writel(hdev, CPU_IC_SOFTINT, BIT(CPU_IC_SOFTINT_H2A_SHIFT));
+}
+
+static int venus_iface_cmdq_write_nolock(struct venus_hfi_device *hdev,
+ void *pkt)
+{
+ struct device *dev = hdev->dev;
+ struct hfi_pkt_hdr *cmd_packet;
+ struct iface_queue *queue;
+ u32 rx_req;
+ int ret;
+
+ WARN(!mutex_is_locked(&hdev->lock),
+ "cmd queue write lock must be acquired");
+
+ if (!venus_is_valid_state(hdev)) {
+ dev_err(dev, "%s: fw not in init state\n", __func__);
+ return -EINVAL;
+ }
+
+ cmd_packet = (struct hfi_pkt_hdr *)pkt;
+ hdev->last_packet_type = cmd_packet->pkt_type;
+
+ queue = &hdev->queues[IFACEQ_CMD_IDX];
+
+ ret = venus_write_queue(hdev, queue, pkt, &rx_req);
+ if (ret) {
+ dev_err(dev, "write to iface cmd queue failed (%d)\n", ret);
+ return ret;
+ }
+
+ if (rx_req)
+ venus_soft_int(hdev);
+
+ return 0;
+}
+
+static int venus_iface_cmdq_write(struct venus_hfi_device *hdev, void *pkt)
+{
+ int ret;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_iface_cmdq_write_nolock(hdev, pkt);
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static int venus_hfi_core_set_resource(struct venus_hfi_device *hdev,
+ u32 id, u32 size, u32 addr, void *cookie)
+{
+ struct hfi_sys_set_resource_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ if (id == VIDC_RESOURCE_NONE)
+ return 0;
+
+ pkt = (struct hfi_sys_set_resource_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, sys_set_resource, pkt, id, size, addr,
+ cookie);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_tzbsp_set_video_state(enum tzbsp_video_state state)
+{
+ return qcom_scm_video_set_state(state, 0);
+}
+
+static int venus_reset_core(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ u32 ctrl_status = 0, count = 0;
+ int max_tries = 100, ret = 0;
+
+ venus_writel(hdev, VIDC_CTRL_INIT, BIT(VIDC_CTRL_INIT_CTRL_SHIFT));
+ venus_writel(hdev, WRAPPER_INTR_MASK, WRAPPER_INTR_MASK_A2HVCODEC_MASK);
+ venus_writel(hdev, CPU_CS_SCIACMDARG3, 1);
+
+ while (!ctrl_status && count < max_tries) {
+ ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0);
+ if ((ctrl_status & 0xfe) == 0x4) {
+ dev_err(dev, "invalid setting for UC_REGION\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ usleep_range(500, 1000);
+ count++;
+ }
+
+ if (count >= max_tries)
+ ret = -ETIMEDOUT;
+
+ return ret;
+}
+
+static u32 venus_hwversion(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ u32 ver = venus_readl(hdev, WRAPPER_HW_VERSION);
+ u32 major, minor, step;
+
+ major = ver & WRAPPER_HW_VERSION_MAJOR_VERSION_MASK;
+ major = major >> WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT;
+ minor = ver & WRAPPER_HW_VERSION_MINOR_VERSION_MASK;
+ minor = minor >> WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT;
+ step = ver & WRAPPER_HW_VERSION_STEP_VERSION_MASK;
+
+ dev_dbg(dev, "venus hw version %d.%d.%d\n", major, minor, step);
+
+ return major;
+}
+
+static int venus_run(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ int ret;
+
+ /*
+ * Re-program all of the registers that get reset as a result of
+ * regulator_disable() and _enable()
+ */
+ venus_set_registers(hdev);
+
+ venus_writel(hdev, UC_REGION_ADDR, hdev->ifaceq_table.da);
+ venus_writel(hdev, UC_REGION_SIZE, SHARED_QSIZE);
+ venus_writel(hdev, CPU_CS_SCIACMDARG2, hdev->ifaceq_table.da);
+ venus_writel(hdev, CPU_CS_SCIACMDARG1, 0x01);
+ if (hdev->sfr.da)
+ venus_writel(hdev, SFR_ADDR, hdev->sfr.da);
+
+ ret = venus_reset_core(hdev);
+ if (ret) {
+ dev_err(dev, "failed to reset venus core\n");
+ return ret;
+ }
+
+ venus_hwversion(hdev);
+
+ return 0;
+}
+
+static int venus_halt_axi(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ void __iomem *base = hdev->base;
+ u32 val;
+ int ret;
+
+ /* Halt AXI and AXI IMEM VBIF Access */
+ val = venus_readl(hdev, VBIF_AXI_HALT_CTRL0);
+ val |= VBIF_AXI_HALT_CTRL0_HALT_REQ;
+ venus_writel(hdev, VBIF_AXI_HALT_CTRL0, val);
+
+ /* Request for AXI bus port halt */
+ ret = readl_poll_timeout(base + VBIF_AXI_HALT_CTRL1, val,
+ val & VBIF_AXI_HALT_CTRL1_HALT_ACK,
+ POLL_INTERVAL_US,
+ VBIF_AXI_HALT_ACK_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "AXI bus port halt timeout\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int venus_power_off(struct venus_hfi_device *hdev)
+{
+ int ret;
+
+ if (!hdev->power_enabled)
+ return 0;
+
+ ret = venus_halt_axi(hdev);
+ if (ret)
+ return ret;
+
+ ret = venus_tzbsp_set_video_state(TZBSP_VIDEO_STATE_SUSPEND);
+ if (ret)
+ return ret;
+
+ hdev->power_enabled = false;
+
+ return 0;
+}
+
+static int venus_power_on(struct venus_hfi_device *hdev)
+{
+ int ret;
+
+ if (hdev->power_enabled)
+ return 0;
+
+ ret = venus_tzbsp_set_video_state(TZBSP_VIDEO_STATE_RESUME);
+ if (ret)
+ goto err;
+
+ ret = venus_run(hdev);
+ if (ret)
+ goto err_suspend;
+
+ hdev->power_enabled = true;
+
+ return 0;
+
+err_suspend:
+ venus_tzbsp_set_video_state(TZBSP_VIDEO_STATE_SUSPEND);
+err:
+ hdev->power_enabled = false;
+ return ret;
+}
+
+static int venus_iface_msgq_read_nolock(struct venus_hfi_device *hdev,
+ void *pkt)
+{
+ struct iface_queue *queue;
+ u32 tx_req;
+ int ret;
+
+ if (!venus_is_valid_state(hdev))
+ return -EINVAL;
+
+ queue = &hdev->queues[IFACEQ_MSG_IDX];
+
+ ret = venus_read_queue(hdev, queue, pkt, &tx_req);
+ if (ret)
+ return ret;
+
+ if (tx_req)
+ venus_soft_int(hdev);
+
+ return 0;
+}
+
+static int venus_iface_msgq_read(struct venus_hfi_device *hdev, void *pkt)
+{
+ int ret;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_iface_msgq_read_nolock(hdev, pkt);
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static int venus_iface_dbgq_read_nolock(struct venus_hfi_device *hdev,
+ void *pkt)
+{
+ struct iface_queue *queue;
+ u32 tx_req;
+ int ret;
+
+ ret = venus_is_valid_state(hdev);
+ if (!ret)
+ return -EINVAL;
+
+ queue = &hdev->queues[IFACEQ_DBG_IDX];
+
+ ret = venus_read_queue(hdev, queue, pkt, &tx_req);
+ if (ret)
+ return ret;
+
+ if (tx_req)
+ venus_soft_int(hdev);
+
+ return 0;
+}
+
+static int venus_iface_dbgq_read(struct venus_hfi_device *hdev, void *pkt)
+{
+ int ret;
+
+ if (!pkt)
+ return -EINVAL;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_iface_dbgq_read_nolock(hdev, pkt);
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static void venus_set_qhdr_defaults(struct hfi_queue_header *qhdr)
+{
+ qhdr->status = 1;
+ qhdr->type = IFACEQ_DFLT_QHDR;
+ qhdr->q_size = IFACEQ_QUEUE_SIZE / 4;
+ qhdr->pkt_size = 0;
+ qhdr->rx_wm = 1;
+ qhdr->tx_wm = 1;
+ qhdr->rx_req = 1;
+ qhdr->tx_req = 0;
+ qhdr->rx_irq_status = 0;
+ qhdr->tx_irq_status = 0;
+ qhdr->read_idx = 0;
+ qhdr->write_idx = 0;
+}
+
+static void venus_interface_queues_release(struct venus_hfi_device *hdev)
+{
+ mutex_lock(&hdev->lock);
+
+ venus_free(hdev, &hdev->ifaceq_table);
+ venus_free(hdev, &hdev->sfr);
+
+ memset(hdev->queues, 0, sizeof(hdev->queues));
+ memset(&hdev->ifaceq_table, 0, sizeof(hdev->ifaceq_table));
+ memset(&hdev->sfr, 0, sizeof(hdev->sfr));
+
+ mutex_unlock(&hdev->lock);
+}
+
+static int venus_interface_queues_init(struct venus_hfi_device *hdev)
+{
+ struct hfi_queue_table_header *tbl_hdr;
+ struct iface_queue *queue;
+ struct hfi_sfr *sfr;
+ struct mem_desc desc = {0};
+ unsigned int offset;
+ unsigned int i;
+ int ret;
+
+ ret = venus_alloc(hdev, &desc, ALIGNED_QUEUE_SIZE);
+ if (ret)
+ return ret;
+
+ hdev->ifaceq_table.kva = desc.kva;
+ hdev->ifaceq_table.da = desc.da;
+ hdev->ifaceq_table.size = IFACEQ_TABLE_SIZE;
+ offset = hdev->ifaceq_table.size;
+
+ for (i = 0; i < IFACEQ_NUM; i++) {
+ queue = &hdev->queues[i];
+ queue->qmem.da = desc.da + offset;
+ queue->qmem.kva = desc.kva + offset;
+ queue->qmem.size = IFACEQ_QUEUE_SIZE;
+ offset += queue->qmem.size;
+ queue->qhdr =
+ IFACEQ_GET_QHDR_START_ADDR(hdev->ifaceq_table.kva, i);
+
+ venus_set_qhdr_defaults(queue->qhdr);
+
+ queue->qhdr->start_addr = queue->qmem.da;
+
+ if (i == IFACEQ_CMD_IDX)
+ queue->qhdr->type |= HFI_HOST_TO_CTRL_CMD_Q;
+ else if (i == IFACEQ_MSG_IDX)
+ queue->qhdr->type |= HFI_CTRL_TO_HOST_MSG_Q;
+ else if (i == IFACEQ_DBG_IDX)
+ queue->qhdr->type |= HFI_CTRL_TO_HOST_DBG_Q;
+ }
+
+ tbl_hdr = hdev->ifaceq_table.kva;
+ tbl_hdr->version = 0;
+ tbl_hdr->size = IFACEQ_TABLE_SIZE;
+ tbl_hdr->qhdr0_offset = sizeof(struct hfi_queue_table_header);
+ tbl_hdr->qhdr_size = sizeof(struct hfi_queue_header);
+ tbl_hdr->num_q = IFACEQ_NUM;
+ tbl_hdr->num_active_q = IFACEQ_NUM;
+
+ /*
+ * Set receive request to zero on debug queue as there is no
+ * need of interrupt from video hardware for debug messages
+ */
+ queue = &hdev->queues[IFACEQ_DBG_IDX];
+ queue->qhdr->rx_req = 0;
+
+ ret = venus_alloc(hdev, &desc, ALIGNED_SFR_SIZE);
+ if (ret) {
+ hdev->sfr.da = 0;
+ } else {
+ hdev->sfr.da = desc.da;
+ hdev->sfr.kva = desc.kva;
+ hdev->sfr.size = ALIGNED_SFR_SIZE;
+ sfr = hdev->sfr.kva;
+ sfr->buf_size = ALIGNED_SFR_SIZE;
+ }
+
+ /* ensure table and queue header structs are settled in memory */
+ wmb();
+
+ return 0;
+}
+
+static int venus_sys_set_debug(struct venus_hfi_device *hdev, u32 debug)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_debug_config, pkt, HFI_DEBUG_MODE_QUEUE,
+ debug);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_sys_set_coverage(struct venus_hfi_device *hdev, u32 mode)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_coverage_config, pkt, mode);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_sys_set_idle_message(struct venus_hfi_device *hdev,
+ bool enable)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ if (!enable)
+ return 0;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_idle_indicator, pkt, enable);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_sys_set_power_control(struct venus_hfi_device *hdev,
+ bool enable)
+{
+ struct hfi_sys_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_sys_set_property_pkt *) packet;
+
+ call_hfi_pkt_op(hdev, sys_power_control, pkt, enable);
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_get_queue_size(struct venus_hfi_device *hdev,
+ unsigned int index)
+{
+ struct hfi_queue_header *qhdr;
+
+ if (index >= IFACEQ_NUM)
+ return -EINVAL;
+
+ qhdr = hdev->queues[index].qhdr;
+ if (!qhdr)
+ return -EINVAL;
+
+ return abs(qhdr->read_idx - qhdr->write_idx);
+}
+
+static int venus_sys_set_default_properties(struct venus_hfi_device *hdev)
+{
+ struct device *dev = hdev->dev;
+ int ret;
+
+ ret = venus_sys_set_debug(hdev, venus_fw_debug);
+ if (ret)
+ dev_warn(dev, "setting fw debug msg ON failed (%d)\n", ret);
+
+ ret = venus_sys_set_idle_message(hdev, venus_sys_idle_indicator);
+ if (ret)
+ dev_warn(dev, "setting idle response ON failed (%d)\n", ret);
+
+ ret = venus_sys_set_power_control(hdev, venus_fw_low_power_mode);
+ if (ret)
+ dev_warn(dev, "setting hw power collapse ON failed (%d)\n",
+ ret);
+
+ return ret;
+}
+
+static int venus_session_cmd(struct hfi_inst *inst, u32 pkt_type)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_pkt pkt;
+ int ret;
+
+ call_hfi_pkt_op(hdev, session_cmd, &pkt, pkt_type, inst);
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void venus_flush_debug_queue(struct venus_hfi_device *hdev)
+{
+ void *packet = hdev->dbg_buf;
+ struct device *dev = hdev->dev;
+
+ while (!venus_iface_dbgq_read(hdev, packet)) {
+ struct hfi_msg_sys_coverage_pkt *pkt = packet;
+
+ if (pkt->hdr.pkt_type != HFI_MSG_SYS_COV) {
+ struct hfi_msg_sys_debug_pkt *pkt = packet;
+
+ dev_dbg(dev, "%s", pkt->msg_data);
+ }
+ }
+}
+
+static int venus_prepare_power_collapse(struct venus_hfi_device *hdev)
+{
+ unsigned long timeout = msecs_to_jiffies(venus_hw_rsp_timeout);
+ struct hfi_sys_pc_prep_pkt pkt;
+ int ret;
+
+ init_completion(&hdev->pwr_collapse_prep);
+
+ call_hfi_pkt_op(hdev, sys_pc_prep, &pkt);
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_timeout(&hdev->pwr_collapse_prep, timeout);
+ if (!ret) {
+ venus_flush_debug_queue(hdev);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int venus_are_queues_empty(struct venus_hfi_device *hdev)
+{
+ int ret1, ret2;
+
+ ret1 = venus_get_queue_size(hdev, IFACEQ_MSG_IDX);
+ if (ret1 < 0)
+ return ret1;
+
+ ret2 = venus_get_queue_size(hdev, IFACEQ_CMD_IDX);
+ if (ret2 < 0)
+ return ret2;
+
+ if (!ret1 && !ret2)
+ return 1;
+
+ return 0;
+}
+
+
+ /*
+ * Once SYS_ERROR received from HW, it is safe to halt the AXI.
+ * With SYS_ERROR, Venus FW may have crashed and HW might be
+ * active and causing unnecessary transactions. Hence it is
+ * safe to stop all AXI transactions from venus subsystem.
+ */
+ venus_halt_axi(hdev);
+ venus_sfr_print(hdev);
+}
+
+static irqreturn_t venus_isr_thread(int irq, struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ void *pkt = hdev->pkt_buf;
+ u32 msg_ret;
+
+ if (!hdev)
+ return IRQ_NONE;
+
+ if (hdev->irq_status & WRAPPER_INTR_STATUS_A2HWD_MASK) {
+ venus_sfr_print(hdev);
+ hfi_process_watchdog_timeout(hfi);
+ }
+
+ while (!venus_iface_msgq_read(hdev, pkt)) {
+ msg_ret = hfi_process_msg_packet(hfi, pkt);
+ switch (msg_ret) {
+ case HFI_MSG_EVENT_NOTIFY:
+ venus_process_msg_sys_error(hdev, pkt);
+ break;
+ case HFI_MSG_SYS_INIT:
+ venus_hfi_core_set_resource(hdev, hdev->res->vmem_id,
+ hdev->res->vmem_size,
+ hdev->res->vmem_addr,
+ hdev);
+ break;
+ case HFI_MSG_SYS_RELEASE_RESOURCE:
+ complete(&hdev->release_resource);
+ break;
+ case HFI_MSG_SYS_PC_PREP:
+ complete(&hdev->pwr_collapse_prep);
+ break;
+ default:
+ break;
+ }
+ }
+
+ venus_flush_debug_queue(hdev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t venus_isr(int irq, struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ u32 status;
+
+ if (!hdev)
+ return IRQ_NONE;
+
+ status = venus_readl(hdev, WRAPPER_INTR_STATUS);
+
+ if (status & WRAPPER_INTR_STATUS_A2H_MASK ||
+ status & WRAPPER_INTR_STATUS_A2HWD_MASK ||
+ status & CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK)
+ hdev->irq_status = status;
+
+ venus_writel(hdev, CPU_CS_A2HSOFTINTCLR, 1);
+ venus_writel(hdev, WRAPPER_INTR_CLEAR, status);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static int venus_hfi_core_init(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct device *dev = hfi->dev;
+ struct hfi_sys_get_property_pkt version_pkt;
+ struct hfi_sys_init_pkt pkt;
+ int ret;
+
+ call_hfi_pkt_op(hdev, sys_init, &pkt, HFI_VIDEO_ARCH_OX);
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ return ret;
+
+ call_hfi_pkt_op(hdev, sys_image_version, &version_pkt);
+
+ ret = venus_iface_cmdq_write(hdev, &version_pkt);
+ if (ret)
+ dev_warn(dev, "failed to send image version pkt to fw\n");
+
+ venus_set_state(hdev, VENUS_STATE_INIT);
+
+ return 0;
+}
+
+static int venus_hfi_core_deinit(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+
+ venus_set_state(hdev, VENUS_STATE_DEINIT);
+ hdev->suspended = true;
+
+ return 0;
+}
+
+static int venus_hfi_core_ping(struct hfi_core *hfi, u32 cookie)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct hfi_sys_ping_pkt pkt;
+
+ call_hfi_pkt_op(hdev, sys_ping, &pkt, cookie);
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_core_trigger_ssr(struct hfi_core *hfi,
+ u32 trigger_type)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct hfi_sys_test_ssr_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, ssr_cmd, trigger_type, &pkt);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+int venus_hfi_session_init(struct hfi_core *hfi, struct hfi_inst *inst,
+ u32 session_type, u32 codec)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct hfi_session_init_pkt pkt;
+ int ret;
+
+ inst->priv = hdev;
+
+ ret = venus_sys_set_default_properties(hdev);
+ if (ret)
+ return ret;
+
+ ret = call_hfi_pkt_op(hdev, session_init, &pkt, inst, session_type,
+ codec);
+ if (ret)
+ goto err;
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ venus_flush_debug_queue(hdev);
+ return ret;
+}
+
+static int venus_hfi_session_end(struct hfi_inst *inst)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct device *dev = hdev->dev;
+
+ if (venus_fw_coverage) {
+ if (venus_sys_set_coverage(hdev, venus_fw_coverage))
+ dev_warn(dev, "fw coverage msg ON failed\n");
+ }
+
+ return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_END);
+}
+
+static int venus_hfi_session_abort(struct hfi_inst *inst)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+
+ venus_flush_debug_queue(hdev);
+
+ return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_ABORT);
+}
+
+static int venus_hfi_session_flush(struct hfi_inst *inst, u32 flush_mode)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_flush_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, session_flush, &pkt, inst, flush_mode);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_session_start(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_START);
+}
+
+static int venus_hfi_session_stop(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_STOP);
+}
+
+static int venus_hfi_session_etb(struct hfi_inst *inst,
+ struct hfi_frame_data *in_frame)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ u32 session_type = inst->session_type;
+ int ret;
+
+ if (session_type == VIDC_SESSION_TYPE_DEC) {
+ struct hfi_session_empty_buffer_compressed_pkt pkt;
+
+ ret = call_hfi_pkt_op(hdev, session_etb_decoder, &pkt, inst,
+ in_frame);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ } else if (session_type == VIDC_SESSION_TYPE_ENC) {
+ struct hfi_session_empty_buffer_uncompressed_plane0_pkt pkt;
+
+ ret = call_hfi_pkt_op(hdev, session_etb_encoder, &pkt, inst,
+ in_frame);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, &pkt);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int venus_hfi_session_ftb(struct hfi_inst *inst,
+ struct hfi_frame_data *out_frame)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_fill_buffer_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, session_ftb, &pkt, inst, out_frame);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_session_set_buffers(struct hfi_inst *inst,
+ struct hfi_buffer_desc *bd)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_set_buffers_pkt *pkt;
+ u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE];
+ int ret;
+
+ if (bd->buffer_type == HFI_BUFFER_INPUT)
+ return 0;
+
+ pkt = (struct hfi_session_set_buffers_pkt *)packet;
+
+ ret = call_hfi_pkt_op(hdev, session_set_buffers, pkt, inst, bd);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_release_buffers(struct hfi_inst *inst,
+ struct hfi_buffer_desc *bd)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_release_buffer_pkt *pkt;
+ u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE];
+ int ret;
+
+ if (bd->buffer_type == HFI_BUFFER_INPUT)
+ return 0;
+
+ pkt = (struct hfi_session_release_buffer_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_release_buffers, pkt, inst, bd);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_load_res(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_LOAD_RESOURCES);
+}
+
+static int venus_hfi_session_release_res(struct hfi_inst *inst)
+{
+ return venus_session_cmd(inst, HFI_CMD_SESSION_RELEASE_RESOURCES);
+}
+
+static int venus_hfi_session_parse_seq_hdr(struct hfi_inst *inst,
+ u32 seq_hdr, u32 seq_hdr_len)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_parse_sequence_header_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_session_parse_sequence_header_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_parse_seq_header,
+ pkt, inst, seq_hdr, seq_hdr_len);
+ if (ret)
+ return ret;
+
+ ret = venus_iface_cmdq_write(hdev, pkt);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int venus_hfi_session_get_seq_hdr(struct hfi_inst *inst,
+ u32 seq_hdr, u32 seq_hdr_len)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_get_sequence_header_pkt *pkt;
+ u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_session_get_sequence_header_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_get_seq_hdr, pkt, inst, seq_hdr,
+ seq_hdr_len);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_set_property(struct hfi_inst *inst, u32 ptype,
+ void *pdata)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_set_property_pkt *pkt;
+ u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE];
+ int ret;
+
+ pkt = (struct hfi_session_set_property_pkt *) packet;
+
+ ret = call_hfi_pkt_op(hdev, session_set_property, pkt, inst, ptype,
+ pdata);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, pkt);
+}
+
+static int venus_hfi_session_get_property(struct hfi_inst *inst, u32 ptype)
+{
+ struct venus_hfi_device *hdev = inst->priv;
+ struct hfi_session_get_property_pkt pkt;
+ int ret;
+
+ ret = call_hfi_pkt_op(hdev, session_get_property, &pkt, inst, ptype);
+ if (ret)
+ return ret;
+
+ return venus_iface_cmdq_write(hdev, &pkt);
+}
+
+static int venus_hfi_resume(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ int ret = 0;
+
+ mutex_lock(&hdev->lock);
+
+ if (hdev->suspended == false)
+ goto unlock;
+
+ ret = venus_power_on(hdev);
+
+unlock:
+ if (!ret)
+ hdev->suspended = false;
+
+ mutex_unlock(&hdev->lock);
+
+ return ret;
+}
+
+static int venus_hfi_suspend(struct hfi_core *hfi)
+{
+ struct venus_hfi_device *hdev = to_hfi_priv(hfi);
+ struct device *dev = hfi->dev;
+ u32 ctrl_status;
+ int ret;
+
+ if (!hdev->power_enabled || hdev->suspended)
+ return 0;
+
+ mutex_lock(&hdev->lock);
+ ret = venus_is_valid_state(hdev);
+ mutex_unlock(&hdev->lock);
+
+ if (!ret) {
+ dev_err(dev, "bad state, cannot suspend\n");
+ return -EINVAL;
+ }
+
+ ret = venus_prepare_power_collapse(hdev);
+ if (ret) {
+ dev_err(dev, "prepare for power collapse fail (%d)\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&hdev->lock);
+
+ if (hdev->last_packet_type != HFI_CMD_SYS_PC_PREP) {
+ mutex_unlock(&hdev->lock);
+ return -EINVAL;
+ }
+
+ ret = venus_are_queues_empty(hdev);
+ if (ret < 0 || !ret) {
+ mutex_unlock(&hdev->lock);
+ return -EINVAL;
+ }
+
+ ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0);
+ if (!(ctrl_status & CPU_CS_SCIACMDARG0_PC_READY)) {
+ mutex_unlock(&hdev->lock);
+ return -EINVAL;
+ }
+
+ ret = venus_power_off(hdev);
+ if (ret) {
+ mutex_unlock(&hdev->lock);
+ return ret;
+ }
+
+ hdev->suspended = true;
+
+ mutex_unlock(&hdev->lock);
+
+ return 0;
+}
+
+ struct venus_hfi_device *hdev;
+ int ret;
+
+ hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
+ if (!hdev)
+ return -ENOMEM;
+
+ mutex_init(&hdev->lock);
+
+ hdev->res = res;
+ hdev->pkt_ops = hfi->pkt_ops;
+ hdev->packetization_type = HFI_PACKETIZATION_LEGACY;
+ hdev->base = base;
+ hdev->dev = hfi->dev;
+ hdev->suspended = true;
+
+ hfi->priv = hdev;
+ hfi->ops = &venus_hfi_ops;
+ hfi->core_caps = VIDC_ENC_ROTATION_CAPABILITY |
+ VIDC_ENC_SCALING_CAPABILITY |
+ VIDC_ENC_DEINTERLACE_CAPABILITY |
+ VIDC_DEC_MULTI_STREAM_CAPABILITY;
+
+ ret = venus_interface_queues_init(hdev);
+ if (ret)
+ goto err_kfree;
+
+ return 0;
+
+err_kfree:
+ kfree(hdev);
+ hfi->priv = NULL;
+ hfi->ops = NULL;
+ return ret;
+}
diff --git a/drivers/media/platform/qcom/vidc/hfi_venus.h b/drivers/media/platform/qcom/vidc/hfi_venus.h
new file mode 100644
index 000000000000..c2e6e4235a0b
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/hfi_venus.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VENUS_HFI_H__
+#define __VENUS_HFI_H__
+
+struct hfi_core;
+struct vidc_resources;
+
+void venus_hfi_destroy(struct hfi_core *hfi);
+int venus_hfi_create(struct hfi_core *hfi, const struct vidc_resources *res,
+ void __iomem *base);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/hfi_venus_io.h b/drivers/media/platform/qcom/vidc/hfi_venus_io.h
new file mode 100644
index 000000000000..7df09fb72011
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/hfi_venus_io.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:19 AM9/7/16
to
Adds binding document for vidc video encoder/decoder driver

Cc: Rob Herring <rob...@kernel.org>
Cc: Mark Rutland <mark.r...@arm.com>
Cc: devic...@vger.kernel.org
Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
.../devicetree/bindings/media/qcom,vidc.txt | 61 ++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,vidc.txt

diff --git a/Documentation/devicetree/bindings/media/qcom,vidc.txt b/Documentation/devicetree/bindings/media/qcom,vidc.txt
new file mode 100644
index 000000000000..0d50a7b2e3ed
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/qcom,vidc.txt
@@ -0,0 +1,61 @@
+* Qualcomm video encoder/decoder accelerator
+
+- compatible:
+ Usage: required
+ Value type: <stringlist>
+ Definition: Value should contain
+ - "qcom,vidc-msm8916"
+ - "qcom,vidc-msm8996"
+- reg:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Register ranges as listed in the reg-names property
+
+- interrupts:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition:
+
+- power-domains:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A phandle and power domain specifier pairs to the
+ power domain which is responsible for collapsing
+ and restoring power to the peripheral
+
+- clocks:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: List of phandle and clock specifier pairs as listed
+ in clock-names property
+- clock-names:
+ Usage: required
+ Value type: <stringlist>
+ Definition: Should contain the following entries
+ - "core" Core video accelerator clock
+ - "iface" Video accelerator AHB clock
+ - "bus" Video accelerator AXI clock
+- rproc:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A phandle to remote processor responsible for
+ firmware loading
+
+- iommus:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A list of phandle and IOMMU specifier pairs
+
+* An Example
+ qcom,vidc@1d00000 {
+ compatible = "qcom,vidc-msm8916";
+ reg = <0x01d00000 0xff000>;
+ clocks = <&gcc GCC_VENUS0_VCODEC0_CLK>,
+ <&gcc GCC_VENUS0_AHB_CLK>,
+ <&gcc GCC_VENUS0_AXI_CLK>;
+ clock-names = "core", "iface", "bus";
+ interrupts = <GIC_SPI 44 0>;
+ power-domains = <&gcc VENUS_GDSC>;
+ rproc = <&vidc_rproc>;
+ iommus = <&apps_iommu 5>;
+ };
--
2.7.4

Stanimir Varbanov

unread,
Sep 7, 2016, 7:50:29 AM9/7/16
to
This adds encoder part of the driver plus encoder controls.

Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
---
drivers/media/platform/qcom/vidc/venc.c | 1252 +++++++++++++++++++++++++
drivers/media/platform/qcom/vidc/venc.h | 29 +
drivers/media/platform/qcom/vidc/venc_ctrls.c | 396 ++++++++
drivers/media/platform/qcom/vidc/venc_ctrls.h | 23 +
4 files changed, 1700 insertions(+)
create mode 100644 drivers/media/platform/qcom/vidc/venc.c
create mode 100644 drivers/media/platform/qcom/vidc/venc.h
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.c
create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.h

diff --git a/drivers/media/platform/qcom/vidc/venc.c b/drivers/media/platform/qcom/vidc/venc.c
new file mode 100644
index 000000000000..3b65f851a807
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc.c
@@ -0,0 +1,1252 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <media/videobuf2-dma-sg.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ctrls.h>
+
+#include "core.h"
+#include "helpers.h"
+#include "venc_ctrls.h"
+
+#define NUM_B_FRAMES_MAX 4
+
+static u32 get_framesize_nv12(int plane, u32 height, u32 width)
+{
+ u32 y_stride, uv_stride, y_plane;
+ u32 y_sclines, uv_sclines, uv_plane;
+ u32 size;
+
+ y_stride = ALIGN(width, 128);
+ uv_stride = ALIGN(width, 128);
+ y_sclines = ALIGN(height, 32);
+ uv_sclines = ALIGN(((height + 1) >> 1), 16);
+
+ y_plane = y_stride * y_sclines;
+ uv_plane = uv_stride * uv_sclines + SZ_4K;
+ size = y_plane + uv_plane + SZ_8K;
+ size = ALIGN(size, SZ_4K);
+
+ return size;
+}
+
+static u32 get_framesize_compressed(u32 height, u32 width)
+{
+ u32 sz = ALIGN(height, 32) * ALIGN(width, 32) * 3 / 2;
+
+ return ALIGN(sz, SZ_4K);
+}
+
+static const struct vidc_format venc_formats[] = {
+ {
+ .pixfmt = V4L2_PIX_FMT_NV12,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG4,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_H263,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_H264,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }, {
+ .pixfmt = V4L2_PIX_FMT_VP8,
+ .num_planes = 1,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ },
+};
+
+static const struct vidc_format *find_format(u32 pixfmt, int type)
+{
+ const struct vidc_format *fmt = venc_formats;
+ unsigned int size = ARRAY_SIZE(venc_formats);
+ unsigned int i;
+
+ for (i = 0; i < size; i++) {
+ if (fmt[i].pixfmt == pixfmt)
+ break;
+ }
+
+ if (i == size || fmt[i].type != type)
+ return NULL;
+
+ return &fmt[i];
+}
+
+static const struct vidc_format *find_format_by_index(int index, int type)
+{
+ const struct vidc_format *fmt = venc_formats;
+ unsigned int size = ARRAY_SIZE(venc_formats);
+ int i, k = 0;
+
+ if (index < 0 || index > size)
+ return NULL;
+
+ for (i = 0; i < size; i++) {
+ if (fmt[i].type != type)
+ continue;
+ if (k == index)
+ break;
+ k++;
+ }
+
+ if (i == size)
+ return NULL;
+
+ return &fmt[i];
+}
+
+ }
+
+ return 0;
+}
+
+static int venc_set_properties(struct vidc_inst *inst)
+{
+ struct venc_controls *ctr = &inst->controls.enc;
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct hfi_intra_period intra_period;
+ struct hfi_profile_level pl;
+ struct hfi_framerate frate;
+ struct hfi_bitrate brate;
+ struct hfi_idr_period idrp;
+ u32 ptype, rate_control, bitrate, profile = 0, level = 0;
+ int ret;
+
+ ptype = HFI_PROPERTY_CONFIG_FRAME_RATE;
+ frate.buffer_type = HFI_BUFFER_OUTPUT;
+ frate.framerate = inst->fps * (1 << 16);
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &frate);
+ if (ret)
+ return ret;
+
+ if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) {
+ struct hfi_h264_vui_timing_info info;
+
+ ptype = HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO;
+ info.enable = 1;
+ info.fixed_framerate = 1;
+ info.time_scale = NSEC_PER_SEC;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &info);
+ if (ret)
+ return ret;
+ }
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD;
+ idrp.idr_period = ctr->gop_size;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &idrp);
+ if (ret)
+ return ret;
+
+ if (ctr->num_b_frames) {
+ u32 max_num_b_frames = NUM_B_FRAMES_MAX;
+
+ ptype = HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst,
+ ptype, &max_num_b_frames);
+ if (ret)
+ return ret;
+ }
+
+ /* intra_period = pframes + bframes + 1 */
+ if (!ctr->num_p_frames)
+ ctr->num_p_frames = 2 * 15 - 1,
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD;
+ intra_period.pframes = ctr->num_p_frames;
+ intra_period.bframes = ctr->num_b_frames;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &intra_period);
+ if (ret)
+ return ret;
+
+ if (ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR)
+ rate_control = HFI_RATE_CONTROL_VBR_CFR;
+ else
+ rate_control = HFI_RATE_CONTROL_CBR_CFR;
+
+ ptype = HFI_PROPERTY_PARAM_VENC_RATE_CONTROL;
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &rate_control);
+ if (ret)
+ return ret;
+
+ if (!ctr->bitrate)
+ bitrate = 64000;
+ else
+ bitrate = ctr->bitrate;
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE;
+ brate.bitrate = bitrate;
+ brate.layer_id = 0;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &brate);
+ if (ret)
+ return ret;
+
+ if (!ctr->bitrate_peak)
+ bitrate *= 2;
+ else
+ bitrate = ctr->bitrate_peak;
+
+ ptype = HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE;
+ brate.bitrate = bitrate;
+ brate.layer_id = 0;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &brate);
+ if (ret)
+ return ret;
+
+ if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) {
+ profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ ctr->profile);
+ level = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ ctr->level);
+ } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_VP8) {
+ profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_VPX_PROFILE,
+ ctr->profile);
+ level = 0;
+ } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_MPEG4) {
+ profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+ ctr->profile);
+ level = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+ ctr->level);
+ } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H263) {
+ profile = 0;
+ level = 0;
+ }
+
+ ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT;
+ pl.profile = profile;
+ pl.level = level;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &pl);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+venc_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+ strlcpy(cap->driver, VIDC_DRV_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, "video encoder", sizeof(cap->card));
+ strlcpy(cap->bus_info, "platform:vidc", sizeof(cap->bus_info));
+
+ return 0;
+}
+
+static int venc_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+ const struct vidc_format *fmt;
+
+ fmt = find_format_by_index(f->index, f->type);
+
+ memset(f->reserved, 0, sizeof(f->reserved));
+
+ if (!fmt)
+ return -EINVAL;
+
+ f->pixelformat = fmt->pixfmt;
+
+ return 0;
+}
+
+static const struct vidc_format *
+venc_try_fmt_common(struct vidc_inst *inst, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt;
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+ const struct vidc_format *fmt;
+ unsigned int p;
+
+ memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved));
+ memset(pixmp->reserved, 0, sizeof(pixmp->reserved));
+
+ fmt = find_format(pixmp->pixelformat, f->type);
+ if (!fmt) {
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ pixmp->pixelformat = V4L2_PIX_FMT_H264;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ pixmp->pixelformat = V4L2_PIX_FMT_NV12;
+ else
+ return NULL;
+ fmt = find_format(pixmp->pixelformat, f->type);
+ pixmp->width = 1280;
+ pixmp->height = 720;
+ }
+
+ pixmp->height = ALIGN(pixmp->height, 32);
+
+ pixmp->width = clamp(pixmp->width, hfi_inst->width.min,
+ hfi_inst->width.max);
+ pixmp->height = clamp(pixmp->height, hfi_inst->height.min,
+ hfi_inst->height.max);
+ if (pixmp->field == V4L2_FIELD_ANY)
+ pixmp->field = V4L2_FIELD_NONE;
+ pixmp->num_planes = fmt->num_planes;
+ pixmp->flags = 0;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ for (p = 0; p < pixmp->num_planes; p++) {
+ pfmt[p].sizeimage = get_framesize_nv12(p, pixmp->height,
+ pixmp->width);
+
+ pfmt[p].bytesperline = ALIGN(pixmp->width, 128);
+ }
+ } else {
+ pfmt[0].sizeimage = get_framesize_compressed(pixmp->height,
+ pixmp->width);
+ pfmt[0].bytesperline = 0;
+ }
+
+ return fmt;
+}
+
+static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ const struct vidc_format *fmt;
+
+ fmt = venc_try_fmt_common(inst, f);
+ if (!fmt)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_pix_format_mplane orig_pixmp;
+ const struct vidc_format *fmt;
+ struct v4l2_format format;
+ u32 pixfmt_out = 0, pixfmt_cap = 0;
+
+ orig_pixmp = *pixmp;
+
+ fmt = venc_try_fmt_common(inst, f);
+ if (!fmt)
+ return -EINVAL;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixfmt_out = pixmp->pixelformat;
+ pixfmt_cap = inst->fmt_cap->pixfmt;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixfmt_cap = pixmp->pixelformat;
+ pixfmt_out = inst->fmt_out->pixfmt;
+ }
+
+ memset(&format, 0, sizeof(format));
+
+ format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ format.fmt.pix_mp.pixelformat = pixfmt_out;
+ format.fmt.pix_mp.width = orig_pixmp.width;
+ format.fmt.pix_mp.height = orig_pixmp.height;
+ venc_try_fmt_common(inst, &format);
+ inst->out_width = format.fmt.pix_mp.width;
+ inst->out_height = format.fmt.pix_mp.height;
+ inst->colorspace = pixmp->colorspace;
+ inst->ycbcr_enc = pixmp->ycbcr_enc;
+ inst->quantization = pixmp->quantization;
+ inst->xfer_func = pixmp->xfer_func;
+
+ memset(&format, 0, sizeof(format));
+
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ format.fmt.pix_mp.pixelformat = pixfmt_cap;
+ format.fmt.pix_mp.width = orig_pixmp.width;
+ format.fmt.pix_mp.height = orig_pixmp.height;
+ venc_try_fmt_common(inst, &format);
+ inst->width = format.fmt.pix_mp.width;
+ inst->height = format.fmt.pix_mp.height;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ inst->fmt_out = fmt;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ inst->fmt_cap = fmt;
+
+ return 0;
+}
+
+static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct vidc_inst *inst = to_inst(file);
+ const struct vidc_format *fmt;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ fmt = inst->fmt_cap;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ fmt = inst->fmt_out;
+ else
+ return -EINVAL;
+
+ pixmp->pixelformat = fmt->pixfmt;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixmp->width = inst->width;
+ pixmp->height = inst->height;
+ pixmp->colorspace = inst->colorspace;
+ pixmp->ycbcr_enc = inst->ycbcr_enc;
+ pixmp->quantization = inst->quantization;
+ pixmp->xfer_func = inst->xfer_func;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixmp->width = inst->out_width;
+ pixmp->height = inst->out_height;
+ }
+
+ venc_try_fmt_common(inst, f);
+
+ return 0;
+}
+
+static int venc_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ s->r.top = 0;
+ s->r.left = 0;
+ s->r.width = inst->width;
+ s->r.height = inst->height;
+ break;
+ case V4L2_SEL_TGT_CROP:
+ s->r.top = 0;
+ s->r.left = 0;
+ s->r.width = inst->out_width;
+ s->r.height = inst->out_height;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int venc_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ return -EINVAL;
+}
+
+static int
+venc_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ if (!b->count)
+ vb2_core_queue_release(queue);
+
+ return vb2_reqbufs(queue, b);
+}
+
+static int venc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+ unsigned int p;
+ int ret;
+
+ ret = vb2_querybuf(queue, b);
+ if (ret)
+ return ret;
+
+ if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ b->memory == V4L2_MEMORY_MMAP) {
+ for (p = 0; p < b->length; p++)
+ b->m.planes[p].m.mem_offset += DST_QUEUE_OFF_BASE;
+ }
+
+ return 0;
+}
+
+static int venc_create_bufs(struct file *file, void *fh,
+ struct v4l2_create_buffers *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->format.type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_create_bufs(queue, b);
+}
+
+static int venc_prepare_buf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_prepare_buf(queue, b);
+}
+
+static int venc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_qbuf(queue, b);
+}
+
+static int
+venc_exportbuf(struct file *file, void *fh, struct v4l2_exportbuffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_expbuf(queue, b);
+}
+
+static int venc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_dqbuf(queue, b, file->f_flags & O_NONBLOCK);
+}
+
+static int venc_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_streamon(queue, type);
+}
+
+static int venc_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct vb2_queue *queue = vidc_to_vb2q(file, type);
+
+ if (!queue)
+ return -EINVAL;
+
+ return vb2_streamoff(queue, type);
+}
+
+static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct vidc_inst *inst = to_inst(file);
+ struct v4l2_outputparm *out = &a->parm.output;
+ struct v4l2_fract *timeperframe = &out->timeperframe;
+ u64 us_per_frame, fps;
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ memset(out->reserved, 0, sizeof(out->reserved));
+
+ if (!timeperframe->denominator)
+ timeperframe->denominator = inst->timeperframe.denominator;
+ if (!timeperframe->numerator)
+ timeperframe->numerator = inst->timeperframe.numerator;
+
+ out->capability = V4L2_CAP_TIMEPERFRAME;
+
+ us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC;
+ do_div(us_per_frame, timeperframe->denominator);
+
+ if (!us_per_frame)
+ return -EINVAL;
+
+ fps = (u64)USEC_PER_SEC;
+ do_div(fps, us_per_frame);
+
+ inst->timeperframe = *timeperframe;
+ inst->fps = fps;
+
+ return 0;
+}
+
+static int venc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct vidc_inst *inst = to_inst(file);
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ a->parm.output.capability |= V4L2_CAP_TIMEPERFRAME;
+ a->parm.output.timeperframe = inst->timeperframe;
+
+ return 0;
+}
+
+static int venc_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct hfi_inst *inst = to_hfi_inst(file);
+ const struct vidc_format *fmt;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+
+ fmt = find_format(fsize->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (!fmt) {
+ fmt = find_format(fsize->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+ if (!fmt)
+ return -EINVAL;
+ }
+
+ if (fsize->index)
+ return -EINVAL;
+
+ fsize->stepwise.min_width = inst->width.min;
+ fsize->stepwise.max_width = inst->width.max;
+ fsize->stepwise.step_width = inst->width.step_size;
+ fsize->stepwise.min_height = inst->height.min;
+ fsize->stepwise.max_height = inst->height.max;
+ fsize->stepwise.step_height = inst->height.step_size;
+
+ return 0;
+}
+
+static int venc_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *fival)
+{
+ struct hfi_inst *inst = to_hfi_inst(file);
+ const struct vidc_format *fmt;
+
+ fival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+
+ fmt = find_format(fival->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (!fmt) {
+ fmt = find_format(fival->pixel_format,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+ if (!fmt)
+ return -EINVAL;
+ }
+
+ if (fival->index)
+ return -EINVAL;
+
+ if (!fival->width || !fival->height)
+ return -EINVAL;
+
+ if (fival->width > inst->width.max ||
+ fival->width < inst->width.min ||
+ fival->height > inst->height.max ||
+ fival->height < inst->height.min)
+ return -EINVAL;
+
+ fival->stepwise.min.numerator = 1;
+ fival->stepwise.min.denominator = inst->framerate.max;
+ fival->stepwise.max.numerator = 1;
+ fival->stepwise.max.denominator = inst->framerate.min;
+ fival->stepwise.step.numerator = 1;
+ fival->stepwise.step.denominator = inst->framerate.max;
+
+ return 0;
+}
+
+static int venc_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_EOS:
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_src_change_event_subscribe(fh, sub);
+ case V4L2_EVENT_CTRL:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ default:
+ return -EINVAL;
+ }
+}
+
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int venc_init_session(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ u32 pixfmt = inst->fmt_cap->pixfmt;
+ struct hfi_framesize fs;
+ u32 ptype;
+ int ret;
+
+ ret = vidc_hfi_session_init(hfi, inst->hfi_inst, pixfmt,
+ VIDC_SESSION_TYPE_ENC);
+ if (ret)
+ return ret;
+
+ ptype = HFI_PROPERTY_PARAM_FRAME_SIZE;
+ fs.buffer_type = HFI_BUFFER_INPUT;
+ fs.width = inst->out_width;
+ fs.height = inst->out_height;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fs);
+ if (ret)
+ goto err;
+
+ fs.buffer_type = HFI_BUFFER_OUTPUT;
+ fs.width = inst->width;
+ fs.height = inst->height;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype, &fs);
+ if (ret)
+ goto err;
+
+ pixfmt = inst->fmt_out->pixfmt;
+
+ ret = vidc_set_color_format(inst, HFI_BUFFER_INPUT, pixfmt);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+ return ret;
+}
+
+static int venc_cap_num_buffers(struct vidc_inst *inst,
+ struct hfi_buffer_requirements *bufreq)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct device *dev = inst->core->dev;
+ int ret, ret2;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = venc_init_session(inst);
+ if (ret)
+ goto put_sync;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_OUTPUT, bufreq);
+
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+
+put_sync:
+ ret2 = pm_runtime_put_sync(dev);
+
+ return ret ? ret : ret2;
+}
+
+static int venc_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_buffer_requirements bufreq;
+ unsigned int p;
+ int ret = 0;
+
+ switch (q->type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ *num_planes = inst->fmt_out->num_planes;
+ *num_buffers = clamp_val(*num_buffers, 5, VIDEO_MAX_FRAME);
+ inst->num_input_bufs = *num_buffers;
+
+ for (p = 0; p < *num_planes; ++p)
+ sizes[p] = get_framesize_nv12(p, inst->height,
+ inst->width);
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ *num_planes = inst->fmt_cap->num_planes;
+
+ ret = venc_cap_num_buffers(inst, &bufreq);
+ if (ret)
+ break;
+
+ *num_buffers = max(*num_buffers, bufreq.count_actual);
+ inst->num_output_bufs = *num_buffers;
+
+ sizes[0] = get_framesize_compressed(inst->height, inst->width);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int venc_check_configuration(struct vidc_inst *inst)
+{
+ struct hfi_buffer_requirements bufreq;
+ int ret;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq);
+ if (ret)
+ return ret;
+
+ if (inst->num_output_bufs < bufreq.count_actual ||
+ inst->num_output_bufs < bufreq.count_min)
+ return -EINVAL;
+
+ ret = vidc_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq);
+ if (ret)
+ return ret;
+
+ if (inst->num_input_bufs < bufreq.count_actual ||
+ inst->num_input_bufs < bufreq.count_min)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int venc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct vidc_inst *inst = vb2_get_drv_priv(q);
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct device *dev = inst->core->dev;
+ struct hfi_buffer_count_actual buf_count;
+ struct vb2_queue *other_queue;
+ u32 ptype;
+ int ret;
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ other_queue = &inst->bufq_cap;
+ else
+ other_queue = &inst->bufq_out;
+
+ if (!vb2_is_streaming(other_queue))
+ return 0;
+
+ inst->sequence = 0;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = venc_init_session(inst);
+ if (ret)
+ goto put_sync;
+
+ ret = venc_set_properties(inst);
+ if (ret)
+ goto deinit_sess;
+
+ ret = venc_check_configuration(inst);
+ if (ret)
+ goto deinit_sess;
+
+ ptype = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL;
+ buf_count.type = HFI_BUFFER_OUTPUT;
+ buf_count.count_actual = inst->num_output_bufs;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &buf_count);
+ if (ret)
+ goto deinit_sess;
+
+ buf_count.type = HFI_BUFFER_INPUT;
+ buf_count.count_actual = inst->num_input_bufs;
+
+ ret = vidc_hfi_session_set_property(hfi, inst->hfi_inst, ptype,
+ &buf_count);
+ if (ret)
+ goto deinit_sess;
+
+ ret = vidc_vb2_start_streaming(inst);
+ if (ret)
+ goto deinit_sess;
+
+ return 0;
+
+deinit_sess:
+ vidc_hfi_session_deinit(hfi, inst->hfi_inst);
+put_sync:
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static const struct vb2_ops venc_vb2_ops = {
+ .queue_setup = venc_queue_setup,
+ .buf_init = vidc_vb2_buf_init,
+ .buf_prepare = vidc_vb2_buf_prepare,
+ .start_streaming = venc_start_streaming,
+ .stop_streaming = vidc_vb2_stop_streaming,
+ .buf_queue = vidc_vb2_buf_queue,
+};
+
+static int venc_empty_buf_done(struct hfi_inst *hfi_inst, u32 addr,
+ u32 bytesused, u32 data_offset, u32 flags)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct vb2_v4l2_buffer *vbuf;
+ enum vb2_buffer_state state;
+ struct vb2_buffer *vb;
+
+ vbuf = vidc_vb2_find_buf(inst, addr);
+ if (!vbuf)
+ return -EINVAL;
+
+ vb = &vbuf->vb2_buf;
+ vb->planes[0].bytesused = bytesused;
+ vb->planes[0].data_offset = data_offset;
+ vbuf->flags = flags;
+ state = VB2_BUF_STATE_DONE;
+
+ if (data_offset > vb->planes[0].length ||
+ bytesused > vb->planes[0].length)
+ state = VB2_BUF_STATE_ERROR;
+
+ vb2_buffer_done(vb, state);
+
+ return 0;
+}
+
+static int venc_fill_buf_done(struct hfi_inst *hfi_inst, u32 addr,
+ u32 bytesused, u32 data_offset, u32 flags,
+ struct timeval *timestamp)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct vb2_v4l2_buffer *vbuf;
+ enum vb2_buffer_state state;
+ struct vb2_buffer *vb;
+
+ vbuf = vidc_vb2_find_buf(inst, addr);
+ if (!vbuf)
+ return -EINVAL;
+
+ vb = &vbuf->vb2_buf;
+ vb->planes[0].bytesused = bytesused;
+ vb->planes[0].data_offset = data_offset;
+ vb->timestamp = timeval_to_ns(timestamp);
+ vbuf->flags = flags;
+ vbuf->sequence = inst->sequence++;
+ state = VB2_BUF_STATE_DONE;
+
+ if (data_offset > vb->planes[0].length ||
+ bytesused > vb->planes[0].length)
+ state = VB2_BUF_STATE_ERROR;
+
+ vb2_buffer_done(vb, state);
+
+ return 0;
+}
+
+static int venc_event_notify(struct hfi_inst *hfi_inst, u32 event,
+ struct hfi_event_data *data)
+{
+ struct vidc_inst *inst = hfi_inst->ops_priv;
+ struct device *dev = inst->core->dev;
+
+ switch (event) {
+ case EVT_SESSION_ERROR:
+ if (hfi_inst) {
+ mutex_lock(&hfi_inst->lock);
+ inst->hfi_inst->state = INST_INVALID;
+ mutex_unlock(&hfi_inst->lock);
+ }
+ dev_err(dev, "enc: event session error (inst:%p)\n", hfi_inst);
+ break;
+ case EVT_SYS_EVENT_CHANGE:
+ switch (data->event_type) {
+ case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES:
+ dev_dbg(dev, "event sufficient resources\n");
+ break;
+ case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES:
+ inst->reconfig_height = data->height;
+ inst->reconfig_width = data->width;
+ inst->in_reconfig = true;
+ dev_dbg(dev, "event not sufficient resources\n");
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hfi_inst_ops venc_hfi_ops = {
+ .empty_buf_done = venc_empty_buf_done,
+ .fill_buf_done = venc_fill_buf_done,
+ .event_notify = venc_event_notify,
+};
+
+static void venc_inst_init(struct vidc_inst *inst)
+{
+ struct hfi_inst *hfi_inst = inst->hfi_inst;
+
+ inst->fmt_cap = &venc_formats[2];
+ inst->fmt_out = &venc_formats[0];
+ inst->width = 1280;
+ inst->height = ALIGN(720, 32);
+ inst->fps = 15;
+ inst->timeperframe.numerator = 1;
+ inst->timeperframe.denominator = 15;
+
+ hfi_inst->width.min = 64;
+ hfi_inst->width.max = 1920;
+ hfi_inst->width.step_size = 1;
+ hfi_inst->height.min = 64;
+ hfi_inst->height.max = ALIGN(1080, 32);
+ hfi_inst->height.step_size = 1;
+ hfi_inst->framerate.min = 1;
+ hfi_inst->framerate.max = 30;
+ hfi_inst->framerate.step_size = 1;
+ hfi_inst->mbs_per_frame.min = 16;
+ hfi_inst->mbs_per_frame.max = 8160;
+}
+
+int venc_init(struct vidc_core *core, struct video_device *enc,
+ const struct v4l2_file_operations *fops)
+{
+ int ret;
+
+ enc->release = video_device_release_empty;
+ enc->fops = fops;
+ enc->ioctl_ops = &venc_ioctl_ops;
+ enc->vfl_dir = VFL_DIR_M2M;
+ enc->v4l2_dev = &core->v4l2_dev;
+ enc->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+
+ ret = video_register_device(enc, VFL_TYPE_GRABBER, -1);
+ if (ret)
+ return ret;
+
+ video_set_drvdata(enc, core);
+
+ return 0;
+}
+
+void venc_deinit(struct vidc_core *core, struct video_device *enc)
+{
+ video_unregister_device(enc);
+}
+
+int venc_open(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+ struct vb2_queue *q;
+ int ret;
+
+ ret = venc_ctrl_init(inst);
+ if (ret)
+ return ret;
+
+ inst->hfi_inst = vidc_hfi_session_create(hfi, &venc_hfi_ops, inst);
+ if (IS_ERR(inst->hfi_inst)) {
+ ret = PTR_ERR(inst->hfi_inst);
+ goto err_ctrl_deinit;
+ }
+
+ venc_inst_init(inst);
+
+ q = &inst->bufq_cap;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->ops = &venc_vb2_ops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->drv_priv = inst;
+ q->buf_struct_size = sizeof(struct vidc_buffer);
+ q->allow_zero_bytesused = 1;
+ q->dev = inst->core->dev;
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_session_destroy;
+
+ q = &inst->bufq_out;
+ q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->ops = &venc_vb2_ops;
+ q->mem_ops = &vb2_dma_sg_memops;
+ q->drv_priv = inst;
+ q->buf_struct_size = sizeof(struct vidc_buffer);
+ q->allow_zero_bytesused = 1;
+ q->dev = inst->core->dev;
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_cap_queue_release;
+
+ return 0;
+
+err_cap_queue_release:
+ vb2_queue_release(&inst->bufq_cap);
+err_session_destroy:
+ vidc_hfi_session_destroy(hfi, inst->hfi_inst);
+ inst->hfi_inst = NULL;
+err_ctrl_deinit:
+ venc_ctrl_deinit(inst);
+ return ret;
+}
+
+void venc_close(struct vidc_inst *inst)
+{
+ struct hfi_core *hfi = &inst->core->hfi;
+
+ vb2_queue_release(&inst->bufq_out);
+ vb2_queue_release(&inst->bufq_cap);
+ venc_ctrl_deinit(inst);
+ vidc_hfi_session_destroy(hfi, inst->hfi_inst);
+}
diff --git a/drivers/media/platform/qcom/vidc/venc.h b/drivers/media/platform/qcom/vidc/venc.h
new file mode 100644
index 000000000000..0afab2e433f9
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_VENC_H__
+#define __VIDC_VENC_H__
+
+struct vidc_core;
+struct video_device;
+struct vidc_inst;
+struct v4l2_file_operations;
+
+int venc_init(struct vidc_core *core, struct video_device *enc,
+ const struct v4l2_file_operations *fops);
+void venc_deinit(struct vidc_core *core, struct video_device *enc);
+int venc_open(struct vidc_inst *inst);
+void venc_close(struct vidc_inst *inst);
+
+#endif
diff --git a/drivers/media/platform/qcom/vidc/venc_ctrls.c b/drivers/media/platform/qcom/vidc/venc_ctrls.c
new file mode 100644
index 000000000000..61331f95d54a
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc_ctrls.c
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#include <linux/types.h>
+#include <media/v4l2-ctrls.h>
+
+#include "core.h"
+
+#define BITRATE_MIN 32000
+#define BITRATE_MAX 160000000
+#define BITRATE_DEFAULT 1000000
+#define BITRATE_DEFAULT_PEAK (BITRATE_DEFAULT * 2)
+#define BITRATE_STEP 100
+#define SLICE_BYTE_SIZE_MAX 1024
+#define SLICE_BYTE_SIZE_MIN 1024
+#define SLICE_MB_SIZE_MAX 300
+#define INTRA_REFRESH_MBS_MAX 300
+#define AT_SLICE_BOUNDARY \
+ V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY
+static struct vidc_ctrl venc_ctrls[] = {
+ {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
+ .max = V4L2_MPEG_VIDEO_BITRATE_MODE_CBR,
+ .def = V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
+ .menu_skip_mask = ~((1 << V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) |
+ (1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = BITRATE_MIN,
+ .max = BITRATE_MAX,
+ .def = BITRATE_DEFAULT,
+ .step = BITRATE_STEP,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = BITRATE_MIN,
+ .max = BITRATE_MAX,
+ .def = BITRATE_DEFAULT_PEAK,
+ .step = BITRATE_STEP,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC,
+ .max = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC,
+ .def = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .max = V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY,
+ .def = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) |
+ (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)
+ ),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ .max = V4L2_MPEG_VIDEO_MPEG4_LEVEL_5,
+ .def = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .max = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH,
+ .def = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)
+ ),
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+ .max = V4L2_MPEG_VIDEO_H264_LEVEL_5_1,
+ .def = V4L2_MPEG_VIDEO_H264_LEVEL_5_0,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = 3,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 26,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 28,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 30,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 1,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 51,
+ .def = 51,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE,
+ .max = V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES,
+ .def = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = SLICE_BYTE_SIZE_MIN,
+ .max = SLICE_BYTE_SIZE_MAX,
+ .def = SLICE_BYTE_SIZE_MIN,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = SLICE_MB_SIZE_MAX,
+ .def = 1,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = -6,
+ .max = 6,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = -6,
+ .max = 6,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED,
+ .max = AT_SLICE_BOUNDARY,
+ .def = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_HEADER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE,
+ .max = V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+ .def = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE,
+ .menu_skip_mask =
+ 1 << V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = INTRA_REFRESH_MBS_MAX,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .min = 0,
+ .max = 1,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .min = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED,
+ .max = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED,
+ .def = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 16) - 1,
+ .def = 12,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 16) - 1,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .min = 0,
+ .max = 1,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 128,
+ .def = 1,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 1,
+ .max = 128,
+ .def = 128,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_B_FRAMES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = INT_MAX,
+ .def = 0,
+ .step = 1,
+ }, {
+ .id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 16) - 1,
+ .step = 1,
+ .def = 0,
+ },
+};
+
+#define NUM_CTRLS ARRAY_SIZE(venc_ctrls)
+
+static int venc_op_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vidc_inst *inst = ctrl_to_inst(ctrl);
+ struct venc_controls *ctr = &inst->controls.enc;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
+ ctr->bitrate_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE:
+ ctr->bitrate = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK:
+ ctr->bitrate_peak = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
+ ctr->h264_entropy_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ case V4L2_CID_MPEG_VIDEO_VPX_PROFILE:
+ ctr->profile = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ ctr->level = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
+ ctr->h264_i_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
+ ctr->h264_p_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP:
+ ctr->h264_b_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_MIN_QP:
+ ctr->h264_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_MAX_QP:
+ ctr->h264_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE:
+ ctr->multi_slice_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES:
+ ctr->multi_slice_max_bytes = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB:
+ ctr->multi_slice_max_mb = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA:
+ ctr->h264_loop_filter_alpha = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA:
+ ctr->h264_loop_filter_beta = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE:
+ ctr->h264_loop_filter_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEADER_MODE:
+ ctr->header_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB:
+ break;
+ case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+ ctr->gop_size = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
+ ctr->h264_i_period = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE:
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC:
+ case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE:
+ case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM:
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP:
+ ctr->vp8_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP:
+ ctr->vp8_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_B_FRAMES:
+ ctr->num_b_frames = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops venc_ctrl_ops = {
+ .s_ctrl = venc_op_s_ctrl,
+};
+
+int venc_ctrl_init(struct vidc_inst *inst)
+{
+ unsigned int i;
+ int ret;
+
+ ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, NUM_CTRLS);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < NUM_CTRLS; i++) {
+ struct v4l2_ctrl *ctrl;
+
+ if (venc_ctrls[i].type == V4L2_CTRL_TYPE_MENU) {
+ ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler,
+ &venc_ctrl_ops, venc_ctrls[i].id,
+ venc_ctrls[i].max,
+ venc_ctrls[i].menu_skip_mask,
+ venc_ctrls[i].def);
+ } else {
+ ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler,
+ &venc_ctrl_ops, venc_ctrls[i].id,
+ venc_ctrls[i].min,
+ venc_ctrls[i].max,
+ venc_ctrls[i].step,
+ venc_ctrls[i].def);
+ }
+
+ ret = inst->ctrl_handler.error;
+ if (ret) {
+ v4l2_ctrl_handler_free(&inst->ctrl_handler);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void venc_ctrl_deinit(struct vidc_inst *inst)
+{
+ v4l2_ctrl_handler_free(&inst->ctrl_handler);
+}
diff --git a/drivers/media/platform/qcom/vidc/venc_ctrls.h b/drivers/media/platform/qcom/vidc/venc_ctrls.h
new file mode 100644
index 000000000000..4441f550f57d
--- /dev/null
+++ b/drivers/media/platform/qcom/vidc/venc_ctrls.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2016 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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 General Public License for more details.
+ *
+ */
+#ifndef __VIDC_VENC_CTRLS_H__
+#define __VIDC_VENC_CTRLS_H__
+
+struct vidc_inst;
+
+int venc_ctrl_init(struct vidc_inst *inst);
+void venc_ctrl_deinit(struct vidc_inst *inst);
+
+#endif
--
2.7.4

Rob Herring

unread,
Sep 16, 2016, 10:30:08 AM9/16/16
to
On Wed, Sep 07, 2016 at 02:37:02PM +0300, Stanimir Varbanov wrote:
> Adds binding document for vidc video encoder/decoder driver
>
> Cc: Rob Herring <rob...@kernel.org>
> Cc: Mark Rutland <mark.r...@arm.com>
> Cc: devic...@vger.kernel.org
> Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
> ---
> .../devicetree/bindings/media/qcom,vidc.txt | 61 ++++++++++++++++++++++
> 1 file changed, 61 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/qcom,vidc.txt
>
> diff --git a/Documentation/devicetree/bindings/media/qcom,vidc.txt b/Documentation/devicetree/bindings/media/qcom,vidc.txt
> new file mode 100644
> index 000000000000..0d50a7b2e3ed
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,vidc.txt
> @@ -0,0 +1,61 @@
> +* Qualcomm video encoder/decoder accelerator
> +
> +- compatible:
> + Usage: required
> + Value type: <stringlist>
> + Definition: Value should contain

... one of:

> + - "qcom,vidc-msm8916"
> + - "qcom,vidc-msm8996"
> +- reg:
> + Usage: required
> + Value type: <prop-encoded-array>
> + Definition: Register ranges as listed in the reg-names property
> +
> +- interrupts:
> + Usage: required
> + Value type: <prop-encoded-array>
> + Definition:

How many interrupts?

> +
> +- power-domains:
> + Usage: required
> + Value type: <prop-encoded-array>
> + Definition: A phandle and power domain specifier pairs to the
> + power domain which is responsible for collapsing
> + and restoring power to the peripheral

How many power domains?
node names should be generic: video-codec@

Hans Verkuil

unread,
Sep 19, 2016, 6:10:09 AM9/19/16
to
This can't happen.

> + fmt = find_format(pixmp->pixelformat, f->type);
> + pixmp->width = 1280;
> + pixmp->height = 720;

Why would you update the width/height as well?
This is almost certainly wrong.

For capture I would expect that you can do compose, but not crop.

This would likely explain that v4l2-compliance thinks that the driver can
scale:

test Scaling: OK

> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + s->r.top = 0;
> + s->r.left = 0;
> + s->r.width = inst->out_width;
> + s->r.height = inst->out_height;
> +
> + return 0;
> +}
> +
> +static int
> +vdec_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
> +{
> + struct vb2_queue *queue = vidc_to_vb2q(file, b->type);
> +
> + if (!queue)
> + return -EINVAL;
> +
> + if (!b->count)
> + vb2_core_queue_release(queue);

Drop this, it's handled by vb2_reqbufs.

> +
> + return vb2_reqbufs(queue, b);
> +}
> +
> +static int
> +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
> +{
> + strlcpy(cap->driver, VIDC_DRV_NAME, sizeof(cap->driver));
> + strlcpy(cap->card, "video decoder", sizeof(cap->card));

"video decoder" is *very* generic, I recommend putting Qualcomm in the card description.

> + strlcpy(cap->bus_info, "platform:vidc", sizeof(cap->bus_info));

Same really for vidc: I strongly recommend something like "qcom-vidc".
Ditto for VIDC_DRV_NAME.
Can queue ever be NULL?
On error start_streaming should call vb2_buffer_done with state VB2_BUF_STATE_QUEUED
for all buffers that are queued in the driver.

Basically the same what happens in stop_streaming, just with a different state.

This ensures that the buffers are given back to vb2 on start_streaming failure.
Shouldn't timestamp and sequence be filled in here?
I think this is wrong. Since you're using video_device_alloc, this should point to
video_device_release. Otherwise the struct is never freed.
Regards,

Hans

Hans Verkuil

unread,
Sep 19, 2016, 6:20:04 AM9/19/16
to
On 09/07/2016 01:37 PM, Stanimir Varbanov wrote:
> This consists of video decoder implementation plus decoder
> controls.
>
> Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
> ---
> drivers/media/platform/qcom/vidc/vdec.c | 1091 +++++++++++++++++++++++++
> drivers/media/platform/qcom/vidc/vdec.h | 29 +
> drivers/media/platform/qcom/vidc/vdec_ctrls.c | 200 +++++
> drivers/media/platform/qcom/vidc/vdec_ctrls.h | 21 +
> 4 files changed, 1341 insertions(+)
> create mode 100644 drivers/media/platform/qcom/vidc/vdec.c
> create mode 100644 drivers/media/platform/qcom/vidc/vdec.h
> create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.c
> create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.h
>

<snip>

> +static int vdec_event_notify(struct hfi_inst *hfi_inst, u32 event,
> + struct hfi_event_data *data)
> +{
> + struct vidc_inst *inst = hfi_inst->ops_priv;
> + struct device *dev = inst->core->dev;
> + const struct v4l2_event ev = { .type = V4L2_EVENT_SOURCE_CHANGE };

1) this can be static
2) set the u.src_change.changes as well.

<snip>

Hans Verkuil

unread,
Sep 19, 2016, 6:20:05 AM9/19/16
to
Many of my review comments for the decoder apply to the encoder as well,
so I won't repeat those.

On 09/07/2016 01:37 PM, Stanimir Varbanov wrote:
> This adds encoder part of the driver plus encoder controls.
>
> Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
> ---
> drivers/media/platform/qcom/vidc/venc.c | 1252 +++++++++++++++++++++++++
> drivers/media/platform/qcom/vidc/venc.h | 29 +
> drivers/media/platform/qcom/vidc/venc_ctrls.c | 396 ++++++++
> drivers/media/platform/qcom/vidc/venc_ctrls.h | 23 +
> 4 files changed, 1700 insertions(+)
> create mode 100644 drivers/media/platform/qcom/vidc/venc.c
> create mode 100644 drivers/media/platform/qcom/vidc/venc.h
> create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.c
> create mode 100644 drivers/media/platform/qcom/vidc/venc_ctrls.h
>
> diff --git a/drivers/media/platform/qcom/vidc/venc.c b/drivers/media/platform/qcom/vidc/venc.c
> new file mode 100644
> index 000000000000..3b65f851a807
> --- /dev/null
> +++ b/drivers/media/platform/qcom/vidc/venc.c
> @@ -0,0 +1,1252 @@

<snip>

> +static int venc_s_selection(struct file *file, void *fh,
> + struct v4l2_selection *s)
> +{
> + return -EINVAL;
> +}

Huh? Either remove this, or implement this correctly.

<snip>

> +static int venc_subscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + switch (sub->type) {
> + case V4L2_EVENT_EOS:
> + return v4l2_event_subscribe(fh, sub, 2, NULL);
> + case V4L2_EVENT_SOURCE_CHANGE:
> + return v4l2_src_change_event_subscribe(fh, sub);

These two events aren't used in this driver AFAICT, so this can be dropped.

Since that leaves just V4L2_EVENT_CTRL this function can be replaced by
v4l2_ctrl_subscribe_event().

Regards,

Hans

Hans Verkuil

unread,
Sep 19, 2016, 6:50:05 AM9/19/16
to
Hi Stanimir,

I've finished my review of this patch series.

I'll be traveling for the next three weeks, so you can take your time with
making a v3 since it is very unlikely I'll be able to review it before I'm
back mid-October.

Thanks for working on this!

Regards,

Hans

On 09/07/2016 01:37 PM, Stanimir Varbanov wrote:

Stanimir Varbanov

unread,
Sep 26, 2016, 3:50:05 PM9/26/16
to
Hi Rob,

Thanks for the review!
It is one, thanks for the catch.

>
>> +
>> +- power-domains:
>> + Usage: required
>> + Value type: <prop-encoded-array>
>> + Definition: A phandle and power domain specifier pairs to the
>> + power domain which is responsible for collapsing
>> + and restoring power to the peripheral
>
> How many power domains?

Good question, for vidc-msm8916 it is one power-domain, for vidc-msm8996
the power domains should be 3. Here the problem is that the genpd
doesn't permit more than one power-domain per device.
correct, will update it in next version.

--
regards,
Stan

Stanimir Varbanov

unread,
Sep 28, 2016, 8:50:06 PM9/28/16
to
Hi Hans,

Thanks for the comments!
I wonder what will happen if the userspace pass format type
V4L2_BUF_TYPE_VIDEO_CAPTURE? Looking into check_fmt in v4l2-ioctls.c it
seems that the userspace is allowed to pass V4L2_BUF_TYPE_VIDEO_CAPTURE
even when ops->vidioc_g_fmt_vid_cap is not implemented.

>
>> + fmt = find_format(pixmp->pixelformat, f->type);
>> + pixmp->width = 1280;
>> + pixmp->height = 720;
>
> Why would you update the width/height as well?

If pixelformat is zero find_format will fail, so I need to populate with
some default values, and I decided to default to h264 and 720p.
OK, then I will return EINVAL for all V4L2_SEL_TGT_CROP_xxx targets, is
that what you suggested?

BTW I think that the hardware supports scailing but I will leave this
for future improvements.
Oops, I forgot that from the initial comment.

>
>> +
>> + return vb2_reqbufs(queue, b);
>> +}
>> +
>> +static int
>> +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
>> +{
>> + strlcpy(cap->driver, VIDC_DRV_NAME, sizeof(cap->driver));
>> + strlcpy(cap->card, "video decoder", sizeof(cap->card));
>
> "video decoder" is *very* generic, I recommend putting Qualcomm in the card description.
>
>> + strlcpy(cap->bus_info, "platform:vidc", sizeof(cap->bus_info));
>
> Same really for vidc: I strongly recommend something like "qcom-vidc".
> Ditto for VIDC_DRV_NAME.

I agree with your suggestion, but I already have comment from Bjorn to
rename the driver to hfi or venus or something better.
yes, probably. If the format.type is V4L2_BUF_TYPE_VIDEO_CAPTURE (not a
MPLAIN) and the driver does not support non-mplain formats.

I agree that this !queue check is annoying.
OK.
No because I don't have the timestamp in the packet which I received
from firmware when the input (compressed frame) has been consumed.
The is freed in vidc_remove platform driver method, but you might be
right that dec->release = video_device_release
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majo...@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>

--
regards,
Stan

Stanimir Varbanov

unread,
Sep 28, 2016, 9:00:05 PM9/28/16
to
Hi,
Sure will do.

--
regards,
Stan

Stanimir Varbanov

unread,
Sep 28, 2016, 9:00:06 PM9/28/16
to
Hi Hans,

On 09/19/2016 01:15 PM, Hans Verkuil wrote:
> Many of my review comments for the decoder apply to the encoder as well,
> so I won't repeat those.

Sure, will address them too.
OK, I cannot remember why I keep it this way, might be v4l2-compliance

>
> <snip>
>
>> +static int venc_subscribe_event(struct v4l2_fh *fh,
>> + const struct v4l2_event_subscription *sub)
>> +{
>> + switch (sub->type) {
>> + case V4L2_EVENT_EOS:
>> + return v4l2_event_subscribe(fh, sub, 2, NULL);
>> + case V4L2_EVENT_SOURCE_CHANGE:
>> + return v4l2_src_change_event_subscribe(fh, sub);
>
> These two events aren't used in this driver AFAICT, so this can be dropped.
>
> Since that leaves just V4L2_EVENT_CTRL this function can be replaced by
> v4l2_ctrl_subscribe_event().

sure I can remove it.

--
regards,
Stan

Stanimir Varbanov

unread,
Nov 3, 2016, 6:50:04 AM11/3/16
to
Hi Hans,

On 09/19/2016 01:04 PM, Hans Verkuil wrote:
> On 09/07/2016 01:37 PM, Stanimir Varbanov wrote:
>> This consists of video decoder implementation plus decoder
>> controls.
>>
>> Signed-off-by: Stanimir Varbanov <stanimir...@linaro.org>
>> ---
>> drivers/media/platform/qcom/vidc/vdec.c | 1091 +++++++++++++++++++++++++
>> drivers/media/platform/qcom/vidc/vdec.h | 29 +
>> drivers/media/platform/qcom/vidc/vdec_ctrls.c | 200 +++++
>> drivers/media/platform/qcom/vidc/vdec_ctrls.h | 21 +
>> 4 files changed, 1341 insertions(+)
>> create mode 100644 drivers/media/platform/qcom/vidc/vdec.c
>> create mode 100644 drivers/media/platform/qcom/vidc/vdec.h
>> create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.c
>> create mode 100644 drivers/media/platform/qcom/vidc/vdec_ctrls.h
>>

<cut>

>> +
>> +static int
>> +vdec_g_selection(struct file *file, void *priv, struct v4l2_selection *s)
>> +{
>> + struct vidc_inst *inst = to_inst(file);
>> +
>> + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
>> + return -EINVAL;
>> +
>> + switch (s->target) {
>> + case V4L2_SEL_TGT_CROP_DEFAULT:
>> + case V4L2_SEL_TGT_CROP_BOUNDS:
>> + case V4L2_SEL_TGT_CROP:
>> + case V4L2_SEL_TGT_COMPOSE_DEFAULT:
>> + case V4L2_SEL_TGT_COMPOSE_BOUNDS:
>> + case V4L2_SEL_TGT_COMPOSE:
>
> This is almost certainly wrong.
>
> For capture I would expect that you can do compose, but not crop.
>
> This would likely explain that v4l2-compliance thinks that the driver can
> scale:
>
> test Scaling: OK
>

Maybe I need some help to implement correctly g_selection.

Lets say that the resolution of the compressed stream is 1280x720, and
that resolution is set with s_fmt(OUTPUT queue), then I calculate the
output resolution which I will return by g_fmt(CAPTURE queue) and it
will be 1280x736 (hardware wants height to be multiple of 32 lines). So
the result will be 16 lines of vertical padding which should be exposed
to client (think of gstreamer v4l2videodec element) via g_crop
(g_selection) as 1280x720 because this is the actual image.

So from what I understood while read Selection API, I need to support
only composing on CAPTURE queue, no scaling and no cropping.

OUTPUT buffer type, data source
TGT_CROP_BOUNDS = TGT_CROP_DEFAULT = TGT_CROP = 1280x720
TGT_COMPOSE_BOUNDS = TGT_COMPOSE_DEFAULT = TGT_COMPOSE = 1280x720

CAPTURE buffer type, data sink
TGT_CROP_BOUNDS = TGT_CROP_DEFAULT = TGT_CROP = EINVAL
TGT_COMPOSE_BOUNDS = 1280x736
TGT_COMPOSE_DEFAULT = TGT_COMPOSE = 1280x720

With this logic in g_selection the output of v4l2-compliance test
application is:
test Cropping: OK
test Composing: OK
test Scaling: OK (Not Supported)

So why v4l2-compliance still thinks that the driver supports Cropping?

>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + s->r.top = 0;
>> + s->r.left = 0;
>> + s->r.width = inst->out_width;
>> + s->r.height = inst->out_height;
>> +
>> + return 0;
>> +}

<cut>

--
regards,
Stan

Hans Verkuil

unread,
Nov 3, 2016, 7:00:05 AM11/3/16
to
The output buffer type doesn't do composition, only crop. So you shouldn't
support the compose targets.

>
> CAPTURE buffer type, data sink
> TGT_CROP_BOUNDS = TGT_CROP_DEFAULT = TGT_CROP = EINVAL
> TGT_COMPOSE_BOUNDS = 1280x736
> TGT_COMPOSE_DEFAULT = TGT_COMPOSE = 1280x720

This looks good.

>
> With this logic in g_selection the output of v4l2-compliance test
> application is:
> test Cropping: OK
> test Composing: OK
> test Scaling: OK (Not Supported)
>
> So why v4l2-compliance still thinks that the driver supports Cropping?

Hmm, v4l2-compliance could be improved. The problem is that it doesn't
show for m2m devices for which side (capture or output) cropping, composing
or scaling is supported. It does test both sides, but you can't tell from
the output.

It's a bit confusing in this case.

Regards,

Hans

Stanimir Varbanov

unread,
Nov 3, 2016, 8:50:06 AM11/3/16
to
Hi,
OK I tried to return EINVAL for compose targets for output buffer type
and the result is :

test Cropping: OK
test Composing: OK
test Scaling: OK

Which is odd because now Scaling is supported.

>
>>
>> CAPTURE buffer type, data sink
>> TGT_CROP_BOUNDS = TGT_CROP_DEFAULT = TGT_CROP = EINVAL
>> TGT_COMPOSE_BOUNDS = 1280x736
>> TGT_COMPOSE_DEFAULT = TGT_COMPOSE = 1280x720
>
> This looks good.
>
>>
>> With this logic in g_selection the output of v4l2-compliance test
>> application is:
>> test Cropping: OK
>> test Composing: OK
>> test Scaling: OK (Not Supported)
>>
>> So why v4l2-compliance still thinks that the driver supports Cropping?
>
> Hmm, v4l2-compliance could be improved. The problem is that it doesn't
> show for m2m devices for which side (capture or output) cropping, composing
> or scaling is supported. It does test both sides, but you can't tell from
> the output.
>
> It's a bit confusing in this case.

OK I will incorporate the above g_selection behavior and send a new
version of the patches, I do not waste time on that. The drawback is
that this padding will be displayed as green bar on bottom of the
displayed image, although the gstreamer master branch now use
g_selection so probably it should be fine there.

>
> Regards,
>
> Hans
>
>>
>>>> + break;
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + s->r.top = 0;
>>>> + s->r.left = 0;
>>>> + s->r.width = inst->out_width;
>>>> + s->r.height = inst->out_height;
>>>> +
>>>> + return 0;
>>>> +}
>>
>> <cut>
>>

--
regards,
Stan
0 new messages