2018-08-31 08:54:43

by Maxime Jourdan

[permalink] [raw]
Subject: [PATCH 0/4] Add Amlogic video decoder driver

Hi everyone,

This patch series adds support for the Amlogic video decoder,
as well as the corresponding dt bindings for GXBB/GXL/GXM chips.

It features decoding for the following formats:
- MPEG 1
- MPEG 2

The following formats will be added in future patches:
- MJPEG
- MPEG 4 (incl. Xvid, H.263)
- H.264
- HEVC (incl. 10-bit)

The following formats' development has still not started, but they are
supported by the hardware:
- VC1
- VP9

The code was made in such a way to allow easy inclusion of those formats
in the future.

The decoder is single instance.

Files:
- vdec.c handles the V4L2 M2M logic
- esparser.c manages the hardware bitstream parser
- vdec_helpers.c provides helpers to DONE the dst buffers as well as
various common code used by the codecs
- vdec_1.c manages the VDEC_1 block of the vdec IP
- codec_mpeg12.c enables decoding for MPEG 1/2.
- vdec_platform.c links codec units with vdec units
(e.g vdec_1 with codec_mpeg12) and lists all the available
src/dst formats and requirements (max width/height, etc.),
per compatible chip.

Firmwares are necessary to run the vdec. They can currently be found at:
https://github.com/chewitt/meson-firmware

It was tested primarily with ffmpeg's v4l2-m2m implementation. For instance:
$ ffmpeg -c:v mpeg2_v4l2m2m -i sample_mpeg2.mkv -f null -

Note: This patch series depends on
"[PATCH v3 0/3] soc: amlogic: add meson-canvas"
https://patchwork.kernel.org/cover/10573763/

The v4l2-compliance results are available below the patch diff.

Maxime Jourdan (4):
dt-bindings: media: add Amlogic Video Decoder Bindings
media: meson: add v4l2 m2m video decoder driver
ARM64: dts: meson-gx: add vdec entry
ARM64: dts: meson: add vdec entries

.../bindings/media/amlogic,vdec.txt | 63 ++
arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 13 +
arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 11 +
arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 11 +
arch/arm64/boot/dts/amlogic/meson-gxm.dtsi | 4 +
drivers/media/platform/Kconfig | 10 +
drivers/media/platform/meson/Makefile | 1 +
drivers/media/platform/meson/vdec/Makefile | 8 +
.../media/platform/meson/vdec/codec_mpeg12.c | 169 +++
.../media/platform/meson/vdec/codec_mpeg12.h | 13 +
drivers/media/platform/meson/vdec/dos_regs.h | 97 ++
drivers/media/platform/meson/vdec/esparser.c | 367 +++++++
drivers/media/platform/meson/vdec/esparser.h | 27 +
drivers/media/platform/meson/vdec/vdec.c | 987 ++++++++++++++++++
drivers/media/platform/meson/vdec/vdec.h | 233 +++++
drivers/media/platform/meson/vdec/vdec_1.c | 227 ++++
drivers/media/platform/meson/vdec/vdec_1.h | 13 +
.../media/platform/meson/vdec/vdec_helpers.c | 353 +++++++
.../media/platform/meson/vdec/vdec_helpers.h | 44 +
.../media/platform/meson/vdec/vdec_platform.c | 100 ++
.../media/platform/meson/vdec/vdec_platform.h | 29 +
21 files changed, 2780 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,vdec.txt
create mode 100644 drivers/media/platform/meson/vdec/Makefile
create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c
create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h
create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h
create mode 100644 drivers/media/platform/meson/vdec/esparser.c
create mode 100644 drivers/media/platform/meson/vdec/esparser.h
create mode 100644 drivers/media/platform/meson/vdec/vdec.c
create mode 100644 drivers/media/platform/meson/vdec/vdec.h
create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c
create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h
create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c
create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h
create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c
create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h

root@libretech-cc:~/v4l-utils# v4l2-compliance -d /dev/video0
v4l2-compliance SHA: not available, 64 bits

Compliance test for device /dev/video0:

Driver Info:
Driver name : meson-vdec
Card type : Amlogic Video Decoder
Bus info : platform:meson-vdec
Driver version : 4.18.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

Required ioctls:
test VIDIOC_QUERYCAP: OK

Allow for multiple opens:
test second /dev/video0 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 (Not Supported)
test VIDIOC_QUERYCTRL: OK (Not Supported)
test VIDIOC_G/S_CTRL: OK (Not Supported)
test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 0 Private Controls: 0

Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK (Not Supported)
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

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

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

--
2.18.0



2018-08-31 08:54:34

by Maxime Jourdan

[permalink] [raw]
Subject: [PATCH 2/4] media: meson: add v4l2 m2m video decoder driver

Amlogic SoCs feature a powerful video decoder unit able to
decode many formats, with a performance of usually up to 4k60.

This is a driver for this IP that is based around the v4l2 m2m framework.

It features decoding for:
- MPEG 1
- MPEG 2

Supported SoCs are: GXBB (S905), GXL (S905X/W/D), GXM (S912)

There is also a hardware bitstream parser (ESPARSER) that is handled here.

Signed-off-by: Maxime Jourdan <[email protected]>
---
drivers/media/platform/Kconfig | 10 +
drivers/media/platform/meson/Makefile | 1 +
drivers/media/platform/meson/vdec/Makefile | 8 +
.../media/platform/meson/vdec/codec_mpeg12.c | 170 +++
.../media/platform/meson/vdec/codec_mpeg12.h | 14 +
drivers/media/platform/meson/vdec/dos_regs.h | 98 ++
drivers/media/platform/meson/vdec/esparser.c | 368 +++++++
drivers/media/platform/meson/vdec/esparser.h | 28 +
drivers/media/platform/meson/vdec/vdec.c | 988 ++++++++++++++++++
drivers/media/platform/meson/vdec/vdec.h | 234 +++++
drivers/media/platform/meson/vdec/vdec_1.c | 228 ++++
drivers/media/platform/meson/vdec/vdec_1.h | 14 +
.../media/platform/meson/vdec/vdec_helpers.c | 354 +++++++
.../media/platform/meson/vdec/vdec_helpers.h | 45 +
.../media/platform/meson/vdec/vdec_platform.c | 101 ++
.../media/platform/meson/vdec/vdec_platform.h | 30 +
16 files changed, 2691 insertions(+)
create mode 100644 drivers/media/platform/meson/vdec/Makefile
create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c
create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h
create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h
create mode 100644 drivers/media/platform/meson/vdec/esparser.c
create mode 100644 drivers/media/platform/meson/vdec/esparser.h
create mode 100644 drivers/media/platform/meson/vdec/vdec.c
create mode 100644 drivers/media/platform/meson/vdec/vdec.h
create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c
create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h
create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c
create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h
create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c
create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 2728376b04b5..1c33d95dd92f 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -482,6 +482,16 @@ config VIDEO_QCOM_VENUS
on various Qualcomm SoCs.
To compile this driver as a module choose m here.

+config VIDEO_MESON_VDEC
+ tristate "Amlogic video decoder driver"
+ depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA
+ depends on (ARCH_MESON) || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ select MESON_CANVAS
+ help
+ Support for the video decoder found in gxbb/gxl/gxm chips.
+
endif # V4L_MEM2MEM_DRIVERS

# TI VIDEO PORT Helper Modules
diff --git a/drivers/media/platform/meson/Makefile b/drivers/media/platform/meson/Makefile
index 597beb8f34d1..f7c6e1031f25 100644
--- a/drivers/media/platform/meson/Makefile
+++ b/drivers/media/platform/meson/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_VIDEO_MESON_AO_CEC) += ao-cec.o
+obj-$(CONFIG_VIDEO_MESON_VDEC) += vdec/
diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile
new file mode 100644
index 000000000000..6bea129084b7
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for Amlogic meson video decoder driver
+
+meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o
+meson-vdec-objs += vdec_1.o
+meson-vdec-objs += codec_mpeg12.o
+
+obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o
diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.c b/drivers/media/platform/meson/vdec/codec_mpeg12.c
new file mode 100644
index 000000000000..18709319cff7
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/codec_mpeg12.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "vdec_helpers.h"
+#include "dos_regs.h"
+
+#define SIZE_WORKSPACE SZ_128K
+/* Offset substracted by the firmware from the workspace paddr */
+#define WORKSPACE_OFFSET (5 * SZ_1K)
+
+/* map firmware registers to known MPEG1/2 functions */
+#define MREG_SEQ_INFO AV_SCRATCH_4
+#define MREG_PIC_INFO AV_SCRATCH_5
+#define MREG_PIC_WIDTH AV_SCRATCH_6
+#define MREG_PIC_HEIGHT AV_SCRATCH_7
+#define MREG_BUFFERIN AV_SCRATCH_8
+#define MREG_BUFFEROUT AV_SCRATCH_9
+#define MREG_CMD AV_SCRATCH_A
+#define MREG_CO_MV_START AV_SCRATCH_B
+#define MREG_ERROR_COUNT AV_SCRATCH_C
+#define MREG_FRAME_OFFSET AV_SCRATCH_D
+#define MREG_WAIT_BUFFER AV_SCRATCH_E
+#define MREG_FATAL_ERROR AV_SCRATCH_F
+
+#define PICINFO_PROG 0x00008000
+#define PICINFO_TOP_FIRST 0x00002000
+
+struct codec_mpeg12 {
+ /* Buffer for the MPEG1/2 Workspace */
+ void *workspace_vaddr;
+ dma_addr_t workspace_paddr;
+};
+
+static int codec_mpeg12_can_recycle(struct amvdec_core *core)
+{
+ return !amvdec_read_dos(core, MREG_BUFFERIN);
+}
+
+static void codec_mpeg12_recycle(struct amvdec_core *core, u32 buf_idx)
+{
+ amvdec_write_dos(core, MREG_BUFFERIN, buf_idx + 1);
+}
+
+static int codec_mpeg12_start(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+ struct codec_mpeg12 *mpeg12 = sess->priv;
+ int ret;
+
+ mpeg12 = kzalloc(sizeof(*mpeg12), GFP_KERNEL);
+ if (!mpeg12)
+ return -ENOMEM;
+
+ /* Allocate some memory for the MPEG1/2 decoder's state */
+ mpeg12->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE,
+ &mpeg12->workspace_paddr,
+ GFP_KERNEL);
+ if (!mpeg12->workspace_vaddr) {
+ dev_err(core->dev, "Failed to request MPEG 1/2 Workspace\n");
+ ret = -ENOMEM;
+ goto free_mpeg12;
+ }
+
+ ret = amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_0, 0 },
+ (u32[]){ 8, 0 });
+ if (ret)
+ goto free_workspace;
+
+ amvdec_write_dos(core, POWER_CTL_VLD, BIT(4));
+ amvdec_write_dos(core, MREG_CO_MV_START,
+ mpeg12->workspace_paddr + WORKSPACE_OFFSET);
+
+ amvdec_write_dos(core, MPEG1_2_REG, 0);
+ amvdec_write_dos(core, PSCALE_CTRL, 0);
+ amvdec_write_dos(core, PIC_HEAD_INFO, 0x380);
+ amvdec_write_dos(core, M4_CONTROL_REG, 0);
+ amvdec_write_dos(core, MREG_BUFFERIN, 0);
+ amvdec_write_dos(core, MREG_BUFFEROUT, 0);
+ amvdec_write_dos(core, MREG_CMD, (sess->width << 16) | sess->height);
+ amvdec_write_dos(core, MREG_ERROR_COUNT, 0);
+ amvdec_write_dos(core, MREG_FATAL_ERROR, 0);
+ amvdec_write_dos(core, MREG_WAIT_BUFFER, 0);
+
+ sess->keyframe_found = 1;
+ sess->priv = mpeg12;
+
+ return 0;
+
+free_workspace:
+ dma_free_coherent(core->dev, SIZE_WORKSPACE, mpeg12->workspace_vaddr,
+ mpeg12->workspace_paddr);
+free_mpeg12:
+ kfree(mpeg12);
+
+ return ret;
+}
+
+static int codec_mpeg12_stop(struct amvdec_session *sess)
+{
+ struct codec_mpeg12 *mpeg12 = sess->priv;
+ struct amvdec_core *core = sess->core;
+
+ if (mpeg12->workspace_vaddr)
+ dma_free_coherent(core->dev, SIZE_WORKSPACE,
+ mpeg12->workspace_vaddr,
+ mpeg12->workspace_paddr);
+
+ return 0;
+}
+
+static irqreturn_t codec_mpeg12_threaded_isr(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+ u32 reg;
+ u32 pic_info;
+ u32 is_progressive;
+ u32 buffer_index;
+ u32 field = V4L2_FIELD_NONE;
+
+ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1);
+ reg = amvdec_read_dos(core, MREG_FATAL_ERROR);
+ if (reg == 1) {
+ dev_err(core->dev, "MPEG1/2 fatal error\n");
+ amvdec_abort(sess);
+ return IRQ_HANDLED;
+ }
+
+ reg = amvdec_read_dos(core, MREG_BUFFEROUT);
+ if (!reg)
+ return IRQ_HANDLED;
+
+ /* Unclear what this means */
+ if ((reg & GENMASK(23, 17)) == GENMASK(23, 17))
+ goto end;
+
+ pic_info = amvdec_read_dos(core, MREG_PIC_INFO);
+ is_progressive = pic_info & PICINFO_PROG;
+
+ if (!is_progressive)
+ field = (pic_info & PICINFO_TOP_FIRST) ?
+ V4L2_FIELD_INTERLACED_TB :
+ V4L2_FIELD_INTERLACED_BT;
+
+ buffer_index = ((reg & 0xf) - 1) & 7;
+ amvdec_dst_buf_done_idx(sess, buffer_index, field);
+
+end:
+ amvdec_write_dos(core, MREG_BUFFEROUT, 0);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t codec_mpeg12_isr(struct amvdec_session *sess)
+{
+ return IRQ_WAKE_THREAD;
+}
+
+struct amvdec_codec_ops codec_mpeg12_ops = {
+ .start = codec_mpeg12_start,
+ .stop = codec_mpeg12_stop,
+ .isr = codec_mpeg12_isr,
+ .threaded_isr = codec_mpeg12_threaded_isr,
+ .can_recycle = codec_mpeg12_can_recycle,
+ .recycle = codec_mpeg12_recycle,
+};
diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.h b/drivers/media/platform/meson/vdec/codec_mpeg12.h
new file mode 100644
index 000000000000..43cab5f39ca0
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/codec_mpeg12.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_CODEC_MPEG12_H_
+#define __MESON_VDEC_CODEC_MPEG12_H_
+
+#include "vdec.h"
+
+extern struct amvdec_codec_ops codec_mpeg12_ops;
+
+#endif
diff --git a/drivers/media/platform/meson/vdec/dos_regs.h b/drivers/media/platform/meson/vdec/dos_regs.h
new file mode 100644
index 000000000000..abd810542dbb
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/dos_regs.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_DOS_REGS_H_
+#define __MESON_VDEC_DOS_REGS_H_
+
+/* DOS registers */
+#define VDEC_ASSIST_AMR1_INT8 0x00b4
+
+#define ASSIST_MBOX1_CLR_REG 0x01d4
+#define ASSIST_MBOX1_MASK 0x01d8
+
+#define MPSR 0x0c04
+#define MCPU_INTR_MSK 0x0c10
+#define CPSR 0x0c84
+
+#define IMEM_DMA_CTRL 0x0d00
+#define IMEM_DMA_ADR 0x0d04
+#define IMEM_DMA_COUNT 0x0d08
+#define LMEM_DMA_CTRL 0x0d40
+
+#define MC_STATUS0 0x2424
+#define MC_CTRL1 0x242c
+
+#define PSCALE_RST 0x2440
+#define PSCALE_CTRL 0x2444
+#define PSCALE_BMEM_ADDR 0x247c
+#define PSCALE_BMEM_DAT 0x2480
+
+#define DBLK_CTRL 0x2544
+#define DBLK_STATUS 0x254c
+
+#define GCLK_EN 0x260c
+#define MDEC_PIC_DC_CTRL 0x2638
+#define MDEC_PIC_DC_STATUS 0x263c
+#define ANC0_CANVAS_ADDR 0x2640
+#define MDEC_PIC_DC_THRESH 0x26e0
+
+/* Firmware interface registers */
+#define AV_SCRATCH_0 0x2700
+#define AV_SCRATCH_1 0x2704
+#define AV_SCRATCH_2 0x2708
+#define AV_SCRATCH_3 0x270c
+#define AV_SCRATCH_4 0x2710
+#define AV_SCRATCH_5 0x2714
+#define AV_SCRATCH_6 0x2718
+#define AV_SCRATCH_7 0x271c
+#define AV_SCRATCH_8 0x2720
+#define AV_SCRATCH_9 0x2724
+#define AV_SCRATCH_A 0x2728
+#define AV_SCRATCH_B 0x272c
+#define AV_SCRATCH_C 0x2730
+#define AV_SCRATCH_D 0x2734
+#define AV_SCRATCH_E 0x2738
+#define AV_SCRATCH_F 0x273c
+#define AV_SCRATCH_G 0x2740
+#define AV_SCRATCH_H 0x2744
+#define AV_SCRATCH_I 0x2748
+#define AV_SCRATCH_J 0x274c
+#define AV_SCRATCH_K 0x2750
+#define AV_SCRATCH_L 0x2754
+
+#define MPEG1_2_REG 0x3004
+#define PIC_HEAD_INFO 0x300c
+#define POWER_CTL_VLD 0x3020
+#define M4_CONTROL_REG 0x30a4
+
+/* Stream Buffer (stbuf) regs */
+#define VLD_MEM_VIFIFO_START_PTR 0x3100
+#define VLD_MEM_VIFIFO_CURR_PTR 0x3104
+#define VLD_MEM_VIFIFO_END_PTR 0x3108
+#define VLD_MEM_VIFIFO_CONTROL 0x3110
+ #define MEM_FIFO_CNT_BIT 16
+ #define MEM_FILL_ON_LEVEL BIT(10)
+ #define MEM_CTRL_EMPTY_EN BIT(2)
+ #define MEM_CTRL_FILL_EN BIT(1)
+#define VLD_MEM_VIFIFO_WP 0x3114
+#define VLD_MEM_VIFIFO_RP 0x3118
+#define VLD_MEM_VIFIFO_LEVEL 0x311c
+#define VLD_MEM_VIFIFO_BUF_CNTL 0x3120
+ #define MEM_BUFCTRL_MANUAL BIT(1)
+#define VLD_MEM_VIFIFO_WRAP_COUNT 0x3144
+
+#define DCAC_DMA_CTRL 0x3848
+
+#define DOS_SW_RESET0 0xfc00
+#define DOS_GCLK_EN0 0xfc04
+#define DOS_GEN_CTRL0 0xfc08
+#define DOS_MEM_PD_VDEC 0xfcc0
+#define DOS_MEM_PD_HEVC 0xfccc
+#define DOS_SW_RESET3 0xfcd0
+#define DOS_GCLK_EN3 0xfcd4
+#define DOS_VDEC_MCRCC_STALL_CTRL 0xfd00
+
+#endif
diff --git a/drivers/media/platform/meson/vdec/esparser.c b/drivers/media/platform/meson/vdec/esparser.c
new file mode 100644
index 000000000000..098c7d76ad3f
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/esparser.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ *
+ * The Elementary Stream Parser is a HW bitstream parser.
+ * It reads bitstream buffers and feeds them to the VIFIFO
+ */
+
+#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/reset.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "dos_regs.h"
+#include "esparser.h"
+#include "vdec_helpers.h"
+
+/* PARSER REGS (CBUS) */
+#define PARSER_CONTROL 0x00
+ #define ES_PACK_SIZE_BIT 8
+ #define ES_WRITE BIT(5)
+ #define ES_SEARCH BIT(1)
+ #define ES_PARSER_START BIT(0)
+#define PARSER_FETCH_ADDR 0x4
+#define PARSER_FETCH_CMD 0x8
+#define PARSER_CONFIG 0x14
+ #define PS_CFG_MAX_FETCH_CYCLE_BIT 0
+ #define PS_CFG_STARTCODE_WID_24_BIT 10
+ #define PS_CFG_MAX_ES_WR_CYCLE_BIT 12
+ #define PS_CFG_PFIFO_EMPTY_CNT_BIT 16
+#define PFIFO_WR_PTR 0x18
+#define PFIFO_RD_PTR 0x1c
+#define PARSER_SEARCH_PATTERN 0x24
+ #define ES_START_CODE_PATTERN 0x00000100
+#define PARSER_SEARCH_MASK 0x28
+ #define ES_START_CODE_MASK 0xffffff00
+ #define FETCH_ENDIAN_BIT 27
+#define PARSER_INT_ENABLE 0x2c
+ #define PARSER_INT_HOST_EN_BIT 8
+#define PARSER_INT_STATUS 0x30
+ #define PARSER_INTSTAT_SC_FOUND 1
+#define PARSER_ES_CONTROL 0x5c
+#define PARSER_VIDEO_START_PTR 0x80
+#define PARSER_VIDEO_END_PTR 0x84
+#define PARSER_VIDEO_HOLE 0x90
+
+#define SEARCH_PATTERN_LEN 512
+#define MIN_PACKET_SIZE (4 * SZ_1K)
+
+/* Buffer to send to the ESPARSER to signal End Of Stream.
+ * Credits to Endless Mobile.
+ */
+#define EOS_TAIL_BUF_SIZE 1024
+static const u8 eos_tail_data[EOS_TAIL_BUF_SIZE] = {
+ 0x00, 0x00, 0x00, 0x01, 0x06, 0x05, 0xff, 0xe4, 0xdc, 0x45, 0xe9, 0xbd,
+ 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef,
+ 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20,
+ 0x36, 0x37, 0x20, 0x72, 0x31, 0x31, 0x33, 0x30, 0x20, 0x38, 0x34, 0x37,
+ 0x35, 0x39, 0x37, 0x37, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34,
+ 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20,
+ 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79,
+ 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30,
+ 0x30, 0x39, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e,
+ 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74,
+ 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+ 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65,
+ 0x66, 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d,
+ 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73,
+ 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20,
+ 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65,
+ 0x3d, 0x36, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e,
+ 0x30, 0x3a, 0x30, 0x2e, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f,
+ 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e,
+ 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61,
+ 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69,
+ 0x73, 0x3d, 0x30, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30,
+ 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a,
+ 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x63, 0x68,
+ 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73,
+ 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
+ 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63,
+ 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x6d, 0x62, 0x61, 0x66,
+ 0x66, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d,
+ 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30,
+ 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d,
+ 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d,
+ 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69,
+ 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x61, 0x74,
+ 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f,
+ 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69,
+ 0x6e, 0x3d, 0x31, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x35,
+ 0x31, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69,
+ 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30,
+ 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80,
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0a, 0x9a, 0x74, 0xf4, 0x20,
+ 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x51, 0xe2, 0x44, 0xd4,
+ 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x32, 0xc8, 0x00, 0x00, 0x00, 0x01,
+ 0x65, 0x88, 0x80, 0x20, 0x00, 0x08, 0x7f, 0xea, 0x6a, 0xe2, 0x99, 0xb6,
+ 0x57, 0xae, 0x49, 0x30, 0xf5, 0xfe, 0x5e, 0x46, 0x0b, 0x72, 0x44, 0xc4,
+ 0xe1, 0xfc, 0x62, 0xda, 0xf1, 0xfb, 0xa2, 0xdb, 0xd6, 0xbe, 0x5c, 0xd7,
+ 0x24, 0xa3, 0xf5, 0xb9, 0x2f, 0x57, 0x16, 0x49, 0x75, 0x47, 0x77, 0x09,
+ 0x5c, 0xa1, 0xb4, 0xc3, 0x4f, 0x60, 0x2b, 0xb0, 0x0c, 0xc8, 0xd6, 0x66,
+ 0xba, 0x9b, 0x82, 0x29, 0x33, 0x92, 0x26, 0x99, 0x31, 0x1c, 0x7f, 0x9b
+};
+
+static DECLARE_WAIT_QUEUE_HEAD(wq);
+static int search_done;
+
+static irqreturn_t esparser_isr(int irq, void *dev)
+{
+ int int_status;
+ struct amvdec_core *core = dev;
+
+ int_status = amvdec_read_parser(core, PARSER_INT_STATUS);
+ amvdec_write_parser(core, PARSER_INT_STATUS, int_status);
+
+ if (int_status & PARSER_INTSTAT_SC_FOUND) {
+ amvdec_write_parser(core, PFIFO_RD_PTR, 0);
+ amvdec_write_parser(core, PFIFO_WR_PTR, 0);
+ search_done = 1;
+ wake_up_interruptible(&wq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* Pad the packet to at least 4KiB bytes otherwise the VDEC unit won't trigger
+ * ISRs.
+ * Also append a start code 000001ff at the end to trigger
+ * the ESPARSER interrupt.
+ */
+static u32 esparser_pad_start_code(struct vb2_buffer *vb)
+{
+ u32 payload_size = vb2_get_plane_payload(vb, 0);
+ u32 pad_size = 0;
+ u8 *vaddr = vb2_plane_vaddr(vb, 0) + payload_size;
+
+ if (payload_size < MIN_PACKET_SIZE) {
+ pad_size = MIN_PACKET_SIZE - payload_size;
+ memset(vaddr, 0, pad_size);
+ }
+
+ memset(vaddr + pad_size, 0, SEARCH_PATTERN_LEN);
+ vaddr[pad_size] = 0x00;
+ vaddr[pad_size + 1] = 0x00;
+ vaddr[pad_size + 2] = 0x01;
+ vaddr[pad_size + 3] = 0xff;
+
+ return pad_size;
+}
+
+static int
+esparser_write_data(struct amvdec_core *core, dma_addr_t addr, u32 size)
+{
+ amvdec_write_parser(core, PFIFO_RD_PTR, 0);
+ amvdec_write_parser(core, PFIFO_WR_PTR, 0);
+ amvdec_write_parser(core, PARSER_CONTROL,
+ ES_WRITE |
+ ES_PARSER_START |
+ ES_SEARCH |
+ (size << ES_PACK_SIZE_BIT));
+
+ amvdec_write_parser(core, PARSER_FETCH_ADDR, addr);
+ amvdec_write_parser(core, PARSER_FETCH_CMD,
+ (7 << FETCH_ENDIAN_BIT) |
+ (size + SEARCH_PATTERN_LEN));
+
+ search_done = 0;
+ return wait_event_interruptible_timeout(wq, search_done, (HZ / 5));
+}
+
+static u32 esparser_vififo_get_free_space(struct amvdec_session *sess)
+{
+ u32 vififo_usage;
+ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
+ struct amvdec_core *core = sess->core;
+
+ vififo_usage = vdec_ops->vififo_level(sess);
+ vififo_usage += amvdec_read_parser(core, PARSER_VIDEO_HOLE);
+ vififo_usage += (6 * SZ_1K);
+
+ if (vififo_usage > sess->vififo_size) {
+ dev_warn(sess->core->dev,
+ "VIFIFO usage (%u) > VIFIFO size (%u)\n",
+ vififo_usage, sess->vififo_size);
+ return 0;
+ }
+
+ return sess->vififo_size - vififo_usage;
+}
+
+int esparser_queue_eos(struct amvdec_core *core)
+{
+ struct device *dev = core->dev;
+ void *eos_vaddr;
+ dma_addr_t eos_paddr;
+ int ret;
+
+ eos_vaddr = dma_alloc_coherent(dev,
+ EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN,
+ &eos_paddr, GFP_KERNEL);
+ if (!eos_vaddr)
+ return -ENOMEM;
+
+ memset(eos_vaddr, 0, EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN);
+ memcpy(eos_vaddr, eos_tail_data, sizeof(eos_tail_data));
+ ret = esparser_write_data(core, eos_paddr, EOS_TAIL_BUF_SIZE);
+ dma_free_coherent(dev, EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN,
+ eos_vaddr, eos_paddr);
+
+ return ret;
+}
+
+static int
+esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)
+{
+ int ret;
+ struct vb2_buffer *vb = &vbuf->vb2_buf;
+ struct amvdec_core *core = sess->core;
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+ u32 num_dst_bufs = 0;
+ u32 payload_size = vb2_get_plane_payload(vb, 0);
+ dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0);
+ u32 pad_size;
+
+ if (!payload_size) {
+ esparser_queue_eos(core);
+ return 0;
+ }
+
+ if (codec_ops->num_pending_bufs)
+ num_dst_bufs = codec_ops->num_pending_bufs(sess);
+
+ num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx);
+
+ if (esparser_vififo_get_free_space(sess) < payload_size ||
+ atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs)
+ return -EAGAIN;
+
+ v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf);
+ amvdec_add_ts_reorder(sess, vb->timestamp);
+ dev_dbg(core->dev, "esparser: Queuing ts = %llu ; pld_size = %u\n",
+ vb->timestamp, payload_size);
+
+ pad_size = esparser_pad_start_code(vb);
+ ret = esparser_write_data(core, phy, payload_size + pad_size);
+
+ if (ret > 0) {
+ /* We need to wait until we parse/decode the first keyframe.
+ * All buffers prior to the first keyframe must be dropped.
+ */
+ if (!sess->keyframe_found)
+ usleep_range(1000, 2000);
+
+ if (sess->keyframe_found)
+ atomic_inc(&sess->esparser_queued_bufs);
+ else
+ amvdec_remove_ts(sess, vb->timestamp);
+
+ vbuf->flags = 0;
+ vbuf->field = V4L2_FIELD_NONE;
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
+ return 0;
+ }
+
+ dev_warn(core->dev, "esparser: input parsing error\n");
+ amvdec_remove_ts(sess, vb->timestamp);
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+ amvdec_write_parser(core, PARSER_FETCH_CMD, 0);
+
+ return 0;
+}
+
+void esparser_queue_all_src(struct work_struct *work)
+{
+ struct v4l2_m2m_buffer *buf, *n;
+ struct amvdec_session *sess =
+ container_of(work, struct amvdec_session, esparser_queue_work);
+
+ mutex_lock(&sess->lock);
+ v4l2_m2m_for_each_src_buf_safe(sess->m2m_ctx, buf, n) {
+ if (esparser_queue(sess, &buf->vb) < 0)
+ break;
+
+ /* Some codecs don't like having data queued in too fast */
+ usleep_range(1000, 2000);
+ }
+ mutex_unlock(&sess->lock);
+}
+
+int esparser_power_up(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
+
+ reset_control_reset(core->esparser_reset);
+ amvdec_write_parser(core, PARSER_CONFIG,
+ (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) |
+ (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) |
+ (16 << PS_CFG_MAX_FETCH_CYCLE_BIT));
+
+ amvdec_write_parser(core, PFIFO_RD_PTR, 0);
+ amvdec_write_parser(core, PFIFO_WR_PTR, 0);
+
+ amvdec_write_parser(core, PARSER_SEARCH_PATTERN,
+ ES_START_CODE_PATTERN);
+ amvdec_write_parser(core, PARSER_SEARCH_MASK, ES_START_CODE_MASK);
+
+ amvdec_write_parser(core, PARSER_CONFIG,
+ (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) |
+ (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) |
+ (16 << PS_CFG_MAX_FETCH_CYCLE_BIT) |
+ (2 << PS_CFG_STARTCODE_WID_24_BIT));
+
+ amvdec_write_parser(core, PARSER_CONTROL,
+ (ES_SEARCH | ES_PARSER_START));
+
+ amvdec_write_parser(core, PARSER_VIDEO_START_PTR, sess->vififo_paddr);
+ amvdec_write_parser(core, PARSER_VIDEO_END_PTR,
+ sess->vififo_paddr + sess->vififo_size - 8);
+ amvdec_write_parser(core, PARSER_ES_CONTROL,
+ amvdec_read_parser(core, PARSER_ES_CONTROL) & ~1);
+
+ if (vdec_ops->conf_esparser)
+ vdec_ops->conf_esparser(sess);
+
+ amvdec_write_parser(core, PARSER_INT_STATUS, 0xffff);
+ amvdec_write_parser(core, PARSER_INT_ENABLE,
+ BIT(PARSER_INT_HOST_EN_BIT));
+
+ return 0;
+}
+
+int esparser_init(struct platform_device *pdev, struct amvdec_core *core)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ int irq;
+
+ irq = platform_get_irq(pdev, 1);
+ if (irq < 0) {
+ dev_err(dev, "Failed getting ESPARSER IRQ from dtb\n");
+ return irq;
+ }
+
+ ret = devm_request_irq(dev, irq, esparser_isr, IRQF_SHARED,
+ "esparserirq", core);
+ if (ret) {
+ dev_err(dev, "Failed requesting ESPARSER IRQ\n");
+ return ret;
+ }
+
+ core->esparser_reset =
+ devm_reset_control_get_exclusive(dev, "esparser");
+ if (IS_ERR(core->esparser_reset)) {
+ dev_err(dev, "Failed to get esparser_reset\n");
+ return PTR_ERR(core->esparser_reset);
+ }
+
+ return 0;
+}
diff --git a/drivers/media/platform/meson/vdec/esparser.h b/drivers/media/platform/meson/vdec/esparser.h
new file mode 100644
index 000000000000..22c2ac5c6d35
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/esparser.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_ESPARSER_H_
+#define __MESON_VDEC_ESPARSER_H_
+
+#include "vdec.h"
+
+int esparser_init(struct platform_device *pdev, struct amvdec_core *core);
+int esparser_power_up(struct amvdec_session *sess);
+
+/**
+ * esparser_queue_eos() - write End Of Stream sequence to the ESPARSER
+ *
+ * @core vdec core struct
+ */
+int esparser_queue_eos(struct amvdec_core *core);
+
+/**
+ * esparser_queue_all_src() - work handler that writes as many src buffers
+ * as possible to the ESPARSER
+ */
+void esparser_queue_all_src(struct work_struct *work);
+
+#endif
diff --git a/drivers/media/platform/meson/vdec/vdec.c b/drivers/media/platform/meson/vdec/vdec.c
new file mode 100644
index 000000000000..32e1e2228297
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec.c
@@ -0,0 +1,988 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "vdec.h"
+#include "esparser.h"
+#include "vdec_helpers.h"
+
+struct dummy_buf {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+/* 16 MiB for parsed bitstream swap exchange */
+#define SIZE_VIFIFO SZ_16M
+
+static u32 get_output_size(u32 width, u32 height)
+{
+ return ALIGN(width * height, SZ_64K);
+}
+
+u32 amvdec_get_output_size(struct amvdec_session *sess)
+{
+ return get_output_size(sess->width, sess->height);
+}
+EXPORT_SYMBOL_GPL(amvdec_get_output_size);
+
+static int vdec_codec_needs_recycle(struct amvdec_session *sess)
+{
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+
+ return codec_ops->can_recycle && codec_ops->recycle;
+}
+
+static int vdec_recycle_thread(void *data)
+{
+ struct amvdec_session *sess = data;
+ struct amvdec_core *core = sess->core;
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+ struct amvdec_buffer *tmp, *n;
+
+ while (!kthread_should_stop()) {
+ mutex_lock(&sess->bufs_recycle_lock);
+ list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
+ if (!codec_ops->can_recycle(core))
+ break;
+
+ codec_ops->recycle(core, tmp->vb->index);
+ dev_dbg(core->dev, "Buffer %d recycled\n",
+ tmp->vb->index);
+ list_del(&tmp->list);
+ kfree(tmp);
+ }
+ mutex_unlock(&sess->bufs_recycle_lock);
+
+ usleep_range(5000, 10000);
+ }
+
+ return 0;
+}
+
+static int vdec_poweron(struct amvdec_session *sess)
+{
+ int ret;
+ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
+
+ ret = clk_prepare_enable(sess->core->dos_parser_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(sess->core->dos_clk);
+ if (ret)
+ goto disable_dos_parser;
+
+ ret = vdec_ops->start(sess);
+ if (ret)
+ goto disable_dos;
+
+ esparser_power_up(sess);
+
+ return 0;
+
+disable_dos:
+ clk_disable_unprepare(sess->core->dos_clk);
+disable_dos_parser:
+ clk_disable_unprepare(sess->core->dos_parser_clk);
+
+ return ret;
+}
+
+static void vdec_wait_inactive(struct amvdec_session *sess)
+{
+ /* We consider 50ms with no IRQ to be inactive. */
+ while (time_is_after_jiffies64(sess->last_irq_jiffies +
+ msecs_to_jiffies(50)))
+ msleep(25);
+}
+
+static void vdec_poweroff(struct amvdec_session *sess)
+{
+ struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+
+ vdec_wait_inactive(sess);
+ if (codec_ops->drain)
+ codec_ops->drain(sess);
+
+ vdec_ops->stop(sess);
+ clk_disable_unprepare(sess->core->dos_clk);
+ clk_disable_unprepare(sess->core->dos_parser_clk);
+}
+
+static void
+vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb)
+{
+ struct amvdec_buffer *new_buf;
+
+ new_buf = kmalloc(sizeof(*new_buf), GFP_KERNEL);
+ new_buf->vb = vb;
+
+ mutex_lock(&sess->bufs_recycle_lock);
+ list_add_tail(&new_buf->list, &sess->bufs_recycle);
+ mutex_unlock(&sess->bufs_recycle_lock);
+}
+
+static void vdec_m2m_device_run(void *priv)
+{
+ struct amvdec_session *sess = priv;
+
+ schedule_work(&sess->esparser_queue_work);
+}
+
+static void vdec_m2m_job_abort(void *priv)
+{
+ struct amvdec_session *sess = priv;
+
+ v4l2_m2m_job_finish(sess->m2m_dev, sess->m2m_ctx);
+}
+
+static const struct v4l2_m2m_ops vdec_m2m_ops = {
+ .device_run = vdec_m2m_device_run,
+ .job_abort = vdec_m2m_job_abort,
+};
+
+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 amvdec_session *sess = vb2_get_drv_priv(q);
+ struct amvdec_core *core = sess->core;
+ const struct amvdec_format *fmt_out = sess->fmt_out;
+ u32 pixfmt_cap = sess->pixfmt_cap;
+
+ switch (q->type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ sizes[0] = amvdec_get_output_size(sess);
+ *num_planes = 1;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ if (pixfmt_cap == V4L2_PIX_FMT_NV12M) {
+ sizes[0] = amvdec_get_output_size(sess);
+ sizes[1] = amvdec_get_output_size(sess) / 2;
+ *num_planes = 2;
+ } else if (pixfmt_cap == V4L2_PIX_FMT_YUV420M) {
+ sizes[0] = amvdec_get_output_size(sess);
+ sizes[1] = amvdec_get_output_size(sess) / 4;
+ sizes[2] = amvdec_get_output_size(sess) / 4;
+ *num_planes = 3;
+ }
+ *num_buffers = min(max(*num_buffers, fmt_out->min_buffers),
+ fmt_out->max_buffers);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&core->lock);
+ if (core->cur_sess && core->cur_sess != sess) {
+ mutex_unlock(&core->lock);
+ return -EBUSY;
+ }
+
+ core->cur_sess = sess;
+ mutex_unlock(&core->lock);
+
+ return 0;
+}
+
+static void vdec_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct amvdec_session *sess = vb2_get_drv_priv(vb->vb2_queue);
+ struct v4l2_m2m_ctx *m2m_ctx = sess->m2m_ctx;
+
+ mutex_lock(&sess->lock);
+ v4l2_m2m_buf_queue(m2m_ctx, vbuf);
+
+ if (!sess->streamon_out || !sess->streamon_cap)
+ goto unlock;
+
+ if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ vdec_codec_needs_recycle(sess))
+ vdec_queue_recycle(sess, vb);
+
+ schedule_work(&sess->esparser_queue_work);
+unlock:
+ mutex_unlock(&sess->lock);
+}
+
+static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct amvdec_session *sess = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *buf;
+ int ret;
+
+ mutex_lock(&sess->lock);
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ sess->streamon_out = 1;
+ else
+ sess->streamon_cap = 1;
+
+ if (!sess->streamon_out || !sess->streamon_cap) {
+ mutex_unlock(&sess->lock);
+ return 0;
+ }
+
+ sess->vififo_size = SIZE_VIFIFO;
+ sess->vififo_vaddr =
+ dma_alloc_coherent(sess->core->dev, sess->vififo_size,
+ &sess->vififo_paddr, GFP_KERNEL);
+ if (!sess->vififo_vaddr) {
+ dev_err(sess->core->dev, "Failed to request VIFIFO buffer\n");
+ ret = -ENOMEM;
+ goto bufs_done;
+ }
+
+ sess->should_stop = 0;
+ sess->keyframe_found = 0;
+ atomic_set(&sess->esparser_queued_bufs, 0);
+ ret = vdec_poweron(sess);
+ if (ret)
+ goto vififo_free;
+
+ sess->sequence_cap = 0;
+ if (vdec_codec_needs_recycle(sess))
+ sess->recycle_thread = kthread_run(vdec_recycle_thread, sess,
+ "vdec_recycle");
+ mutex_unlock(&sess->lock);
+
+ return 0;
+
+vififo_free:
+ dma_free_coherent(sess->core->dev, sess->vififo_size,
+ sess->vififo_vaddr, sess->vififo_paddr);
+bufs_done:
+ while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
+ while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ sess->streamon_out = 0;
+ else
+ sess->streamon_cap = 0;
+ mutex_unlock(&sess->lock);
+ return ret;
+}
+
+static void vdec_free_canvas(struct amvdec_session *sess)
+{
+ int i;
+
+ for (i = 0; i < sess->canvas_num; ++i)
+ meson_canvas_free(sess->core->canvas, sess->canvas_alloc[i]);
+
+ sess->canvas_num = 0;
+}
+
+static void vdec_reset_timestamps(struct amvdec_session *sess)
+{
+ struct amvdec_timestamp *tmp, *n;
+
+ list_for_each_entry_safe(tmp, n, &sess->timestamps, list) {
+ list_del(&tmp->list);
+ kfree(tmp);
+ }
+}
+
+static void vdec_reset_bufs_recycle(struct amvdec_session *sess)
+{
+ struct amvdec_buffer *tmp, *n;
+
+ list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
+ list_del(&tmp->list);
+ kfree(tmp);
+ }
+}
+
+static void vdec_stop_streaming(struct vb2_queue *q)
+{
+ struct amvdec_session *sess = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *buf;
+
+ mutex_lock(&sess->lock);
+
+ if (sess->streamon_out && sess->streamon_cap) {
+ if (vdec_codec_needs_recycle(sess))
+ kthread_stop(sess->recycle_thread);
+
+ vdec_poweroff(sess);
+ vdec_free_canvas(sess);
+ dma_free_coherent(sess->core->dev, sess->vififo_size,
+ sess->vififo_vaddr, sess->vififo_paddr);
+ vdec_reset_timestamps(sess);
+ vdec_reset_bufs_recycle(sess);
+ kfree(sess->priv);
+ sess->priv = NULL;
+ }
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
+
+ sess->streamon_out = 0;
+ } else {
+ while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
+ v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
+
+ sess->streamon_cap = 0;
+ }
+
+ mutex_unlock(&sess->lock);
+}
+
+static const struct vb2_ops vdec_vb2_ops = {
+ .queue_setup = vdec_queue_setup,
+ .start_streaming = vdec_start_streaming,
+ .stop_streaming = vdec_stop_streaming,
+ .buf_queue = vdec_vb2_buf_queue,
+};
+
+static int
+vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+ strlcpy(cap->driver, "meson-vdec", sizeof(cap->driver));
+ strlcpy(cap->card, "Amlogic Video Decoder", sizeof(cap->card));
+ strlcpy(cap->bus_info, "platform:meson-vdec", sizeof(cap->bus_info));
+
+ return 0;
+}
+
+static const struct amvdec_format *
+find_format(const struct amvdec_format *fmts, u32 size, u32 pixfmt)
+{
+ unsigned int i;
+
+ for (i = 0; i < size; i++) {
+ if (fmts[i].pixfmt == pixfmt)
+ return &fmts[i];
+ }
+
+ return NULL;
+}
+
+static unsigned int
+vdec_supports_pixfmt_cap(const struct amvdec_format *fmt_out, u32 pixfmt_cap)
+{
+ int i;
+
+ for (i = 0; fmt_out->pixfmts_cap[i]; i++)
+ if (fmt_out->pixfmts_cap[i] == pixfmt_cap)
+ return 1;
+
+ return 0;
+}
+
+static const struct amvdec_format *
+vdec_try_fmt_common(struct amvdec_session *sess, u32 size,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt;
+ const struct amvdec_format *fmts = sess->core->platform->formats;
+ const struct amvdec_format *fmt_out;
+
+ memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved));
+ memset(pixmp->reserved, 0, sizeof(pixmp->reserved));
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ fmt_out = find_format(fmts, size, pixmp->pixelformat);
+ if (!fmt_out) {
+ pixmp->pixelformat = V4L2_PIX_FMT_MPEG2;
+ fmt_out = find_format(fmts, size, pixmp->pixelformat);
+ pixmp->width = 1280;
+ pixmp->height = 720;
+ }
+
+ pfmt[0].sizeimage =
+ get_output_size(pixmp->width, pixmp->height);
+ pfmt[0].bytesperline = 0;
+ pixmp->num_planes = 1;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ fmt_out = sess->fmt_out;
+ if (!vdec_supports_pixfmt_cap(fmt_out, pixmp->pixelformat))
+ pixmp->pixelformat = fmt_out->pixfmts_cap[0];
+
+ memset(pfmt[1].reserved, 0, sizeof(pfmt[1].reserved));
+ if (pixmp->pixelformat == V4L2_PIX_FMT_NV12M) {
+ pfmt[0].sizeimage =
+ get_output_size(pixmp->width, pixmp->height);
+ pfmt[0].bytesperline = ALIGN(pixmp->width, 64);
+
+ pfmt[1].sizeimage =
+ get_output_size(pixmp->width, pixmp->height) / 2;
+ pfmt[1].bytesperline = ALIGN(pixmp->width, 64);
+ pixmp->num_planes = 2;
+ } else if (pixmp->pixelformat == V4L2_PIX_FMT_YUV420M) {
+ pfmt[0].sizeimage =
+ get_output_size(pixmp->width, pixmp->height);
+ pfmt[0].bytesperline = ALIGN(pixmp->width, 64);
+
+ pfmt[1].sizeimage =
+ get_output_size(pixmp->width, pixmp->height) / 4;
+ pfmt[1].bytesperline = ALIGN(pixmp->width, 64) / 2;
+
+ pfmt[2].sizeimage =
+ get_output_size(pixmp->width, pixmp->height) / 4;
+ pfmt[2].bytesperline = ALIGN(pixmp->width, 64) / 2;
+ pixmp->num_planes = 3;
+ }
+ } else {
+ return NULL;
+ }
+
+ pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width);
+ pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height);
+
+ if (pixmp->field == V4L2_FIELD_ANY)
+ pixmp->field = V4L2_FIELD_NONE;
+
+ pixmp->flags = 0;
+
+ return fmt_out;
+}
+
+static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+
+ vdec_try_fmt_common(sess, sess->core->platform->num_formats, f);
+
+ return 0;
+}
+
+static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ pixmp->pixelformat = sess->pixfmt_cap;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ pixmp->pixelformat = sess->fmt_out->pixfmt;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixmp->width = sess->width;
+ pixmp->height = sess->height;
+ pixmp->colorspace = sess->colorspace;
+ pixmp->ycbcr_enc = sess->ycbcr_enc;
+ pixmp->quantization = sess->quantization;
+ pixmp->xfer_func = sess->xfer_func;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixmp->width = sess->width;
+ pixmp->height = sess->height;
+ }
+
+ vdec_try_fmt_common(sess, sess->core->platform->num_formats, f);
+
+ return 0;
+}
+
+static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+ struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
+ u32 num_formats = sess->core->platform->num_formats;
+ const struct amvdec_format *fmt_out;
+ struct v4l2_pix_format_mplane orig_pixmp;
+ struct v4l2_format format;
+ u32 pixfmt_out = 0, pixfmt_cap = 0;
+
+ orig_pixmp = *pixmp;
+
+ fmt_out = vdec_try_fmt_common(sess, num_formats, f);
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ pixfmt_out = pixmp->pixelformat;
+ pixfmt_cap = sess->pixfmt_cap;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ pixfmt_cap = pixmp->pixelformat;
+ pixfmt_out = sess->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(sess, num_formats, &format);
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ sess->width = format.fmt.pix_mp.width;
+ sess->height = format.fmt.pix_mp.height;
+ sess->colorspace = pixmp->colorspace;
+ sess->ycbcr_enc = pixmp->ycbcr_enc;
+ sess->quantization = pixmp->quantization;
+ sess->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(sess, num_formats, &format);
+
+ sess->width = format.fmt.pix_mp.width;
+ sess->height = format.fmt.pix_mp.height;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ sess->fmt_out = fmt_out;
+ else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ sess->pixfmt_cap = format.fmt.pix_mp.pixelformat;
+
+ return 0;
+}
+
+static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+ const struct vdec_platform *platform = sess->core->platform;
+ const struct amvdec_format *fmt_out;
+
+ memset(f->reserved, 0, sizeof(f->reserved));
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ if (f->index >= platform->num_formats)
+ return -EINVAL;
+
+ fmt_out = &platform->formats[f->index];
+ f->pixelformat = fmt_out->pixfmt;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ fmt_out = sess->fmt_out;
+ if (f->index >= 4 || !fmt_out->pixfmts_cap[f->index])
+ return -EINVAL;
+
+ f->pixelformat = fmt_out->pixfmts_cap[f->index];
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vdec_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+ const struct amvdec_format *formats = sess->core->platform->formats;
+ const struct amvdec_format *fmt;
+ u32 num_formats = sess->core->platform->num_formats;
+
+ fmt = find_format(formats, num_formats, fsize->pixel_format);
+ if (!fmt || fsize->index)
+ return -EINVAL;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+
+ fsize->stepwise.min_width = 256;
+ fsize->stepwise.max_width = fmt->max_width;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.min_height = 144;
+ fsize->stepwise.max_height = fmt->max_height;
+ fsize->stepwise.step_height = 1;
+
+ return 0;
+}
+
+static int
+vdec_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd)
+{
+ switch (cmd->cmd) {
+ case V4L2_DEC_CMD_STOP:
+ if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+ int ret;
+
+ ret = vdec_try_decoder_cmd(file, fh, cmd);
+ if (ret)
+ return ret;
+
+ if (!(sess->streamon_out & sess->streamon_cap))
+ goto unlock;
+
+ dev_dbg(sess->core->dev, "Received V4L2_DEC_CMD_STOP\n");
+ sess->should_stop = 1;
+
+ vdec_wait_inactive(sess);
+
+ mutex_lock(&sess->lock);
+ if (codec_ops->drain)
+ codec_ops->drain(sess);
+ else
+ esparser_queue_eos(sess->core);
+
+unlock:
+ mutex_unlock(&sess->lock);
+ return ret;
+}
+
+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);
+ 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_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+ .vidioc_enum_framesizes = vdec_enum_framesizes,
+ .vidioc_subscribe_event = vdec_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_try_decoder_cmd = vdec_try_decoder_cmd,
+ .vidioc_decoder_cmd = vdec_decoder_cmd,
+};
+
+static int m2m_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct amvdec_session *sess = priv;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->ops = &vdec_vb2_ops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->drv_priv = sess;
+ src_vq->buf_struct_size = sizeof(struct dummy_buf);
+ src_vq->allow_zero_bytesused = 1;
+ src_vq->min_buffers_needed = 1;
+ src_vq->dev = sess->core->dev;
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->ops = &vdec_vb2_ops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->drv_priv = sess;
+ dst_vq->buf_struct_size = sizeof(struct dummy_buf);
+ dst_vq->allow_zero_bytesused = 1;
+ dst_vq->min_buffers_needed = 1;
+ dst_vq->dev = sess->core->dev;
+ ret = vb2_queue_init(dst_vq);
+ if (ret) {
+ vb2_queue_release(src_vq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vdec_open(struct file *file)
+{
+ struct amvdec_core *core = video_drvdata(file);
+ struct device *dev = core->dev;
+ const struct amvdec_format *formats = core->platform->formats;
+ struct amvdec_session *sess;
+ int ret;
+
+ sess = kzalloc(sizeof(*sess), GFP_KERNEL);
+ if (!sess) {
+ mutex_unlock(&core->lock);
+ return -ENOMEM;
+ }
+
+ sess->core = core;
+
+ sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops);
+ if (IS_ERR(sess->m2m_dev)) {
+ dev_err(dev, "Fail to v4l2_m2m_init\n");
+ ret = PTR_ERR(sess->m2m_dev);
+ goto err_free_sess;
+ }
+
+ sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init);
+ if (IS_ERR(sess->m2m_ctx)) {
+ dev_err(dev, "Fail to v4l2_m2m_ctx_init\n");
+ ret = PTR_ERR(sess->m2m_ctx);
+ goto err_m2m_release;
+ }
+
+ sess->pixfmt_cap = formats[0].pixfmts_cap[0];
+ sess->fmt_out = &formats[0];
+ sess->width = 1280;
+ sess->height = 720;
+
+ INIT_LIST_HEAD(&sess->timestamps);
+ INIT_LIST_HEAD(&sess->bufs_recycle);
+ INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src);
+ mutex_init(&sess->lock);
+ mutex_init(&sess->bufs_recycle_lock);
+ spin_lock_init(&sess->ts_spinlock);
+
+ v4l2_fh_init(&sess->fh, core->vdev_dec);
+ v4l2_fh_add(&sess->fh);
+ sess->fh.m2m_ctx = sess->m2m_ctx;
+ file->private_data = &sess->fh;
+
+ return 0;
+
+err_m2m_release:
+ v4l2_m2m_release(sess->m2m_dev);
+err_free_sess:
+ kfree(sess);
+ return ret;
+}
+
+static int vdec_close(struct file *file)
+{
+ struct amvdec_session *sess =
+ container_of(file->private_data, struct amvdec_session, fh);
+ struct amvdec_core *core = sess->core;
+
+ v4l2_m2m_ctx_release(sess->m2m_ctx);
+ v4l2_m2m_release(sess->m2m_dev);
+ v4l2_fh_del(&sess->fh);
+ v4l2_fh_exit(&sess->fh);
+
+ mutex_destroy(&sess->lock);
+ mutex_destroy(&sess->bufs_recycle_lock);
+
+ kfree(sess);
+
+ if (core->cur_sess == sess)
+ core->cur_sess = NULL;
+
+ return 0;
+}
+
+static const struct v4l2_file_operations vdec_fops = {
+ .owner = THIS_MODULE,
+ .open = vdec_open,
+ .release = vdec_close,
+ .unlocked_ioctl = video_ioctl2,
+ .poll = v4l2_m2m_fop_poll,
+ .mmap = v4l2_m2m_fop_mmap,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = v4l2_compat_ioctl32,
+#endif
+};
+
+static irqreturn_t vdec_isr(int irq, void *data)
+{
+ struct amvdec_core *core = data;
+ struct amvdec_session *sess = core->cur_sess;
+
+ sess->last_irq_jiffies = get_jiffies_64();
+
+ return sess->fmt_out->codec_ops->isr(sess);
+}
+
+static irqreturn_t vdec_threaded_isr(int irq, void *data)
+{
+ struct amvdec_core *core = data;
+ struct amvdec_session *sess = core->cur_sess;
+
+ return sess->fmt_out->codec_ops->threaded_isr(sess);
+}
+
+static const struct of_device_id vdec_dt_match[] = {
+ { .compatible = "amlogic,gxbb-vdec",
+ .data = &vdec_platform_gxbb },
+ { .compatible = "amlogic,gxm-vdec",
+ .data = &vdec_platform_gxm },
+ { .compatible = "amlogic,gxl-vdec",
+ .data = &vdec_platform_gxl },
+ {}
+};
+MODULE_DEVICE_TABLE(of, vdec_dt_match);
+
+static int vdec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct video_device *vdev;
+ struct amvdec_core *core;
+ struct resource *r;
+ const struct of_device_id *of_id;
+ int irq;
+ int ret;
+
+ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
+ if (!core)
+ return -ENOMEM;
+
+ core->dev = dev;
+ platform_set_drvdata(pdev, core);
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dos");
+ core->dos_base = devm_ioremap_resource(dev, r);
+ if (IS_ERR(core->dos_base)) {
+ dev_err(dev, "Couldn't remap DOS memory\n");
+ return PTR_ERR(core->dos_base);
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "esparser");
+ core->esparser_base = devm_ioremap_resource(dev, r);
+ if (IS_ERR(core->esparser_base)) {
+ dev_err(dev, "Couldn't remap ESPARSER memory\n");
+ return PTR_ERR(core->esparser_base);
+ }
+
+ core->regmap_ao = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "amlogic,ao-sysctrl");
+ if (IS_ERR(core->regmap_ao)) {
+ dev_err(dev, "Couldn't regmap AO sysctrl\n");
+ return PTR_ERR(core->regmap_ao);
+ }
+
+ core->canvas = meson_canvas_get(dev);
+ if (!core->canvas)
+ return PTR_ERR(core->canvas);
+
+ core->dos_parser_clk = devm_clk_get(dev, "dos_parser");
+ if (IS_ERR(core->dos_parser_clk))
+ return -EPROBE_DEFER;
+
+ core->dos_clk = devm_clk_get(dev, "dos");
+ if (IS_ERR(core->dos_clk))
+ return -EPROBE_DEFER;
+
+ core->vdec_1_clk = devm_clk_get(dev, "vdec_1");
+ if (IS_ERR(core->vdec_1_clk))
+ return -EPROBE_DEFER;
+
+ core->vdec_hevc_clk = devm_clk_get(dev, "vdec_hevc");
+ if (IS_ERR(core->vdec_hevc_clk))
+ return -EPROBE_DEFER;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(core->dev, irq, vdec_isr,
+ vdec_threaded_isr, IRQF_ONESHOT,
+ "vdec", core);
+ if (ret)
+ return ret;
+
+ ret = esparser_init(pdev, core);
+ if (ret)
+ return ret;
+
+ ret = v4l2_device_register(dev, &core->v4l2_dev);
+ if (ret) {
+ dev_err(dev, "Couldn't register v4l2 device\n");
+ return -ENOMEM;
+ }
+
+ vdev = video_device_alloc();
+ if (!vdev) {
+ ret = -ENOMEM;
+ goto err_vdev_release;
+ }
+
+ strlcpy(vdev->name, "meson-video-decoder", sizeof(vdev->name));
+ vdev->release = video_device_release;
+ vdev->fops = &vdec_fops;
+ vdev->ioctl_ops = &vdec_ioctl_ops;
+ vdev->vfl_dir = VFL_DIR_M2M;
+ vdev->v4l2_dev = &core->v4l2_dev;
+ vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret) {
+ dev_err(dev, "Failed registering video device\n");
+ goto err_vdev_release;
+ }
+
+ of_id = of_match_node(vdec_dt_match, dev->of_node);
+ core->platform = of_id->data;
+ core->vdev_dec = vdev;
+ core->dev_dec = dev;
+ mutex_init(&core->lock);
+
+ video_set_drvdata(vdev, core);
+
+ return 0;
+
+err_vdev_release:
+ video_device_release(vdev);
+ return ret;
+}
+
+static int vdec_remove(struct platform_device *pdev)
+{
+ struct amvdec_core *core = platform_get_drvdata(pdev);
+
+ video_unregister_device(core->vdev_dec);
+
+ return 0;
+}
+
+static struct platform_driver meson_vdec_driver = {
+ .probe = vdec_probe,
+ .remove = vdec_remove,
+ .driver = {
+ .name = "meson-vdec",
+ .of_match_table = vdec_dt_match,
+ },
+};
+module_platform_driver(meson_vdec_driver);
+
+MODULE_DESCRIPTION("Meson video decoder driver for GXBB/GXL/GXM");
+MODULE_AUTHOR("Maxime Jourdan <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/meson/vdec/vdec.h b/drivers/media/platform/meson/vdec/vdec.h
new file mode 100644
index 000000000000..8250fb82dfab
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_CORE_H_
+#define __MESON_VDEC_CORE_H_
+
+/* 32 buffers in 3-plane YUV420 */
+#define MAX_CANVAS (32 * 3)
+
+#include <linux/regmap.h>
+#include <linux/list.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <linux/soc/amlogic/meson-canvas.h>
+
+#include "vdec_platform.h"
+
+struct amvdec_buffer {
+ struct list_head list;
+ struct vb2_buffer *vb;
+};
+
+struct amvdec_timestamp {
+ struct list_head list;
+ u64 ts;
+};
+
+struct amvdec_session;
+
+/**
+ * struct amvdec_core - device parameters, singleton
+ *
+ * @dos_base: DOS memory base address
+ * @esparser_base: PARSER memory base address
+ * @regmap_ao: regmap for the AO bus
+ * @dev: core device
+ * @dev_dec: decoder device
+ * @platform: platform-specific data
+ * @canvas: canvas provider reference
+ * @dos_parser_clk: DOS_PARSER clock
+ * @dos_clk: DOS clock
+ * @vdec_1_clk: VDEC_1 clock
+ * @vdec_hevc_clk: VDEC_HEVC clock
+ * @esparser_reset: RESET for the PARSER
+ * @vdec_dec: video device for the decoder
+ * @v4l2_dev: v4l2 device
+ * @cur_sess: current decoding session
+ * @lock: lock for this structure
+ */
+struct amvdec_core {
+ void __iomem *dos_base;
+ void __iomem *esparser_base;
+ struct regmap *regmap_ao;
+
+ struct device *dev;
+ struct device *dev_dec;
+ const struct vdec_platform *platform;
+
+ struct meson_canvas *canvas;
+
+ struct clk *dos_parser_clk;
+ struct clk *dos_clk;
+ struct clk *vdec_1_clk;
+ struct clk *vdec_hevc_clk;
+
+ struct reset_control *esparser_reset;
+
+ struct video_device *vdev_dec;
+ struct v4l2_device v4l2_dev;
+
+ struct amvdec_session *cur_sess;
+ struct mutex lock;
+};
+
+/**
+ * struct amvdec_ops - vdec operations
+ *
+ * @start: mandatory call when the vdec needs to initialize
+ * @stop: mandatory call when the vdec needs to stop
+ * @conf_esparser: mandatory call to let the vdec configure the ESPARSER
+ * @vififo_level: mandatory call to get the current amount of data
+ * in the VIFIFO
+ */
+struct amvdec_ops {
+ int (*start)(struct amvdec_session *sess);
+ int (*stop)(struct amvdec_session *sess);
+ void (*conf_esparser)(struct amvdec_session *sess);
+ u32 (*vififo_level)(struct amvdec_session *sess);
+};
+
+/**
+ * struct amvdec_codec_ops - codec operations
+ *
+ * @start: mandatory call when the codec needs to initialize
+ * @stop: mandatory call when the codec needs to stop
+ * @load_extended_firmware: optional call to load additional firmware bits
+ * @num_pending_bufs: optional call to get the number of dst buffers on hold
+ * @can_recycle: optional call to know if the codec is ready to recycle
+ * a dst buffer
+ * @recycle: optional call to tell the codec to recycle a dst buffer. Must go
+ * in pair with can_recycle
+ * @drain: optional call if the codec has a custom way of draining
+ * @isr: mandatory call when the ISR triggers
+ * @threaded_isr: mandatory call for the threaded ISR
+ */
+struct amvdec_codec_ops {
+ int (*start)(struct amvdec_session *sess);
+ int (*stop)(struct amvdec_session *sess);
+ int (*load_extended_firmware)(struct amvdec_session *sess,
+ const u8 *data, u32 len);
+ u32 (*num_pending_bufs)(struct amvdec_session *sess);
+ int (*can_recycle)(struct amvdec_core *core);
+ void (*recycle)(struct amvdec_core *core, u32 buf_idx);
+ void (*drain)(struct amvdec_session *sess);
+ irqreturn_t (*isr)(struct amvdec_session *sess);
+ irqreturn_t (*threaded_isr)(struct amvdec_session *sess);
+};
+
+/**
+ * struct amvdec_format - describes one of the OUTPUT (src) format supported
+ *
+ * @pixfmt: V4L2 pixel format
+ * @min_buffers: minimum amount of CAPTURE (dst) buffers
+ * @max_buffers: maximum amount of CAPTURE (dst) buffers
+ * @max_width: maximum picture width supported
+ * @max_height: maximum picture height supported
+ * @vdec_ops: the VDEC operations that support this format
+ * @codec_ops: the codec operations that support this format
+ * @firmware_path: Path to the firmware that supports this format
+ * @pixfmts_cap: list of CAPTURE pixel formats available with pixfmt
+ */
+struct amvdec_format {
+ u32 pixfmt;
+ u32 min_buffers;
+ u32 max_buffers;
+ u32 max_width;
+ u32 max_height;
+
+ struct amvdec_ops *vdec_ops;
+ struct amvdec_codec_ops *codec_ops;
+
+ char *firmware_path;
+ u32 pixfmts_cap[4];
+};
+
+/**
+ * struct amvdec_session - decoding session parameters
+ *
+ * @core: reference to the vdec core struct
+ * @fh: v4l2 file handle
+ * @m2m_dev: v4l2 m2m device
+ * @m2m_ctx: v4l2 m2m context
+ * @lock: session lock
+ * @fmt_out: vdec pixel format for the OUTPUT queue
+ * @pixfmt_cap: V4L2 pixel format for the CAPTURE queue
+ * @width: current picture width
+ * @height: current picture height
+ * @colorspace: current colorspace
+ * @ycbcr_enc: current ycbcr_enc
+ * @quantization: current quantization
+ * @xfer_func: current transfer function
+ * @esparser_queued_bufs: number of buffers currently queued into ESPARSER
+ * @esparser_queue_work: work struct for the ESPARSER to process src buffers
+ * @streamon_cap: stream on flag for capture queue
+ * @streamon_out: stream on flag for output queue
+ * @sequence_cap: capture sequence counter
+ * @should_stop: flag set is userspacec signaled EOS via command
+ * or empty buffer
+ * @keyframe_found: flag set once a keyframe has been parsed
+ * @canvas_alloc: array of all the canvas IDs allocated
+ * @canvas_num: number of canvas IDs allocated
+ * @vififo_vaddr: virtual address for the VIFIFO
+ * @vififo_paddr: physical address for the VIFIFO
+ * @vififo_size: size of the VIFIFO dma alloc
+ * @bufs_recycle: list of buffers that need to be recycled
+ * @bufs_recycle_lock: lock for the bufs_recycle list
+ * @recycle_thread: task struct for the recycling thread
+ * @timestamps: chronological list of src timestamps
+ * @ts_spinlock: spinlock for the timestamps list
+ * @last_irq_jiffies: tracks last time the vdec triggered an IRQ
+ * @priv: codec private data
+ */
+struct amvdec_session {
+ struct amvdec_core *core;
+
+ struct v4l2_fh fh;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct v4l2_m2m_ctx *m2m_ctx;
+ struct mutex lock;
+
+ const struct amvdec_format *fmt_out;
+ u32 pixfmt_cap;
+
+ u32 width;
+ u32 height;
+ u32 colorspace;
+ u8 ycbcr_enc;
+ u8 quantization;
+ u8 xfer_func;
+
+ atomic_t esparser_queued_bufs;
+ struct work_struct esparser_queue_work;
+
+ unsigned int streamon_cap, streamon_out;
+ unsigned int sequence_cap;
+ unsigned int should_stop;
+ unsigned int keyframe_found;
+
+ u8 canvas_alloc[MAX_CANVAS];
+ u32 canvas_num;
+
+ void *vififo_vaddr;
+ dma_addr_t vififo_paddr;
+ u32 vififo_size;
+
+ struct list_head bufs_recycle;
+ struct mutex bufs_recycle_lock;
+ struct task_struct *recycle_thread;
+
+ struct list_head timestamps;
+ spinlock_t ts_spinlock;
+
+ u64 last_irq_jiffies;
+
+ void *priv;
+};
+
+u32 amvdec_get_output_size(struct amvdec_session *sess);
+
+#endif
diff --git a/drivers/media/platform/meson/vdec/vdec_1.c b/drivers/media/platform/meson/vdec/vdec_1.c
new file mode 100644
index 000000000000..29f6305a6276
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec_1.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ *
+ * VDEC_1 is a video decoding block that allows decoding of
+ * MPEG 1/2/4, H.263, H.264, MJPEG, VC1
+ */
+
+#include <linux/firmware.h>
+#include <linux/clk.h>
+
+#include "vdec_1.h"
+#include "vdec_helpers.h"
+#include "dos_regs.h"
+
+/* AO Registers */
+#define AO_RTI_GEN_PWR_SLEEP0 0xe8
+#define AO_RTI_GEN_PWR_ISO0 0xec
+ #define GEN_PWR_VDEC_1 (BIT(3) | BIT(2))
+
+#define MC_SIZE (4096 * 4)
+
+static int
+vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname)
+{
+ const struct firmware *fw;
+ struct amvdec_core *core = sess->core;
+ struct device *dev = core->dev_dec;
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+ static void *mc_addr;
+ static dma_addr_t mc_addr_map;
+ int ret;
+ u32 i = 1000;
+
+ ret = request_firmware(&fw, fwname, dev);
+ if (ret < 0)
+ return -EINVAL;
+
+ if (fw->size < MC_SIZE) {
+ dev_err(dev, "Firmware size %zu is too small. Expected %u.\n",
+ fw->size, MC_SIZE);
+ ret = -EINVAL;
+ goto release_firmware;
+ }
+
+ mc_addr = dma_alloc_coherent(core->dev, MC_SIZE,
+ &mc_addr_map, GFP_KERNEL);
+ if (!mc_addr) {
+ dev_err(dev,
+ "Failed allocating memory for firmware loading\n");
+ ret = -ENOMEM;
+ goto release_firmware;
+ }
+
+ memcpy(mc_addr, fw->data, MC_SIZE);
+
+ amvdec_write_dos(core, MPSR, 0);
+ amvdec_write_dos(core, CPSR, 0);
+
+ amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31));
+
+ amvdec_write_dos(core, IMEM_DMA_ADR, mc_addr_map);
+ amvdec_write_dos(core, IMEM_DMA_COUNT, MC_SIZE / 4);
+ amvdec_write_dos(core, IMEM_DMA_CTRL, (0x8000 | (7 << 16)));
+
+ while (--i && amvdec_read_dos(core, IMEM_DMA_CTRL) & 0x8000) { }
+
+ if (i == 0) {
+ dev_err(dev, "Firmware load fail (DMA hang?)\n");
+ ret = -EINVAL;
+ goto free_mc;
+ }
+
+ if (codec_ops->load_extended_firmware)
+ codec_ops->load_extended_firmware(sess, fw->data + MC_SIZE,
+ fw->size - MC_SIZE);
+
+free_mc:
+ dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map);
+release_firmware:
+ release_firmware(fw);
+ return ret;
+}
+
+int vdec_1_stbuf_power_up(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_CONTROL, 0);
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_WRAP_COUNT, 0);
+ amvdec_write_dos(core, POWER_CTL_VLD, BIT(4));
+
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_START_PTR, sess->vififo_paddr);
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_CURR_PTR, sess->vififo_paddr);
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_END_PTR,
+ sess->vififo_paddr + sess->vififo_size - 8);
+
+ amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1);
+ amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1);
+
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, MEM_BUFCTRL_MANUAL);
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_WP, sess->vififo_paddr);
+
+ amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
+ amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
+
+ amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL,
+ (0x11 << MEM_FIFO_CNT_BIT) | MEM_FILL_ON_LEVEL |
+ MEM_CTRL_FILL_EN | MEM_CTRL_EMPTY_EN);
+
+ return 0;
+}
+
+static void vdec_1_conf_esparser(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+
+ /* VDEC_1 specific ESPARSER stuff */
+ amvdec_write_dos(core, DOS_GEN_CTRL0, 0);
+ amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
+ amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
+}
+
+static u32 vdec_1_vififo_level(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+
+ return amvdec_read_dos(core, VLD_MEM_VIFIFO_LEVEL);
+}
+
+static int vdec_1_stop(struct amvdec_session *sess)
+{
+ struct amvdec_core *core = sess->core;
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+
+ amvdec_write_dos(core, MPSR, 0);
+ amvdec_write_dos(core, CPSR, 0);
+ amvdec_write_dos(core, ASSIST_MBOX1_MASK, 0);
+
+ amvdec_write_dos(core, DOS_SW_RESET0, BIT(12) | BIT(11));
+ amvdec_write_dos(core, DOS_SW_RESET0, 0);
+ amvdec_read_dos(core, DOS_SW_RESET0);
+
+ /* enable vdec1 isolation */
+ regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0xc0);
+ /* power off vdec1 memories */
+ amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0xffffffff);
+ /* power off vdec1 */
+ regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0,
+ GEN_PWR_VDEC_1, GEN_PWR_VDEC_1);
+
+ clk_disable_unprepare(core->vdec_1_clk);
+
+ if (sess->priv)
+ codec_ops->stop(sess);
+
+ return 0;
+}
+
+static int vdec_1_start(struct amvdec_session *sess)
+{
+ int ret;
+ struct amvdec_core *core = sess->core;
+ struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
+
+ /* Configure the vdec clk to the maximum available */
+ clk_set_rate(core->vdec_1_clk, 666666666);
+ ret = clk_prepare_enable(core->vdec_1_clk);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0,
+ GEN_PWR_VDEC_1, 0);
+ udelay(10);
+
+ /* Reset VDEC1 */
+ amvdec_write_dos(core, DOS_SW_RESET0, 0xfffffffc);
+ amvdec_write_dos(core, DOS_SW_RESET0, 0x00000000);
+
+ amvdec_write_dos(core, DOS_GCLK_EN0, 0x3ff);
+
+ /* enable VDEC Memories */
+ amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0);
+ /* Remove VDEC1 Isolation */
+ regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0);
+ /* Reset DOS top registers */
+ amvdec_write_dos(core, DOS_VDEC_MCRCC_STALL_CTRL, 0);
+
+ amvdec_write_dos(core, GCLK_EN, 0x3ff);
+ amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31));
+
+ vdec_1_stbuf_power_up(sess);
+
+ ret = vdec_1_load_firmware(sess, sess->fmt_out->firmware_path);
+ if (ret)
+ goto stop;
+
+ ret = codec_ops->start(sess);
+ if (ret)
+ goto stop;
+
+ /* Enable IRQ */
+ amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1);
+ amvdec_write_dos(core, ASSIST_MBOX1_MASK, 1);
+
+ /* Enable 2-plane output */
+ if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M)
+ amvdec_write_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17));
+
+ /* Enable firmware processor */
+ amvdec_write_dos(core, MPSR, 1);
+ /* Let the firmware settle */
+ udelay(10);
+
+ return 0;
+
+stop:
+ vdec_1_stop(sess);
+ return ret;
+}
+
+struct amvdec_ops vdec_1_ops = {
+ .start = vdec_1_start,
+ .stop = vdec_1_stop,
+ .conf_esparser = vdec_1_conf_esparser,
+ .vififo_level = vdec_1_vififo_level,
+};
diff --git a/drivers/media/platform/meson/vdec/vdec_1.h b/drivers/media/platform/meson/vdec/vdec_1.h
new file mode 100644
index 000000000000..042d930c40d7
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec_1.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_VDEC_1_H_
+#define __MESON_VDEC_VDEC_1_H_
+
+#include "vdec.h"
+
+extern struct amvdec_ops vdec_1_ops;
+
+#endif
diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.c b/drivers/media/platform/meson/vdec/vdec_helpers.c
new file mode 100644
index 000000000000..615107629765
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec_helpers.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "vdec_helpers.h"
+
+#define NUM_CANVAS_NV12 2
+#define NUM_CANVAS_YUV420 3
+
+u32 amvdec_read_dos(struct amvdec_core *core, u32 reg)
+{
+ return readl_relaxed(core->dos_base + reg);
+}
+EXPORT_SYMBOL_GPL(amvdec_read_dos);
+
+void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val)
+{
+ writel_relaxed(val, core->dos_base + reg);
+}
+EXPORT_SYMBOL_GPL(amvdec_write_dos);
+
+void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val)
+{
+ amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) | val);
+}
+EXPORT_SYMBOL_GPL(amvdec_write_dos_bits);
+
+void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val)
+{
+ amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) & ~val);
+}
+EXPORT_SYMBOL_GPL(amvdec_clear_dos_bits);
+
+u32 amvdec_read_parser(struct amvdec_core *core, u32 reg)
+{
+ return readl_relaxed(core->esparser_base + reg);
+}
+EXPORT_SYMBOL_GPL(amvdec_read_parser);
+
+void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val)
+{
+ writel_relaxed(val, core->esparser_base + reg);
+}
+EXPORT_SYMBOL_GPL(amvdec_write_parser);
+
+static int canvas_alloc(struct amvdec_session *sess, u8 *canvas_id)
+{
+ int ret;
+
+ if (sess->canvas_num >= MAX_CANVAS) {
+ dev_err(sess->core->dev, "Reached max number of canvas\n");
+ return -ENOMEM;
+ }
+
+ ret = meson_canvas_alloc(sess->core->canvas, canvas_id);
+ if (ret)
+ return ret;
+
+ sess->canvas_alloc[sess->canvas_num++] = *canvas_id;
+ return 0;
+}
+
+static int set_canvas_yuv420m(struct amvdec_session *sess,
+ struct vb2_buffer *vb, u32 width,
+ u32 height, u32 reg)
+{
+ struct amvdec_core *core = sess->core;
+ u8 canvas_id[NUM_CANVAS_YUV420]; /* Y U V */
+ dma_addr_t buf_paddr[NUM_CANVAS_YUV420]; /* Y U V */
+ int ret, i;
+
+ for (i = 0; i < NUM_CANVAS_YUV420; ++i) {
+ ret = canvas_alloc(sess, &canvas_id[i]);
+ if (ret)
+ return ret;
+
+ buf_paddr[i] =
+ vb2_dma_contig_plane_dma_addr(vb, i);
+ }
+
+ /* Y plane */
+ meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0],
+ width, height, MESON_CANVAS_WRAP_NONE,
+ MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+
+ /* U plane */
+ meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1],
+ width / 2, height / 2, MESON_CANVAS_WRAP_NONE,
+ MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+
+ /* V plane */
+ meson_canvas_config(core->canvas, canvas_id[2], buf_paddr[2],
+ width / 2, height / 2, MESON_CANVAS_WRAP_NONE,
+ MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+
+ amvdec_write_dos(core, reg,
+ ((canvas_id[2]) << 16) |
+ ((canvas_id[1]) << 8) |
+ (canvas_id[0]));
+
+ return 0;
+}
+
+static int set_canvas_nv12m(struct amvdec_session *sess,
+ struct vb2_buffer *vb, u32 width,
+ u32 height, u32 reg)
+{
+ struct amvdec_core *core = sess->core;
+ u8 canvas_id[NUM_CANVAS_NV12]; /* Y U/V */
+ dma_addr_t buf_paddr[NUM_CANVAS_NV12]; /* Y U/V */
+ int ret, i;
+
+ for (i = 0; i < NUM_CANVAS_NV12; ++i) {
+ ret = canvas_alloc(sess, &canvas_id[i]);
+ if (ret)
+ return ret;
+
+ buf_paddr[i] =
+ vb2_dma_contig_plane_dma_addr(vb, i);
+ }
+
+ /* Y plane */
+ meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0],
+ width, height, MESON_CANVAS_WRAP_NONE,
+ MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+
+ /* U/V plane */
+ meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1],
+ width, height / 2, MESON_CANVAS_WRAP_NONE,
+ MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+
+ amvdec_write_dos(core, reg,
+ ((canvas_id[1]) << 16) |
+ ((canvas_id[1]) << 8) |
+ (canvas_id[0]));
+
+ return 0;
+}
+
+int amvdec_set_canvases(struct amvdec_session *sess,
+ u32 reg_base[], u32 reg_num[])
+{
+ struct v4l2_m2m_buffer *buf;
+ u32 pixfmt = sess->pixfmt_cap;
+ u32 width = ALIGN(sess->width, 64);
+ u32 height = ALIGN(sess->height, 64);
+ u32 reg_cur = reg_base[0];
+ u32 reg_num_cur = 0;
+ u32 reg_base_cur = 0;
+ int ret;
+
+ v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) {
+ if (!reg_base[reg_base_cur])
+ return -EINVAL;
+
+ reg_cur = reg_base[reg_base_cur] + reg_num_cur * 4;
+
+ switch (pixfmt) {
+ case V4L2_PIX_FMT_NV12M:
+ ret = set_canvas_nv12m(sess, &buf->vb.vb2_buf, width,
+ height, reg_cur);
+ if (ret)
+ return ret;
+ break;
+ case V4L2_PIX_FMT_YUV420M:
+ ret = set_canvas_yuv420m(sess, &buf->vb.vb2_buf, width,
+ height, reg_cur);
+ if (ret)
+ return ret;
+ break;
+ default:
+ dev_err(sess->core->dev, "Unsupported pixfmt %08X\n",
+ pixfmt);
+ return -EINVAL;
+ };
+
+ reg_num_cur++;
+ if (reg_num_cur >= reg_num[reg_base_cur]) {
+ reg_base_cur++;
+ reg_num_cur = 0;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(amvdec_set_canvases);
+
+void amvdec_dst_buf_done(struct amvdec_session *sess,
+ struct vb2_v4l2_buffer *vbuf, u32 field)
+{
+ struct device *dev = sess->core->dev_dec;
+ struct amvdec_timestamp *tmp;
+ struct list_head *timestamps = &sess->timestamps;
+ u32 output_size = amvdec_get_output_size(sess);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sess->ts_spinlock, flags);
+ if (list_empty(timestamps)) {
+ dev_err(dev, "Buffer %u done but list is empty\n",
+ vbuf->vb2_buf.index);
+
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+ amvdec_abort(sess);
+ spin_unlock_irqrestore(&sess->ts_spinlock, flags);
+ goto end;
+ }
+
+ tmp = list_first_entry(timestamps, struct amvdec_timestamp, list);
+
+ switch (sess->pixfmt_cap) {
+ case V4L2_PIX_FMT_NV12M:
+ vbuf->vb2_buf.planes[0].bytesused = output_size;
+ vbuf->vb2_buf.planes[1].bytesused = output_size / 2;
+ break;
+ case V4L2_PIX_FMT_YUV420M:
+ vbuf->vb2_buf.planes[0].bytesused = output_size;
+ vbuf->vb2_buf.planes[1].bytesused = output_size / 4;
+ vbuf->vb2_buf.planes[2].bytesused = output_size / 4;
+ break;
+ }
+ vbuf->vb2_buf.timestamp = tmp->ts;
+ vbuf->sequence = sess->sequence_cap++;
+
+ list_del(&tmp->list);
+ kfree(tmp);
+ spin_unlock_irqrestore(&sess->ts_spinlock, flags);
+
+ atomic_dec(&sess->esparser_queued_bufs);
+
+ if (sess->should_stop && list_empty(timestamps)) {
+ const struct v4l2_event ev = { .type = V4L2_EVENT_EOS };
+
+ dev_dbg(dev, "Signaling EOS\n");
+ v4l2_event_queue_fh(&sess->fh, &ev);
+ vbuf->flags |= V4L2_BUF_FLAG_LAST;
+ } else if (sess->should_stop)
+ dev_dbg(dev, "should_stop, %u bufs remain\n",
+ atomic_read(&sess->esparser_queued_bufs));
+
+ vbuf->field = field;
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
+
+end:
+ /* Buffer done probably means the vififo got freed */
+ schedule_work(&sess->esparser_queue_work);
+}
+EXPORT_SYMBOL_GPL(amvdec_dst_buf_done);
+
+void
+amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx, u32 field)
+{
+ struct vb2_v4l2_buffer *vbuf;
+ struct device *dev = sess->core->dev_dec;
+
+ vbuf = v4l2_m2m_dst_buf_remove_by_idx(sess->m2m_ctx, buf_idx);
+ if (!vbuf) {
+ dev_err(dev,
+ "Buffer %u done but it doesn't exist in m2m_ctx\n",
+ buf_idx);
+ amvdec_rm_first_ts(sess);
+ return;
+ }
+
+ amvdec_dst_buf_done(sess, vbuf, field);
+}
+EXPORT_SYMBOL_GPL(amvdec_dst_buf_done_idx);
+
+void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts)
+{
+ struct amvdec_timestamp *new_ts, *tmp;
+ unsigned long flags;
+
+ new_ts = kmalloc(sizeof(*new_ts), GFP_KERNEL);
+ new_ts->ts = ts;
+
+ spin_lock_irqsave(&sess->ts_spinlock, flags);
+
+ if (list_empty(&sess->timestamps))
+ goto add_tail;
+
+ list_for_each_entry(tmp, &sess->timestamps, list) {
+ if (ts < tmp->ts) {
+ list_add_tail(&new_ts->list, &tmp->list);
+ goto unlock;
+ }
+ }
+
+add_tail:
+ list_add_tail(&new_ts->list, &sess->timestamps);
+unlock:
+ spin_unlock_irqrestore(&sess->ts_spinlock, flags);
+}
+EXPORT_SYMBOL_GPL(amvdec_add_ts_reorder);
+
+void amvdec_remove_ts(struct amvdec_session *sess, u64 ts)
+{
+ struct amvdec_timestamp *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sess->ts_spinlock, flags);
+ list_for_each_entry(tmp, &sess->timestamps, list) {
+ if (tmp->ts == ts) {
+ list_del(&tmp->list);
+ kfree(tmp);
+ goto unlock;
+ }
+ }
+ dev_warn(sess->core->dev_dec,
+ "Couldn't remove buffer with timestamp %llu from list\n", ts);
+
+unlock:
+ spin_unlock_irqrestore(&sess->ts_spinlock, flags);
+}
+EXPORT_SYMBOL_GPL(amvdec_remove_ts);
+
+void amvdec_rm_first_ts(struct amvdec_session *sess)
+{
+ unsigned long flags;
+ struct amvdec_buffer *tmp;
+ struct device *dev = sess->core->dev_dec;
+
+ spin_lock_irqsave(&sess->ts_spinlock, flags);
+ if (list_empty(&sess->timestamps)) {
+ dev_err(dev, "Can't rm first timestamp: list empty\n");
+ goto unlock;
+ }
+
+ tmp = list_first_entry(&sess->timestamps, struct amvdec_buffer, list);
+ list_del(&tmp->list);
+ kfree(tmp);
+ atomic_dec(&sess->esparser_queued_bufs);
+
+unlock:
+ spin_unlock_irqrestore(&sess->ts_spinlock, flags);
+}
+
+void amvdec_abort(struct amvdec_session *sess)
+{
+ dev_info(sess->core->dev, "Aborting decoding session!\n");
+ vb2_queue_error(&sess->m2m_ctx->cap_q_ctx.q);
+ vb2_queue_error(&sess->m2m_ctx->out_q_ctx.q);
+}
+EXPORT_SYMBOL_GPL(amvdec_abort);
diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.h b/drivers/media/platform/meson/vdec/vdec_helpers.h
new file mode 100644
index 000000000000..352c6b4c4b84
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec_helpers.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_HELPERS_H_
+#define __MESON_VDEC_HELPERS_H_
+
+#include "vdec.h"
+
+/**
+ * amvdec_set_canvases() - Map VB2 buffers to canvases
+ *
+ * @sess: current session
+ * @reg_base: Registry bases of where to write the canvas indexes
+ * @reg_num: number of contiguous registers after each reg_base (including it)
+ */
+int amvdec_set_canvases(struct amvdec_session *sess,
+ u32 reg_base[], u32 reg_num[]);
+
+u32 amvdec_read_dos(struct amvdec_core *core, u32 reg);
+void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val);
+void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val);
+void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val);
+u32 amvdec_read_parser(struct amvdec_core *core, u32 reg);
+void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val);
+
+void amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx,
+ u32 field);
+void amvdec_dst_buf_done(struct amvdec_session *sess,
+ struct vb2_v4l2_buffer *vbuf, u32 field);
+
+/**
+ * amvdec_add_ts_reorder() - Add a timestamp to the list in chronological order
+ *
+ * @sess: current session
+ * @ts: timestamp to add
+ */
+void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts);
+void amvdec_remove_ts(struct amvdec_session *sess, u64 ts);
+void amvdec_rm_first_ts(struct amvdec_session *sess);
+
+void amvdec_abort(struct amvdec_session *sess);
+#endif
diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c
new file mode 100644
index 000000000000..46eeb7426f54
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec_platform.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#include "vdec_platform.h"
+#include "vdec.h"
+
+#include "vdec_1.h"
+#include "codec_mpeg12.h"
+
+static const struct amvdec_format vdec_formats_gxbb[] = {
+ {
+ .pixfmt = V4L2_PIX_FMT_MPEG1,
+ .min_buffers = 8,
+ .max_buffers = 8,
+ .max_width = 1920,
+ .max_height = 1080,
+ .vdec_ops = &vdec_1_ops,
+ .codec_ops = &codec_mpeg12_ops,
+ .firmware_path = "meson/gx/vmpeg12_mc",
+ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG2,
+ .min_buffers = 8,
+ .max_buffers = 8,
+ .max_width = 1920,
+ .max_height = 1080,
+ .vdec_ops = &vdec_1_ops,
+ .codec_ops = &codec_mpeg12_ops,
+ .firmware_path = "meson/gx/vmpeg12_mc",
+ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
+ },
+};
+
+static const struct amvdec_format vdec_formats_gxl[] = {
+ {
+ .pixfmt = V4L2_PIX_FMT_MPEG1,
+ .min_buffers = 8,
+ .max_buffers = 8,
+ .max_width = 1920,
+ .max_height = 1080,
+ .vdec_ops = &vdec_1_ops,
+ .codec_ops = &codec_mpeg12_ops,
+ .firmware_path = "meson/gx/vmpeg12_mc",
+ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG2,
+ .min_buffers = 8,
+ .max_buffers = 8,
+ .max_width = 1920,
+ .max_height = 1080,
+ .vdec_ops = &vdec_1_ops,
+ .codec_ops = &codec_mpeg12_ops,
+ .firmware_path = "meson/gx/vmpeg12_mc",
+ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
+ },
+};
+
+static const struct amvdec_format vdec_formats_gxm[] = {
+ {
+ .pixfmt = V4L2_PIX_FMT_MPEG1,
+ .min_buffers = 8,
+ .max_buffers = 8,
+ .max_width = 1920,
+ .max_height = 1080,
+ .vdec_ops = &vdec_1_ops,
+ .codec_ops = &codec_mpeg12_ops,
+ .firmware_path = "meson/gx/vmpeg12_mc",
+ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
+ }, {
+ .pixfmt = V4L2_PIX_FMT_MPEG2,
+ .min_buffers = 8,
+ .max_buffers = 8,
+ .max_width = 1920,
+ .max_height = 1080,
+ .vdec_ops = &vdec_1_ops,
+ .codec_ops = &codec_mpeg12_ops,
+ .firmware_path = "meson/gx/vmpeg12_mc",
+ .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
+ },
+};
+
+const struct vdec_platform vdec_platform_gxbb = {
+ .formats = vdec_formats_gxbb,
+ .num_formats = ARRAY_SIZE(vdec_formats_gxbb),
+ .revision = VDEC_REVISION_GXBB,
+};
+
+const struct vdec_platform vdec_platform_gxl = {
+ .formats = vdec_formats_gxl,
+ .num_formats = ARRAY_SIZE(vdec_formats_gxl),
+ .revision = VDEC_REVISION_GXL,
+};
+
+const struct vdec_platform vdec_platform_gxm = {
+ .formats = vdec_formats_gxm,
+ .num_formats = ARRAY_SIZE(vdec_formats_gxm),
+ .revision = VDEC_REVISION_GXM,
+};
diff --git a/drivers/media/platform/meson/vdec/vdec_platform.h b/drivers/media/platform/meson/vdec/vdec_platform.h
new file mode 100644
index 000000000000..f6025326db1d
--- /dev/null
+++ b/drivers/media/platform/meson/vdec/vdec_platform.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 BayLibre, SAS
+ * Author: Maxime Jourdan <[email protected]>
+ */
+
+#ifndef __MESON_VDEC_PLATFORM_H_
+#define __MESON_VDEC_PLATFORM_H_
+
+#include "vdec.h"
+
+struct amvdec_format;
+
+enum vdec_revision {
+ VDEC_REVISION_GXBB,
+ VDEC_REVISION_GXL,
+ VDEC_REVISION_GXM,
+};
+
+struct vdec_platform {
+ const struct amvdec_format *formats;
+ const u32 num_formats;
+ enum vdec_revision revision;
+};
+
+extern const struct vdec_platform vdec_platform_gxbb;
+extern const struct vdec_platform vdec_platform_gxm;
+extern const struct vdec_platform vdec_platform_gxl;
+
+#endif
--
2.18.0


2018-08-31 08:54:50

by Maxime Jourdan

[permalink] [raw]
Subject: [PATCH 4/4] ARM64: dts: meson: add vdec entries

This enables the video decoder for gxbb, gxl and gxm chips

Signed-off-by: Maxime Jourdan <[email protected]>
---
arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 11 +++++++++++
arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 11 +++++++++++
arch/arm64/boot/dts/amlogic/meson-gxm.dtsi | 4 ++++
3 files changed, 26 insertions(+)

diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
index 98cbba6809ca..daee53a755d7 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi
@@ -774,3 +774,14 @@
compatible = "amlogic,meson-gxbb-vpu", "amlogic,meson-gx-vpu";
power-domains = <&pwrc_vpu>;
};
+
+&vdec {
+ compatible = "amlogic,gxbb-vdec";
+ clocks = <&clkc CLKID_DOS_PARSER>,
+ <&clkc CLKID_DOS>,
+ <&clkc CLKID_VDEC_1>,
+ <&clkc CLKID_VDEC_HEVC>;
+ clock-names = "dos_parser", "dos", "vdec_1", "vdec_hevc";
+ resets = <&reset RESET_PARSER>;
+ reset-names = "esparser";
+};
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi
index c87a80e9bcc6..bfd65d1a8959 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxl.dtsi
@@ -775,3 +775,14 @@
compatible = "amlogic,meson-gxl-vpu", "amlogic,meson-gx-vpu";
power-domains = <&pwrc_vpu>;
};
+
+&vdec {
+ compatible = "amlogic,gxl-vdec";
+ clocks = <&clkc CLKID_DOS_PARSER>,
+ <&clkc CLKID_DOS>,
+ <&clkc CLKID_VDEC_1>,
+ <&clkc CLKID_VDEC_HEVC>;
+ clock-names = "dos_parser", "dos", "vdec_1", "vdec_hevc";
+ resets = <&reset RESET_PARSER>;
+ reset-names = "esparser";
+};
diff --git a/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi b/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi
index 247888d68a3a..2f356495be5e 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gxm.dtsi
@@ -117,3 +117,7 @@
&dwc3 {
phys = <&usb3_phy>, <&usb2_phy0>, <&usb2_phy1>, <&usb2_phy2>;
};
+
+&vdec {
+ compatible = "amlogic,gxm-vdec";
+};
--
2.18.0


2018-08-31 08:55:01

by Maxime Jourdan

[permalink] [raw]
Subject: [PATCH 1/4] dt-bindings: media: add Amlogic Video Decoder Bindings

Add documentation for the meson vdec dts node.

Signed-off-by: Maxime Jourdan <[email protected]>
---
.../bindings/media/amlogic,vdec.txt | 63 +++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/amlogic,vdec.txt

diff --git a/Documentation/devicetree/bindings/media/amlogic,vdec.txt b/Documentation/devicetree/bindings/media/amlogic,vdec.txt
new file mode 100644
index 000000000000..c6450f2e7f28
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,vdec.txt
@@ -0,0 +1,63 @@
+Amlogic Video Decoder
+================================
+
+The VDEC IP is composed of the following blocks :
+
+- ESPARSER is a bitstream parser that outputs to a VIFIFO. Further VDEC blocks
+then feed from this VIFIFO.
+- VDEC_1 can decode MPEG-1, MPEG-2, MPEG-4 part 2, H.263, H.264.
+- VDEC_2 is used as a helper for corner cases like H.264 4K on older SoCs.
+It is not handled by this driver.
+- VDEC_HCODEC is the H.264 encoding block. It is not handled by this driver.
+- VDEC_HEVC can decode HEVC and VP9.
+
+Device Tree Bindings:
+---------------------
+
+VDEC: Video Decoder
+--------------------------
+
+Required properties:
+- compatible: value should be different for each SoC family as :
+ - GXBB (S905) : "amlogic,gxbb-vdec"
+ - GXL (S905X, S905D) : "amlogic,gxl-vdec"
+ - GXM (S912) : "amlogic,gxm-vdec"
+- reg: base address and size of he following memory-mapped regions :
+ - dos
+ - esparser
+- reg-names: should contain the names of the previous memory regions
+- interrupts: should contain the vdec and esparser IRQs.
+- amlogic,ao-sysctrl: should point to the AOBUS sysctrl node
+- amlogic,canvas: should point to a canvas provider node
+- clocks: should contain the following clocks :
+ - dos_parser
+ - dos
+ - vdec_1
+ - vdec_hevc
+- clock-names: should contain the names of the previous clocks
+- resets: should contain the parser reset.
+- reset-names: should be "esparser".
+
+Example:
+
+vdec: video-decoder@c8820000 {
+ compatible = "amlogic,gxbb-vdec";
+ reg = <0x0 0xc8820000 0x0 0x10000>,
+ <0x0 0xc110a580 0x0 0xe4>;
+ reg-names = "dos", "esparser";
+
+ interrupts = <GIC_SPI 44 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 32 IRQ_TYPE_EDGE_RISING>;
+
+ amlogic,ao-sysctrl = <&sysctrl_AO>;
+ amlogic,canvas = <&canvas>;
+
+ clocks = <&clkc CLKID_DOS_PARSER>,
+ <&clkc CLKID_DOS>,
+ <&clkc CLKID_VDEC_1>,
+ <&clkc CLKID_VDEC_HEVC>;
+ clock-names = "dos_parser", "dos", "vdec_1", "vdec_hevc";
+
+ resets = <&reset RESET_PARSER>;
+ reset-names = "esparser";
+};
--
2.18.0


2018-08-31 09:02:06

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH 0/4] Add Amlogic video decoder driver

On 08/31/2018 10:52 AM, Maxime Jourdan wrote:
> Hi everyone,
>
> This patch series adds support for the Amlogic video decoder,
> as well as the corresponding dt bindings for GXBB/GXL/GXM chips.
>
> It features decoding for the following formats:
> - MPEG 1
> - MPEG 2
>
> The following formats will be added in future patches:
> - MJPEG
> - MPEG 4 (incl. Xvid, H.263)
> - H.264
> - HEVC (incl. 10-bit)
>
> The following formats' development has still not started, but they are
> supported by the hardware:
> - VC1
> - VP9
>
> The code was made in such a way to allow easy inclusion of those formats
> in the future.
>
> The decoder is single instance.
>
> Files:
> - vdec.c handles the V4L2 M2M logic
> - esparser.c manages the hardware bitstream parser
> - vdec_helpers.c provides helpers to DONE the dst buffers as well as
> various common code used by the codecs
> - vdec_1.c manages the VDEC_1 block of the vdec IP
> - codec_mpeg12.c enables decoding for MPEG 1/2.
> - vdec_platform.c links codec units with vdec units
> (e.g vdec_1 with codec_mpeg12) and lists all the available
> src/dst formats and requirements (max width/height, etc.),
> per compatible chip.
>
> Firmwares are necessary to run the vdec. They can currently be found at:
> https://github.com/chewitt/meson-firmware
>
> It was tested primarily with ffmpeg's v4l2-m2m implementation. For instance:
> $ ffmpeg -c:v mpeg2_v4l2m2m -i sample_mpeg2.mkv -f null -
>
> Note: This patch series depends on
> "[PATCH v3 0/3] soc: amlogic: add meson-canvas"
> https://patchwork.kernel.org/cover/10573763/
>
> The v4l2-compliance results are available below the patch diff.
>
> Maxime Jourdan (4):
> dt-bindings: media: add Amlogic Video Decoder Bindings
> media: meson: add v4l2 m2m video decoder driver
> ARM64: dts: meson-gx: add vdec entry
> ARM64: dts: meson: add vdec entries
>
> .../bindings/media/amlogic,vdec.txt | 63 ++
> arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 13 +
> arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 11 +
> arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 11 +
> arch/arm64/boot/dts/amlogic/meson-gxm.dtsi | 4 +
> drivers/media/platform/Kconfig | 10 +
> drivers/media/platform/meson/Makefile | 1 +
> drivers/media/platform/meson/vdec/Makefile | 8 +
> .../media/platform/meson/vdec/codec_mpeg12.c | 169 +++
> .../media/platform/meson/vdec/codec_mpeg12.h | 13 +
> drivers/media/platform/meson/vdec/dos_regs.h | 97 ++
> drivers/media/platform/meson/vdec/esparser.c | 367 +++++++
> drivers/media/platform/meson/vdec/esparser.h | 27 +
> drivers/media/platform/meson/vdec/vdec.c | 987 ++++++++++++++++++
> drivers/media/platform/meson/vdec/vdec.h | 233 +++++
> drivers/media/platform/meson/vdec/vdec_1.c | 227 ++++
> drivers/media/platform/meson/vdec/vdec_1.h | 13 +
> .../media/platform/meson/vdec/vdec_helpers.c | 353 +++++++
> .../media/platform/meson/vdec/vdec_helpers.h | 44 +
> .../media/platform/meson/vdec/vdec_platform.c | 100 ++
> .../media/platform/meson/vdec/vdec_platform.h | 29 +
> 21 files changed, 2780 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/amlogic,vdec.txt
> create mode 100644 drivers/media/platform/meson/vdec/Makefile
> create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c
> create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h
> create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h
> create mode 100644 drivers/media/platform/meson/vdec/esparser.c
> create mode 100644 drivers/media/platform/meson/vdec/esparser.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h
>
> root@libretech-cc:~/v4l-utils# v4l2-compliance -d /dev/video0
> v4l2-compliance SHA: not available, 64 bits

I see that there is no SHA, so you probably didn't compile this from a
checked-out git repo. Please do so, since without a SHA I don't know
whether or not you used the latest v4l2-compliance version.

Regards,

Hans

>
> Compliance test for device /dev/video0:
>
> Driver Info:
> Driver name : meson-vdec
> Card type : Amlogic Video Decoder
> Bus info : platform:meson-vdec
> Driver version : 4.18.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
>
> Required ioctls:
> test VIDIOC_QUERYCAP: OK
>
> Allow for multiple opens:
> test second /dev/video0 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 (Not Supported)
> test VIDIOC_QUERYCTRL: OK (Not Supported)
> test VIDIOC_G/S_CTRL: OK (Not Supported)
> test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
> test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
> test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> Standard Controls: 0 Private Controls: 0
>
> Format ioctls:
> test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> test VIDIOC_G/S_PARM: OK (Not Supported)
> 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
>
> Buffer ioctls:
> test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> test VIDIOC_EXPBUF: OK
>
> Total: 43, Succeeded: 43, Failed: 0, Warnings: 0
>


2018-08-31 09:10:39

by Maxime Jourdan

[permalink] [raw]
Subject: Re: [PATCH 0/4] Add Amlogic video decoder driver

2018-08-31 11:00 GMT+02:00 Hans Verkuil <[email protected]>:
>
> On 08/31/2018 10:52 AM, Maxime Jourdan wrote:
> > Hi everyone,
> >
> > This patch series adds support for the Amlogic video decoder,
> > as well as the corresponding dt bindings for GXBB/GXL/GXM chips.
> >
> > It features decoding for the following formats:
> > - MPEG 1
> > - MPEG 2
> >
> > The following formats will be added in future patches:
> > - MJPEG
> > - MPEG 4 (incl. Xvid, H.263)
> > - H.264
> > - HEVC (incl. 10-bit)
> >
> > The following formats' development has still not started, but they are
> > supported by the hardware:
> > - VC1
> > - VP9
> >
> > The code was made in such a way to allow easy inclusion of those formats
> > in the future.
> >
> > The decoder is single instance.
> >
> > Files:
> > - vdec.c handles the V4L2 M2M logic
> > - esparser.c manages the hardware bitstream parser
> > - vdec_helpers.c provides helpers to DONE the dst buffers as well as
> > various common code used by the codecs
> > - vdec_1.c manages the VDEC_1 block of the vdec IP
> > - codec_mpeg12.c enables decoding for MPEG 1/2.
> > - vdec_platform.c links codec units with vdec units
> > (e.g vdec_1 with codec_mpeg12) and lists all the available
> > src/dst formats and requirements (max width/height, etc.),
> > per compatible chip.
> >
> > Firmwares are necessary to run the vdec. They can currently be found at:
> > https://github.com/chewitt/meson-firmware
> >
> > It was tested primarily with ffmpeg's v4l2-m2m implementation. For instance:
> > $ ffmpeg -c:v mpeg2_v4l2m2m -i sample_mpeg2.mkv -f null -
> >
> > Note: This patch series depends on
> > "[PATCH v3 0/3] soc: amlogic: add meson-canvas"
> > https://patchwork.kernel.org/cover/10573763/
> >
> > The v4l2-compliance results are available below the patch diff.
> >
> > Maxime Jourdan (4):
> > dt-bindings: media: add Amlogic Video Decoder Bindings
> > media: meson: add v4l2 m2m video decoder driver
> > ARM64: dts: meson-gx: add vdec entry
> > ARM64: dts: meson: add vdec entries
> >
> > .../bindings/media/amlogic,vdec.txt | 63 ++
> > arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 13 +
> > arch/arm64/boot/dts/amlogic/meson-gxbb.dtsi | 11 +
> > arch/arm64/boot/dts/amlogic/meson-gxl.dtsi | 11 +
> > arch/arm64/boot/dts/amlogic/meson-gxm.dtsi | 4 +
> > drivers/media/platform/Kconfig | 10 +
> > drivers/media/platform/meson/Makefile | 1 +
> > drivers/media/platform/meson/vdec/Makefile | 8 +
> > .../media/platform/meson/vdec/codec_mpeg12.c | 169 +++
> > .../media/platform/meson/vdec/codec_mpeg12.h | 13 +
> > drivers/media/platform/meson/vdec/dos_regs.h | 97 ++
> > drivers/media/platform/meson/vdec/esparser.c | 367 +++++++
> > drivers/media/platform/meson/vdec/esparser.h | 27 +
> > drivers/media/platform/meson/vdec/vdec.c | 987 ++++++++++++++++++
> > drivers/media/platform/meson/vdec/vdec.h | 233 +++++
> > drivers/media/platform/meson/vdec/vdec_1.c | 227 ++++
> > drivers/media/platform/meson/vdec/vdec_1.h | 13 +
> > .../media/platform/meson/vdec/vdec_helpers.c | 353 +++++++
> > .../media/platform/meson/vdec/vdec_helpers.h | 44 +
> > .../media/platform/meson/vdec/vdec_platform.c | 100 ++
> > .../media/platform/meson/vdec/vdec_platform.h | 29 +
> > 21 files changed, 2780 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/media/amlogic,vdec.txt
> > create mode 100644 drivers/media/platform/meson/vdec/Makefile
> > create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c
> > create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h
> > create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h
> > create mode 100644 drivers/media/platform/meson/vdec/esparser.c
> > create mode 100644 drivers/media/platform/meson/vdec/esparser.h
> > create mode 100644 drivers/media/platform/meson/vdec/vdec.c
> > create mode 100644 drivers/media/platform/meson/vdec/vdec.h
> > create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c
> > create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h
> > create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c
> > create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h
> > create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c
> > create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h
> >
> > root@libretech-cc:~/v4l-utils# v4l2-compliance -d /dev/video0
> > v4l2-compliance SHA: not available, 64 bits
>
> I see that there is no SHA, so you probably didn't compile this from a
> checked-out git repo. Please do so, since without a SHA I don't know
> whether or not you used the latest v4l2-compliance version.

Noted, FWIW the last v4l-utils commit for this run is
e37fbf50a28c1a1cfe9e00a60542bc14192a87ba (Aug. 27).

I'll make sure to compile inside the git repo for the next submission.

Regards,
Maxime

> Regards,
>
> Hans
>
> >
> > Compliance test for device /dev/video0:
> >
> > Driver Info:
> > Driver name : meson-vdec
> > Card type : Amlogic Video Decoder
> > Bus info : platform:meson-vdec
> > Driver version : 4.18.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
> >
> > Required ioctls:
> > test VIDIOC_QUERYCAP: OK
> >
> > Allow for multiple opens:
> > test second /dev/video0 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 (Not Supported)
> > test VIDIOC_QUERYCTRL: OK (Not Supported)
> > test VIDIOC_G/S_CTRL: OK (Not Supported)
> > test VIDIOC_G/S/TRY_EXT_CTRLS: OK (Not Supported)
> > test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK (Not Supported)
> > test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
> > Standard Controls: 0 Private Controls: 0
> >
> > Format ioctls:
> > test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
> > test VIDIOC_G/S_PARM: OK (Not Supported)
> > 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
> >
> > Buffer ioctls:
> > test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK
> > test VIDIOC_EXPBUF: OK
> >
> > Total: 43, Succeeded: 43, Failed: 0, Warnings: 0
> >
>

2018-08-31 09:32:17

by Maxime Jourdan

[permalink] [raw]
Subject: [PATCH 3/4] ARM64: dts: meson-gx: add vdec entry

Add the video decoder dts entry

Signed-off-by: Maxime Jourdan <[email protected]>
---
arch/arm64/boot/dts/amlogic/meson-gx.dtsi | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
index 737b741df035..d9e0fea71dbe 100644
--- a/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-gx.dtsi
@@ -410,6 +410,19 @@
};
};

+ vdec: video-decoder@c8820000 {
+ compatible = "amlogic,gx-vdec";
+ reg = <0x0 0xc8820000 0x0 0x10000>,
+ <0x0 0xc110a580 0x0 0xe4>;
+ reg-names = "dos", "esparser";
+
+ interrupts = <GIC_SPI 44 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 32 IRQ_TYPE_EDGE_RISING>;
+
+ amlogic,ao-sysctrl = <&sysctrl_AO>;
+ amlogic,canvas = <&canvas>;
+ };
+
periphs: periphs@c8834000 {
compatible = "simple-bus";
reg = <0x0 0xc8834000 0x0 0x2000>;
--
2.18.0


2018-08-31 22:40:55

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 2/4] media: meson: add v4l2 m2m video decoder driver

Hi Maxime,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on linuxtv-media/master]
[also build test ERROR on v4.19-rc1 next-20180831]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url: https://github.com/0day-ci/linux/commits/Maxime-Jourdan/Add-Amlogic-video-decoder-driver/20180901-033653
base: git://linuxtv.org/media_tree.git master
config: i386-allmodconfig (attached as .config)
compiler: gcc-7 (Debian 7.3.0-16) 7.3.0
reproduce:
# save the attached .config to linux build tree
make ARCH=i386

All errors (new ones prefixed by >>):

In file included from drivers/media/platform/meson/vdec/esparser.h:10:0,
from drivers/media/platform/meson/vdec/esparser.c:21:
>> drivers/media/platform/meson/vdec/vdec.h:18:10: fatal error: linux/soc/amlogic/meson-canvas.h: No such file or directory
#include <linux/soc/amlogic/meson-canvas.h>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

vim +18 drivers/media/platform/meson/vdec/vdec.h

12
13 #include <linux/regmap.h>
14 #include <linux/list.h>
15 #include <media/videobuf2-v4l2.h>
16 #include <media/v4l2-ctrls.h>
17 #include <media/v4l2-device.h>
> 18 #include <linux/soc/amlogic/meson-canvas.h>
19

---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation


Attachments:
(No filename) (1.50 kB)
.config.gz (63.62 kB)
Download all attachments

2018-09-03 11:19:44

by Hans Verkuil

[permalink] [raw]
Subject: Re: [PATCH 2/4] media: meson: add v4l2 m2m video decoder driver

Hi Maxime,

Thank you for this patch series, nice to see amlogic becoming supported as well!

On 08/31/2018 10:52 AM, Maxime Jourdan wrote:
> Amlogic SoCs feature a powerful video decoder unit able to
> decode many formats, with a performance of usually up to 4k60.
>
> This is a driver for this IP that is based around the v4l2 m2m framework.
>
> It features decoding for:
> - MPEG 1
> - MPEG 2
>
> Supported SoCs are: GXBB (S905), GXL (S905X/W/D), GXM (S912)
>
> There is also a hardware bitstream parser (ESPARSER) that is handled here.
>
> Signed-off-by: Maxime Jourdan <[email protected]>
> ---
> drivers/media/platform/Kconfig | 10 +
> drivers/media/platform/meson/Makefile | 1 +
> drivers/media/platform/meson/vdec/Makefile | 8 +
> .../media/platform/meson/vdec/codec_mpeg12.c | 170 +++
> .../media/platform/meson/vdec/codec_mpeg12.h | 14 +
> drivers/media/platform/meson/vdec/dos_regs.h | 98 ++
> drivers/media/platform/meson/vdec/esparser.c | 368 +++++++
> drivers/media/platform/meson/vdec/esparser.h | 28 +
> drivers/media/platform/meson/vdec/vdec.c | 988 ++++++++++++++++++
> drivers/media/platform/meson/vdec/vdec.h | 234 +++++
> drivers/media/platform/meson/vdec/vdec_1.c | 228 ++++
> drivers/media/platform/meson/vdec/vdec_1.h | 14 +
> .../media/platform/meson/vdec/vdec_helpers.c | 354 +++++++
> .../media/platform/meson/vdec/vdec_helpers.h | 45 +
> .../media/platform/meson/vdec/vdec_platform.c | 101 ++
> .../media/platform/meson/vdec/vdec_platform.h | 30 +

Missing MAINTAINERS file update.

> 16 files changed, 2691 insertions(+)
> create mode 100644 drivers/media/platform/meson/vdec/Makefile
> create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c
> create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h
> create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h
> create mode 100644 drivers/media/platform/meson/vdec/esparser.c
> create mode 100644 drivers/media/platform/meson/vdec/esparser.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h
> create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c
> create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 2728376b04b5..1c33d95dd92f 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -482,6 +482,16 @@ config VIDEO_QCOM_VENUS
> on various Qualcomm SoCs.
> To compile this driver as a module choose m here.
>
> +config VIDEO_MESON_VDEC
> + tristate "Amlogic video decoder driver"
> + depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA
> + depends on (ARCH_MESON) || COMPILE_TEST

Why is ARCH_MESON between parenthesis?

> + select VIDEOBUF2_DMA_CONTIG
> + select V4L2_MEM2MEM_DEV
> + select MESON_CANVAS
> + help
> + Support for the video decoder found in gxbb/gxl/gxm chips.
> +
> endif # V4L_MEM2MEM_DRIVERS
>
> # TI VIDEO PORT Helper Modules
> diff --git a/drivers/media/platform/meson/Makefile b/drivers/media/platform/meson/Makefile
> index 597beb8f34d1..f7c6e1031f25 100644
> --- a/drivers/media/platform/meson/Makefile
> +++ b/drivers/media/platform/meson/Makefile
> @@ -1 +1,2 @@
> obj-$(CONFIG_VIDEO_MESON_AO_CEC) += ao-cec.o
> +obj-$(CONFIG_VIDEO_MESON_VDEC) += vdec/
> diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile
> new file mode 100644
> index 000000000000..6bea129084b7
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Makefile for Amlogic meson video decoder driver
> +
> +meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o
> +meson-vdec-objs += vdec_1.o
> +meson-vdec-objs += codec_mpeg12.o
> +
> +obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o
> diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.c b/drivers/media/platform/meson/vdec/codec_mpeg12.c
> new file mode 100644
> index 000000000000..18709319cff7
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/codec_mpeg12.c
> @@ -0,0 +1,170 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#include <media/v4l2-mem2mem.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "vdec_helpers.h"
> +#include "dos_regs.h"
> +
> +#define SIZE_WORKSPACE SZ_128K
> +/* Offset substracted by the firmware from the workspace paddr */
> +#define WORKSPACE_OFFSET (5 * SZ_1K)
> +
> +/* map firmware registers to known MPEG1/2 functions */
> +#define MREG_SEQ_INFO AV_SCRATCH_4
> +#define MREG_PIC_INFO AV_SCRATCH_5
> +#define MREG_PIC_WIDTH AV_SCRATCH_6
> +#define MREG_PIC_HEIGHT AV_SCRATCH_7
> +#define MREG_BUFFERIN AV_SCRATCH_8
> +#define MREG_BUFFEROUT AV_SCRATCH_9
> +#define MREG_CMD AV_SCRATCH_A
> +#define MREG_CO_MV_START AV_SCRATCH_B
> +#define MREG_ERROR_COUNT AV_SCRATCH_C
> +#define MREG_FRAME_OFFSET AV_SCRATCH_D
> +#define MREG_WAIT_BUFFER AV_SCRATCH_E
> +#define MREG_FATAL_ERROR AV_SCRATCH_F
> +
> +#define PICINFO_PROG 0x00008000
> +#define PICINFO_TOP_FIRST 0x00002000
> +
> +struct codec_mpeg12 {
> + /* Buffer for the MPEG1/2 Workspace */
> + void *workspace_vaddr;
> + dma_addr_t workspace_paddr;
> +};
> +
> +static int codec_mpeg12_can_recycle(struct amvdec_core *core)
> +{
> + return !amvdec_read_dos(core, MREG_BUFFERIN);
> +}
> +
> +static void codec_mpeg12_recycle(struct amvdec_core *core, u32 buf_idx)
> +{
> + amvdec_write_dos(core, MREG_BUFFERIN, buf_idx + 1);
> +}
> +
> +static int codec_mpeg12_start(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> + struct codec_mpeg12 *mpeg12 = sess->priv;
> + int ret;
> +
> + mpeg12 = kzalloc(sizeof(*mpeg12), GFP_KERNEL);
> + if (!mpeg12)
> + return -ENOMEM;
> +
> + /* Allocate some memory for the MPEG1/2 decoder's state */
> + mpeg12->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE,
> + &mpeg12->workspace_paddr,
> + GFP_KERNEL);
> + if (!mpeg12->workspace_vaddr) {
> + dev_err(core->dev, "Failed to request MPEG 1/2 Workspace\n");
> + ret = -ENOMEM;
> + goto free_mpeg12;
> + }
> +
> + ret = amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_0, 0 },
> + (u32[]){ 8, 0 });
> + if (ret)
> + goto free_workspace;
> +
> + amvdec_write_dos(core, POWER_CTL_VLD, BIT(4));
> + amvdec_write_dos(core, MREG_CO_MV_START,
> + mpeg12->workspace_paddr + WORKSPACE_OFFSET);
> +
> + amvdec_write_dos(core, MPEG1_2_REG, 0);
> + amvdec_write_dos(core, PSCALE_CTRL, 0);
> + amvdec_write_dos(core, PIC_HEAD_INFO, 0x380);
> + amvdec_write_dos(core, M4_CONTROL_REG, 0);
> + amvdec_write_dos(core, MREG_BUFFERIN, 0);
> + amvdec_write_dos(core, MREG_BUFFEROUT, 0);
> + amvdec_write_dos(core, MREG_CMD, (sess->width << 16) | sess->height);
> + amvdec_write_dos(core, MREG_ERROR_COUNT, 0);
> + amvdec_write_dos(core, MREG_FATAL_ERROR, 0);
> + amvdec_write_dos(core, MREG_WAIT_BUFFER, 0);
> +
> + sess->keyframe_found = 1;
> + sess->priv = mpeg12;
> +
> + return 0;
> +
> +free_workspace:
> + dma_free_coherent(core->dev, SIZE_WORKSPACE, mpeg12->workspace_vaddr,
> + mpeg12->workspace_paddr);
> +free_mpeg12:
> + kfree(mpeg12);
> +
> + return ret;
> +}
> +
> +static int codec_mpeg12_stop(struct amvdec_session *sess)
> +{
> + struct codec_mpeg12 *mpeg12 = sess->priv;
> + struct amvdec_core *core = sess->core;
> +
> + if (mpeg12->workspace_vaddr)
> + dma_free_coherent(core->dev, SIZE_WORKSPACE,
> + mpeg12->workspace_vaddr,
> + mpeg12->workspace_paddr);
> +
> + return 0;
> +}
> +
> +static irqreturn_t codec_mpeg12_threaded_isr(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> + u32 reg;
> + u32 pic_info;
> + u32 is_progressive;
> + u32 buffer_index;
> + u32 field = V4L2_FIELD_NONE;
> +
> + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1);
> + reg = amvdec_read_dos(core, MREG_FATAL_ERROR);
> + if (reg == 1) {
> + dev_err(core->dev, "MPEG1/2 fatal error\n");
> + amvdec_abort(sess);
> + return IRQ_HANDLED;
> + }
> +
> + reg = amvdec_read_dos(core, MREG_BUFFEROUT);
> + if (!reg)
> + return IRQ_HANDLED;
> +
> + /* Unclear what this means */
> + if ((reg & GENMASK(23, 17)) == GENMASK(23, 17))
> + goto end;
> +
> + pic_info = amvdec_read_dos(core, MREG_PIC_INFO);
> + is_progressive = pic_info & PICINFO_PROG;
> +
> + if (!is_progressive)
> + field = (pic_info & PICINFO_TOP_FIRST) ?
> + V4L2_FIELD_INTERLACED_TB :
> + V4L2_FIELD_INTERLACED_BT;
> +
> + buffer_index = ((reg & 0xf) - 1) & 7;
> + amvdec_dst_buf_done_idx(sess, buffer_index, field);
> +
> +end:
> + amvdec_write_dos(core, MREG_BUFFEROUT, 0);
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t codec_mpeg12_isr(struct amvdec_session *sess)
> +{
> + return IRQ_WAKE_THREAD;
> +}
> +
> +struct amvdec_codec_ops codec_mpeg12_ops = {
> + .start = codec_mpeg12_start,
> + .stop = codec_mpeg12_stop,
> + .isr = codec_mpeg12_isr,
> + .threaded_isr = codec_mpeg12_threaded_isr,
> + .can_recycle = codec_mpeg12_can_recycle,
> + .recycle = codec_mpeg12_recycle,
> +};
> diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.h b/drivers/media/platform/meson/vdec/codec_mpeg12.h
> new file mode 100644
> index 000000000000..43cab5f39ca0
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/codec_mpeg12.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_CODEC_MPEG12_H_
> +#define __MESON_VDEC_CODEC_MPEG12_H_
> +
> +#include "vdec.h"
> +
> +extern struct amvdec_codec_ops codec_mpeg12_ops;
> +
> +#endif
> diff --git a/drivers/media/platform/meson/vdec/dos_regs.h b/drivers/media/platform/meson/vdec/dos_regs.h
> new file mode 100644
> index 000000000000..abd810542dbb
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/dos_regs.h
> @@ -0,0 +1,98 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_DOS_REGS_H_
> +#define __MESON_VDEC_DOS_REGS_H_
> +
> +/* DOS registers */
> +#define VDEC_ASSIST_AMR1_INT8 0x00b4
> +
> +#define ASSIST_MBOX1_CLR_REG 0x01d4
> +#define ASSIST_MBOX1_MASK 0x01d8
> +
> +#define MPSR 0x0c04
> +#define MCPU_INTR_MSK 0x0c10
> +#define CPSR 0x0c84
> +
> +#define IMEM_DMA_CTRL 0x0d00
> +#define IMEM_DMA_ADR 0x0d04
> +#define IMEM_DMA_COUNT 0x0d08
> +#define LMEM_DMA_CTRL 0x0d40
> +
> +#define MC_STATUS0 0x2424
> +#define MC_CTRL1 0x242c
> +
> +#define PSCALE_RST 0x2440
> +#define PSCALE_CTRL 0x2444
> +#define PSCALE_BMEM_ADDR 0x247c
> +#define PSCALE_BMEM_DAT 0x2480
> +
> +#define DBLK_CTRL 0x2544
> +#define DBLK_STATUS 0x254c
> +
> +#define GCLK_EN 0x260c
> +#define MDEC_PIC_DC_CTRL 0x2638
> +#define MDEC_PIC_DC_STATUS 0x263c
> +#define ANC0_CANVAS_ADDR 0x2640
> +#define MDEC_PIC_DC_THRESH 0x26e0
> +
> +/* Firmware interface registers */
> +#define AV_SCRATCH_0 0x2700
> +#define AV_SCRATCH_1 0x2704
> +#define AV_SCRATCH_2 0x2708
> +#define AV_SCRATCH_3 0x270c
> +#define AV_SCRATCH_4 0x2710
> +#define AV_SCRATCH_5 0x2714
> +#define AV_SCRATCH_6 0x2718
> +#define AV_SCRATCH_7 0x271c
> +#define AV_SCRATCH_8 0x2720
> +#define AV_SCRATCH_9 0x2724
> +#define AV_SCRATCH_A 0x2728
> +#define AV_SCRATCH_B 0x272c
> +#define AV_SCRATCH_C 0x2730
> +#define AV_SCRATCH_D 0x2734
> +#define AV_SCRATCH_E 0x2738
> +#define AV_SCRATCH_F 0x273c
> +#define AV_SCRATCH_G 0x2740
> +#define AV_SCRATCH_H 0x2744
> +#define AV_SCRATCH_I 0x2748
> +#define AV_SCRATCH_J 0x274c
> +#define AV_SCRATCH_K 0x2750
> +#define AV_SCRATCH_L 0x2754
> +
> +#define MPEG1_2_REG 0x3004
> +#define PIC_HEAD_INFO 0x300c
> +#define POWER_CTL_VLD 0x3020
> +#define M4_CONTROL_REG 0x30a4
> +
> +/* Stream Buffer (stbuf) regs */
> +#define VLD_MEM_VIFIFO_START_PTR 0x3100
> +#define VLD_MEM_VIFIFO_CURR_PTR 0x3104
> +#define VLD_MEM_VIFIFO_END_PTR 0x3108
> +#define VLD_MEM_VIFIFO_CONTROL 0x3110
> + #define MEM_FIFO_CNT_BIT 16
> + #define MEM_FILL_ON_LEVEL BIT(10)
> + #define MEM_CTRL_EMPTY_EN BIT(2)
> + #define MEM_CTRL_FILL_EN BIT(1)
> +#define VLD_MEM_VIFIFO_WP 0x3114
> +#define VLD_MEM_VIFIFO_RP 0x3118
> +#define VLD_MEM_VIFIFO_LEVEL 0x311c
> +#define VLD_MEM_VIFIFO_BUF_CNTL 0x3120
> + #define MEM_BUFCTRL_MANUAL BIT(1)
> +#define VLD_MEM_VIFIFO_WRAP_COUNT 0x3144
> +
> +#define DCAC_DMA_CTRL 0x3848
> +
> +#define DOS_SW_RESET0 0xfc00
> +#define DOS_GCLK_EN0 0xfc04
> +#define DOS_GEN_CTRL0 0xfc08
> +#define DOS_MEM_PD_VDEC 0xfcc0
> +#define DOS_MEM_PD_HEVC 0xfccc
> +#define DOS_SW_RESET3 0xfcd0
> +#define DOS_GCLK_EN3 0xfcd4
> +#define DOS_VDEC_MCRCC_STALL_CTRL 0xfd00
> +
> +#endif
> diff --git a/drivers/media/platform/meson/vdec/esparser.c b/drivers/media/platform/meson/vdec/esparser.c
> new file mode 100644
> index 000000000000..098c7d76ad3f
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/esparser.c
> @@ -0,0 +1,368 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + *
> + * The Elementary Stream Parser is a HW bitstream parser.
> + * It reads bitstream buffers and feeds them to the VIFIFO
> + */
> +
> +#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/reset.h>
> +#include <media/videobuf2-dma-contig.h>
> +#include <media/v4l2-mem2mem.h>
> +
> +#include "dos_regs.h"
> +#include "esparser.h"
> +#include "vdec_helpers.h"
> +
> +/* PARSER REGS (CBUS) */
> +#define PARSER_CONTROL 0x00
> + #define ES_PACK_SIZE_BIT 8
> + #define ES_WRITE BIT(5)
> + #define ES_SEARCH BIT(1)
> + #define ES_PARSER_START BIT(0)
> +#define PARSER_FETCH_ADDR 0x4
> +#define PARSER_FETCH_CMD 0x8
> +#define PARSER_CONFIG 0x14
> + #define PS_CFG_MAX_FETCH_CYCLE_BIT 0
> + #define PS_CFG_STARTCODE_WID_24_BIT 10
> + #define PS_CFG_MAX_ES_WR_CYCLE_BIT 12
> + #define PS_CFG_PFIFO_EMPTY_CNT_BIT 16
> +#define PFIFO_WR_PTR 0x18
> +#define PFIFO_RD_PTR 0x1c
> +#define PARSER_SEARCH_PATTERN 0x24
> + #define ES_START_CODE_PATTERN 0x00000100
> +#define PARSER_SEARCH_MASK 0x28
> + #define ES_START_CODE_MASK 0xffffff00
> + #define FETCH_ENDIAN_BIT 27
> +#define PARSER_INT_ENABLE 0x2c
> + #define PARSER_INT_HOST_EN_BIT 8
> +#define PARSER_INT_STATUS 0x30
> + #define PARSER_INTSTAT_SC_FOUND 1
> +#define PARSER_ES_CONTROL 0x5c
> +#define PARSER_VIDEO_START_PTR 0x80
> +#define PARSER_VIDEO_END_PTR 0x84
> +#define PARSER_VIDEO_HOLE 0x90
> +
> +#define SEARCH_PATTERN_LEN 512
> +#define MIN_PACKET_SIZE (4 * SZ_1K)
> +
> +/* Buffer to send to the ESPARSER to signal End Of Stream.
> + * Credits to Endless Mobile.
> + */
> +#define EOS_TAIL_BUF_SIZE 1024
> +static const u8 eos_tail_data[EOS_TAIL_BUF_SIZE] = {
> + 0x00, 0x00, 0x00, 0x01, 0x06, 0x05, 0xff, 0xe4, 0xdc, 0x45, 0xe9, 0xbd,
> + 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef,
> + 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20,
> + 0x36, 0x37, 0x20, 0x72, 0x31, 0x31, 0x33, 0x30, 0x20, 0x38, 0x34, 0x37,
> + 0x35, 0x39, 0x37, 0x37, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34,
> + 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20,
> + 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79,
> + 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30,
> + 0x30, 0x39, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
> + 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e,
> + 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74,
> + 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
> + 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65,
> + 0x66, 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d,
> + 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73,
> + 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20,
> + 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65,
> + 0x3d, 0x36, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e,
> + 0x30, 0x3a, 0x30, 0x2e, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f,
> + 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e,
> + 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61,
> + 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69,
> + 0x73, 0x3d, 0x30, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30,
> + 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a,
> + 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x63, 0x68,
> + 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73,
> + 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
> + 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63,
> + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x6d, 0x62, 0x61, 0x66,
> + 0x66, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d,
> + 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30,
> + 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d,
> + 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d,
> + 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69,
> + 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x61, 0x74,
> + 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f,
> + 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69,
> + 0x6e, 0x3d, 0x31, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x35,
> + 0x31, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69,
> + 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30,
> + 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80,
> + 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0a, 0x9a, 0x74, 0xf4, 0x20,
> + 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x51, 0xe2, 0x44, 0xd4,
> + 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x32, 0xc8, 0x00, 0x00, 0x00, 0x01,
> + 0x65, 0x88, 0x80, 0x20, 0x00, 0x08, 0x7f, 0xea, 0x6a, 0xe2, 0x99, 0xb6,
> + 0x57, 0xae, 0x49, 0x30, 0xf5, 0xfe, 0x5e, 0x46, 0x0b, 0x72, 0x44, 0xc4,
> + 0xe1, 0xfc, 0x62, 0xda, 0xf1, 0xfb, 0xa2, 0xdb, 0xd6, 0xbe, 0x5c, 0xd7,
> + 0x24, 0xa3, 0xf5, 0xb9, 0x2f, 0x57, 0x16, 0x49, 0x75, 0x47, 0x77, 0x09,
> + 0x5c, 0xa1, 0xb4, 0xc3, 0x4f, 0x60, 0x2b, 0xb0, 0x0c, 0xc8, 0xd6, 0x66,
> + 0xba, 0x9b, 0x82, 0x29, 0x33, 0x92, 0x26, 0x99, 0x31, 0x1c, 0x7f, 0x9b
> +};
> +
> +static DECLARE_WAIT_QUEUE_HEAD(wq);
> +static int search_done;
> +
> +static irqreturn_t esparser_isr(int irq, void *dev)
> +{
> + int int_status;
> + struct amvdec_core *core = dev;
> +
> + int_status = amvdec_read_parser(core, PARSER_INT_STATUS);
> + amvdec_write_parser(core, PARSER_INT_STATUS, int_status);
> +
> + if (int_status & PARSER_INTSTAT_SC_FOUND) {
> + amvdec_write_parser(core, PFIFO_RD_PTR, 0);
> + amvdec_write_parser(core, PFIFO_WR_PTR, 0);
> + search_done = 1;
> + wake_up_interruptible(&wq);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +/* Pad the packet to at least 4KiB bytes otherwise the VDEC unit won't trigger
> + * ISRs.
> + * Also append a start code 000001ff at the end to trigger
> + * the ESPARSER interrupt.
> + */
> +static u32 esparser_pad_start_code(struct vb2_buffer *vb)
> +{
> + u32 payload_size = vb2_get_plane_payload(vb, 0);
> + u32 pad_size = 0;
> + u8 *vaddr = vb2_plane_vaddr(vb, 0) + payload_size;
> +
> + if (payload_size < MIN_PACKET_SIZE) {
> + pad_size = MIN_PACKET_SIZE - payload_size;
> + memset(vaddr, 0, pad_size);
> + }
> +
> + memset(vaddr + pad_size, 0, SEARCH_PATTERN_LEN);
> + vaddr[pad_size] = 0x00;
> + vaddr[pad_size + 1] = 0x00;
> + vaddr[pad_size + 2] = 0x01;
> + vaddr[pad_size + 3] = 0xff;
> +
> + return pad_size;
> +}
> +
> +static int
> +esparser_write_data(struct amvdec_core *core, dma_addr_t addr, u32 size)
> +{
> + amvdec_write_parser(core, PFIFO_RD_PTR, 0);
> + amvdec_write_parser(core, PFIFO_WR_PTR, 0);
> + amvdec_write_parser(core, PARSER_CONTROL,
> + ES_WRITE |
> + ES_PARSER_START |
> + ES_SEARCH |
> + (size << ES_PACK_SIZE_BIT));
> +
> + amvdec_write_parser(core, PARSER_FETCH_ADDR, addr);
> + amvdec_write_parser(core, PARSER_FETCH_CMD,
> + (7 << FETCH_ENDIAN_BIT) |
> + (size + SEARCH_PATTERN_LEN));
> +
> + search_done = 0;
> + return wait_event_interruptible_timeout(wq, search_done, (HZ / 5));
> +}
> +
> +static u32 esparser_vififo_get_free_space(struct amvdec_session *sess)
> +{
> + u32 vififo_usage;
> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
> + struct amvdec_core *core = sess->core;
> +
> + vififo_usage = vdec_ops->vififo_level(sess);
> + vififo_usage += amvdec_read_parser(core, PARSER_VIDEO_HOLE);
> + vififo_usage += (6 * SZ_1K);
> +
> + if (vififo_usage > sess->vififo_size) {
> + dev_warn(sess->core->dev,
> + "VIFIFO usage (%u) > VIFIFO size (%u)\n",
> + vififo_usage, sess->vififo_size);
> + return 0;
> + }
> +
> + return sess->vififo_size - vififo_usage;
> +}
> +
> +int esparser_queue_eos(struct amvdec_core *core)
> +{
> + struct device *dev = core->dev;
> + void *eos_vaddr;
> + dma_addr_t eos_paddr;
> + int ret;
> +
> + eos_vaddr = dma_alloc_coherent(dev,
> + EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN,
> + &eos_paddr, GFP_KERNEL);
> + if (!eos_vaddr)
> + return -ENOMEM;
> +
> + memset(eos_vaddr, 0, EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN);
> + memcpy(eos_vaddr, eos_tail_data, sizeof(eos_tail_data));
> + ret = esparser_write_data(core, eos_paddr, EOS_TAIL_BUF_SIZE);
> + dma_free_coherent(dev, EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN,
> + eos_vaddr, eos_paddr);
> +
> + return ret;
> +}
> +
> +static int
> +esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)
> +{
> + int ret;
> + struct vb2_buffer *vb = &vbuf->vb2_buf;
> + struct amvdec_core *core = sess->core;
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> + u32 num_dst_bufs = 0;
> + u32 payload_size = vb2_get_plane_payload(vb, 0);
> + dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0);
> + u32 pad_size;
> +
> + if (!payload_size) {
> + esparser_queue_eos(core);
> + return 0;
> + }
> +
> + if (codec_ops->num_pending_bufs)
> + num_dst_bufs = codec_ops->num_pending_bufs(sess);
> +
> + num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx);
> +
> + if (esparser_vififo_get_free_space(sess) < payload_size ||
> + atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs)
> + return -EAGAIN;
> +
> + v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf);
> + amvdec_add_ts_reorder(sess, vb->timestamp);
> + dev_dbg(core->dev, "esparser: Queuing ts = %llu ; pld_size = %u\n",
> + vb->timestamp, payload_size);
> +
> + pad_size = esparser_pad_start_code(vb);
> + ret = esparser_write_data(core, phy, payload_size + pad_size);
> +
> + if (ret > 0) {
> + /* We need to wait until we parse/decode the first keyframe.
> + * All buffers prior to the first keyframe must be dropped.
> + */
> + if (!sess->keyframe_found)
> + usleep_range(1000, 2000);
> +
> + if (sess->keyframe_found)
> + atomic_inc(&sess->esparser_queued_bufs);
> + else
> + amvdec_remove_ts(sess, vb->timestamp);
> +
> + vbuf->flags = 0;
> + vbuf->field = V4L2_FIELD_NONE;
> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
> + return 0;
> + }
> +
> + dev_warn(core->dev, "esparser: input parsing error\n");
> + amvdec_remove_ts(sess, vb->timestamp);
> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
> + amvdec_write_parser(core, PARSER_FETCH_CMD, 0);
> +
> + return 0;
> +}
> +
> +void esparser_queue_all_src(struct work_struct *work)
> +{
> + struct v4l2_m2m_buffer *buf, *n;
> + struct amvdec_session *sess =
> + container_of(work, struct amvdec_session, esparser_queue_work);
> +
> + mutex_lock(&sess->lock);
> + v4l2_m2m_for_each_src_buf_safe(sess->m2m_ctx, buf, n) {
> + if (esparser_queue(sess, &buf->vb) < 0)
> + break;
> +
> + /* Some codecs don't like having data queued in too fast */

This needs some more extensive explanation. Which codecs? Why is this is
problem? Why is 1 ms delay sufficient?

It's weird and unexpected, so that needs better documentation.

> + usleep_range(1000, 2000);
> + }
> + mutex_unlock(&sess->lock);
> +}
> +
> +int esparser_power_up(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
> +
> + reset_control_reset(core->esparser_reset);
> + amvdec_write_parser(core, PARSER_CONFIG,
> + (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) |
> + (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) |
> + (16 << PS_CFG_MAX_FETCH_CYCLE_BIT));
> +
> + amvdec_write_parser(core, PFIFO_RD_PTR, 0);
> + amvdec_write_parser(core, PFIFO_WR_PTR, 0);
> +
> + amvdec_write_parser(core, PARSER_SEARCH_PATTERN,
> + ES_START_CODE_PATTERN);
> + amvdec_write_parser(core, PARSER_SEARCH_MASK, ES_START_CODE_MASK);
> +
> + amvdec_write_parser(core, PARSER_CONFIG,
> + (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) |
> + (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) |
> + (16 << PS_CFG_MAX_FETCH_CYCLE_BIT) |
> + (2 << PS_CFG_STARTCODE_WID_24_BIT));
> +
> + amvdec_write_parser(core, PARSER_CONTROL,
> + (ES_SEARCH | ES_PARSER_START));
> +
> + amvdec_write_parser(core, PARSER_VIDEO_START_PTR, sess->vififo_paddr);
> + amvdec_write_parser(core, PARSER_VIDEO_END_PTR,
> + sess->vififo_paddr + sess->vififo_size - 8);
> + amvdec_write_parser(core, PARSER_ES_CONTROL,
> + amvdec_read_parser(core, PARSER_ES_CONTROL) & ~1);
> +
> + if (vdec_ops->conf_esparser)
> + vdec_ops->conf_esparser(sess);
> +
> + amvdec_write_parser(core, PARSER_INT_STATUS, 0xffff);
> + amvdec_write_parser(core, PARSER_INT_ENABLE,
> + BIT(PARSER_INT_HOST_EN_BIT));
> +
> + return 0;
> +}
> +
> +int esparser_init(struct platform_device *pdev, struct amvdec_core *core)
> +{
> + struct device *dev = &pdev->dev;
> + int ret;
> + int irq;
> +
> + irq = platform_get_irq(pdev, 1);
> + if (irq < 0) {
> + dev_err(dev, "Failed getting ESPARSER IRQ from dtb\n");
> + return irq;
> + }
> +
> + ret = devm_request_irq(dev, irq, esparser_isr, IRQF_SHARED,
> + "esparserirq", core);
> + if (ret) {
> + dev_err(dev, "Failed requesting ESPARSER IRQ\n");
> + return ret;
> + }
> +
> + core->esparser_reset =
> + devm_reset_control_get_exclusive(dev, "esparser");
> + if (IS_ERR(core->esparser_reset)) {
> + dev_err(dev, "Failed to get esparser_reset\n");
> + return PTR_ERR(core->esparser_reset);
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/media/platform/meson/vdec/esparser.h b/drivers/media/platform/meson/vdec/esparser.h
> new file mode 100644
> index 000000000000..22c2ac5c6d35
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/esparser.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_ESPARSER_H_
> +#define __MESON_VDEC_ESPARSER_H_
> +
> +#include "vdec.h"
> +
> +int esparser_init(struct platform_device *pdev, struct amvdec_core *core);
> +int esparser_power_up(struct amvdec_session *sess);
> +
> +/**
> + * esparser_queue_eos() - write End Of Stream sequence to the ESPARSER
> + *
> + * @core vdec core struct
> + */
> +int esparser_queue_eos(struct amvdec_core *core);
> +
> +/**
> + * esparser_queue_all_src() - work handler that writes as many src buffers
> + * as possible to the ESPARSER
> + */
> +void esparser_queue_all_src(struct work_struct *work);
> +
> +#endif
> diff --git a/drivers/media/platform/meson/vdec/vdec.c b/drivers/media/platform/meson/vdec/vdec.c
> new file mode 100644
> index 000000000000..32e1e2228297
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec.c
> @@ -0,0 +1,988 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#include <linux/of_device.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/slab.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-mem2mem.h>
> +#include <media/v4l2-dev.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "vdec.h"
> +#include "esparser.h"
> +#include "vdec_helpers.h"
> +
> +struct dummy_buf {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +};
> +
> +/* 16 MiB for parsed bitstream swap exchange */
> +#define SIZE_VIFIFO SZ_16M
> +
> +static u32 get_output_size(u32 width, u32 height)
> +{
> + return ALIGN(width * height, SZ_64K);
> +}
> +
> +u32 amvdec_get_output_size(struct amvdec_session *sess)
> +{
> + return get_output_size(sess->width, sess->height);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_get_output_size);
> +
> +static int vdec_codec_needs_recycle(struct amvdec_session *sess)
> +{
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> +
> + return codec_ops->can_recycle && codec_ops->recycle;
> +}
> +
> +static int vdec_recycle_thread(void *data)
> +{
> + struct amvdec_session *sess = data;
> + struct amvdec_core *core = sess->core;
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> + struct amvdec_buffer *tmp, *n;
> +
> + while (!kthread_should_stop()) {
> + mutex_lock(&sess->bufs_recycle_lock);
> + list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
> + if (!codec_ops->can_recycle(core))
> + break;
> +
> + codec_ops->recycle(core, tmp->vb->index);
> + dev_dbg(core->dev, "Buffer %d recycled\n",
> + tmp->vb->index);
> + list_del(&tmp->list);
> + kfree(tmp);
> + }
> + mutex_unlock(&sess->bufs_recycle_lock);
> +
> + usleep_range(5000, 10000);
> + }
> +
> + return 0;
> +}
> +
> +static int vdec_poweron(struct amvdec_session *sess)
> +{
> + int ret;
> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
> +
> + ret = clk_prepare_enable(sess->core->dos_parser_clk);
> + if (ret)
> + return ret;
> +
> + ret = clk_prepare_enable(sess->core->dos_clk);
> + if (ret)
> + goto disable_dos_parser;
> +
> + ret = vdec_ops->start(sess);
> + if (ret)
> + goto disable_dos;
> +
> + esparser_power_up(sess);
> +
> + return 0;
> +
> +disable_dos:
> + clk_disable_unprepare(sess->core->dos_clk);
> +disable_dos_parser:
> + clk_disable_unprepare(sess->core->dos_parser_clk);
> +
> + return ret;
> +}
> +
> +static void vdec_wait_inactive(struct amvdec_session *sess)
> +{
> + /* We consider 50ms with no IRQ to be inactive. */
> + while (time_is_after_jiffies64(sess->last_irq_jiffies +
> + msecs_to_jiffies(50)))
> + msleep(25);
> +}
> +
> +static void vdec_poweroff(struct amvdec_session *sess)
> +{
> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> +
> + vdec_wait_inactive(sess);
> + if (codec_ops->drain)
> + codec_ops->drain(sess);
> +
> + vdec_ops->stop(sess);
> + clk_disable_unprepare(sess->core->dos_clk);
> + clk_disable_unprepare(sess->core->dos_parser_clk);
> +}
> +
> +static void
> +vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb)
> +{
> + struct amvdec_buffer *new_buf;
> +
> + new_buf = kmalloc(sizeof(*new_buf), GFP_KERNEL);
> + new_buf->vb = vb;
> +
> + mutex_lock(&sess->bufs_recycle_lock);
> + list_add_tail(&new_buf->list, &sess->bufs_recycle);
> + mutex_unlock(&sess->bufs_recycle_lock);
> +}
> +
> +static void vdec_m2m_device_run(void *priv)
> +{
> + struct amvdec_session *sess = priv;
> +
> + schedule_work(&sess->esparser_queue_work);
> +}
> +
> +static void vdec_m2m_job_abort(void *priv)
> +{
> + struct amvdec_session *sess = priv;
> +
> + v4l2_m2m_job_finish(sess->m2m_dev, sess->m2m_ctx);
> +}
> +
> +static const struct v4l2_m2m_ops vdec_m2m_ops = {
> + .device_run = vdec_m2m_device_run,
> + .job_abort = vdec_m2m_job_abort,
> +};
> +
> +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 amvdec_session *sess = vb2_get_drv_priv(q);
> + struct amvdec_core *core = sess->core;
> + const struct amvdec_format *fmt_out = sess->fmt_out;
> + u32 pixfmt_cap = sess->pixfmt_cap;
> +
> + switch (q->type) {
> + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> + sizes[0] = amvdec_get_output_size(sess);
> + *num_planes = 1;
> + break;
> + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
> + if (pixfmt_cap == V4L2_PIX_FMT_NV12M) {
> + sizes[0] = amvdec_get_output_size(sess);
> + sizes[1] = amvdec_get_output_size(sess) / 2;
> + *num_planes = 2;
> + } else if (pixfmt_cap == V4L2_PIX_FMT_YUV420M) {
> + sizes[0] = amvdec_get_output_size(sess);
> + sizes[1] = amvdec_get_output_size(sess) / 4;
> + sizes[2] = amvdec_get_output_size(sess) / 4;
> + *num_planes = 3;
> + }
> + *num_buffers = min(max(*num_buffers, fmt_out->min_buffers),
> + fmt_out->max_buffers);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + mutex_lock(&core->lock);
> + if (core->cur_sess && core->cur_sess != sess) {
> + mutex_unlock(&core->lock);
> + return -EBUSY;
> + }
> +
> + core->cur_sess = sess;
> + mutex_unlock(&core->lock);
> +
> + return 0;
> +}
> +
> +static void vdec_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> + struct amvdec_session *sess = vb2_get_drv_priv(vb->vb2_queue);
> + struct v4l2_m2m_ctx *m2m_ctx = sess->m2m_ctx;
> +
> + mutex_lock(&sess->lock);
> + v4l2_m2m_buf_queue(m2m_ctx, vbuf);
> +
> + if (!sess->streamon_out || !sess->streamon_cap)
> + goto unlock;
> +
> + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
> + vdec_codec_needs_recycle(sess))
> + vdec_queue_recycle(sess, vb);
> +
> + schedule_work(&sess->esparser_queue_work);
> +unlock:
> + mutex_unlock(&sess->lock);
> +}
> +
> +static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> + struct amvdec_session *sess = vb2_get_drv_priv(q);
> + struct vb2_v4l2_buffer *buf;
> + int ret;
> +
> + mutex_lock(&sess->lock);
> +
> + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> + sess->streamon_out = 1;
> + else
> + sess->streamon_cap = 1;
> +
> + if (!sess->streamon_out || !sess->streamon_cap) {
> + mutex_unlock(&sess->lock);
> + return 0;
> + }
> +
> + sess->vififo_size = SIZE_VIFIFO;
> + sess->vififo_vaddr =
> + dma_alloc_coherent(sess->core->dev, sess->vififo_size,
> + &sess->vififo_paddr, GFP_KERNEL);
> + if (!sess->vififo_vaddr) {
> + dev_err(sess->core->dev, "Failed to request VIFIFO buffer\n");
> + ret = -ENOMEM;
> + goto bufs_done;
> + }
> +
> + sess->should_stop = 0;
> + sess->keyframe_found = 0;
> + atomic_set(&sess->esparser_queued_bufs, 0);
> + ret = vdec_poweron(sess);
> + if (ret)
> + goto vififo_free;
> +
> + sess->sequence_cap = 0;
> + if (vdec_codec_needs_recycle(sess))
> + sess->recycle_thread = kthread_run(vdec_recycle_thread, sess,
> + "vdec_recycle");
> + mutex_unlock(&sess->lock);
> +
> + return 0;
> +
> +vififo_free:
> + dma_free_coherent(sess->core->dev, sess->vififo_size,
> + sess->vififo_vaddr, sess->vififo_paddr);
> +bufs_done:
> + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
> + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
> +
> + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> + sess->streamon_out = 0;
> + else
> + sess->streamon_cap = 0;
> + mutex_unlock(&sess->lock);
> + return ret;
> +}
> +
> +static void vdec_free_canvas(struct amvdec_session *sess)
> +{
> + int i;
> +
> + for (i = 0; i < sess->canvas_num; ++i)
> + meson_canvas_free(sess->core->canvas, sess->canvas_alloc[i]);
> +
> + sess->canvas_num = 0;
> +}
> +
> +static void vdec_reset_timestamps(struct amvdec_session *sess)
> +{
> + struct amvdec_timestamp *tmp, *n;
> +
> + list_for_each_entry_safe(tmp, n, &sess->timestamps, list) {
> + list_del(&tmp->list);
> + kfree(tmp);
> + }
> +}
> +
> +static void vdec_reset_bufs_recycle(struct amvdec_session *sess)
> +{
> + struct amvdec_buffer *tmp, *n;
> +
> + list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
> + list_del(&tmp->list);
> + kfree(tmp);
> + }
> +}
> +
> +static void vdec_stop_streaming(struct vb2_queue *q)
> +{
> + struct amvdec_session *sess = vb2_get_drv_priv(q);
> + struct vb2_v4l2_buffer *buf;
> +
> + mutex_lock(&sess->lock);
> +
> + if (sess->streamon_out && sess->streamon_cap) {
> + if (vdec_codec_needs_recycle(sess))
> + kthread_stop(sess->recycle_thread);
> +
> + vdec_poweroff(sess);
> + vdec_free_canvas(sess);
> + dma_free_coherent(sess->core->dev, sess->vififo_size,
> + sess->vififo_vaddr, sess->vififo_paddr);
> + vdec_reset_timestamps(sess);
> + vdec_reset_bufs_recycle(sess);
> + kfree(sess->priv);
> + sess->priv = NULL;
> + }
> +
> + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
> +
> + sess->streamon_out = 0;
> + } else {
> + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
> +
> + sess->streamon_cap = 0;
> + }
> +
> + mutex_unlock(&sess->lock);
> +}
> +
> +static const struct vb2_ops vdec_vb2_ops = {
> + .queue_setup = vdec_queue_setup,
> + .start_streaming = vdec_start_streaming,
> + .stop_streaming = vdec_stop_streaming,
> + .buf_queue = vdec_vb2_buf_queue,

You need to add:

.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,

and set the lock field for the vb2_queues.

Otherwise a DQBUF ioctl can block other ioctls issued from another thread
if DQBUF does a blocking wait.

> +};
> +
> +static int
> +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
> +{
> + strlcpy(cap->driver, "meson-vdec", sizeof(cap->driver));
> + strlcpy(cap->card, "Amlogic Video Decoder", sizeof(cap->card));
> + strlcpy(cap->bus_info, "platform:meson-vdec", sizeof(cap->bus_info));
> +
> + return 0;
> +}
> +
> +static const struct amvdec_format *
> +find_format(const struct amvdec_format *fmts, u32 size, u32 pixfmt)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < size; i++) {
> + if (fmts[i].pixfmt == pixfmt)
> + return &fmts[i];
> + }
> +
> + return NULL;
> +}
> +
> +static unsigned int
> +vdec_supports_pixfmt_cap(const struct amvdec_format *fmt_out, u32 pixfmt_cap)
> +{
> + int i;
> +
> + for (i = 0; fmt_out->pixfmts_cap[i]; i++)
> + if (fmt_out->pixfmts_cap[i] == pixfmt_cap)
> + return 1;
> +
> + return 0;
> +}
> +
> +static const struct amvdec_format *
> +vdec_try_fmt_common(struct amvdec_session *sess, u32 size,
> + struct v4l2_format *f)
> +{
> + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
> + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt;
> + const struct amvdec_format *fmts = sess->core->platform->formats;
> + const struct amvdec_format *fmt_out;
> +
> + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved));
> + memset(pixmp->reserved, 0, sizeof(pixmp->reserved));
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> + fmt_out = find_format(fmts, size, pixmp->pixelformat);
> + if (!fmt_out) {
> + pixmp->pixelformat = V4L2_PIX_FMT_MPEG2;
> + fmt_out = find_format(fmts, size, pixmp->pixelformat);
> + pixmp->width = 1280;
> + pixmp->height = 720;

Why set the width and height here? You normally keep that as-is.

> + }
> +
> + pfmt[0].sizeimage =
> + get_output_size(pixmp->width, pixmp->height);
> + pfmt[0].bytesperline = 0;
> + pixmp->num_planes = 1;
> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> + fmt_out = sess->fmt_out;
> + if (!vdec_supports_pixfmt_cap(fmt_out, pixmp->pixelformat))
> + pixmp->pixelformat = fmt_out->pixfmts_cap[0];
> +
> + memset(pfmt[1].reserved, 0, sizeof(pfmt[1].reserved));
> + if (pixmp->pixelformat == V4L2_PIX_FMT_NV12M) {
> + pfmt[0].sizeimage =
> + get_output_size(pixmp->width, pixmp->height);
> + pfmt[0].bytesperline = ALIGN(pixmp->width, 64);
> +
> + pfmt[1].sizeimage =
> + get_output_size(pixmp->width, pixmp->height) / 2;
> + pfmt[1].bytesperline = ALIGN(pixmp->width, 64);
> + pixmp->num_planes = 2;
> + } else if (pixmp->pixelformat == V4L2_PIX_FMT_YUV420M) {
> + pfmt[0].sizeimage =
> + get_output_size(pixmp->width, pixmp->height);
> + pfmt[0].bytesperline = ALIGN(pixmp->width, 64);
> +
> + pfmt[1].sizeimage =
> + get_output_size(pixmp->width, pixmp->height) / 4;
> + pfmt[1].bytesperline = ALIGN(pixmp->width, 64) / 2;
> +
> + pfmt[2].sizeimage =
> + get_output_size(pixmp->width, pixmp->height) / 4;
> + pfmt[2].bytesperline = ALIGN(pixmp->width, 64) / 2;
> + pixmp->num_planes = 3;
> + }
> + } else {
> + return NULL;
> + }
> +
> + pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width);
> + pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height);
> +
> + if (pixmp->field == V4L2_FIELD_ANY)
> + pixmp->field = V4L2_FIELD_NONE;
> +
> + pixmp->flags = 0;

Shouldn't be necessary, the core takes care of that if I remember correctly.

> +
> + return fmt_out;
> +}
> +
> +static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> +
> + vdec_try_fmt_common(sess, sess->core->platform->num_formats, f);
> +
> + return 0;
> +}
> +
> +static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> + pixmp->pixelformat = sess->pixfmt_cap;
> + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> + pixmp->pixelformat = sess->fmt_out->pixfmt;
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> + pixmp->width = sess->width;
> + pixmp->height = sess->height;
> + pixmp->colorspace = sess->colorspace;
> + pixmp->ycbcr_enc = sess->ycbcr_enc;
> + pixmp->quantization = sess->quantization;
> + pixmp->xfer_func = sess->xfer_func;
> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> + pixmp->width = sess->width;
> + pixmp->height = sess->height;
> + }
> +
> + vdec_try_fmt_common(sess, sess->core->platform->num_formats, f);
> +
> + return 0;
> +}
> +
> +static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
> + u32 num_formats = sess->core->platform->num_formats;
> + const struct amvdec_format *fmt_out;
> + struct v4l2_pix_format_mplane orig_pixmp;
> + struct v4l2_format format;
> + u32 pixfmt_out = 0, pixfmt_cap = 0;
> +
> + orig_pixmp = *pixmp;
> +
> + fmt_out = vdec_try_fmt_common(sess, num_formats, f);
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> + pixfmt_out = pixmp->pixelformat;
> + pixfmt_cap = sess->pixfmt_cap;
> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> + pixfmt_cap = pixmp->pixelformat;
> + pixfmt_out = sess->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(sess, num_formats, &format);
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> + sess->width = format.fmt.pix_mp.width;
> + sess->height = format.fmt.pix_mp.height;
> + sess->colorspace = pixmp->colorspace;
> + sess->ycbcr_enc = pixmp->ycbcr_enc;
> + sess->quantization = pixmp->quantization;
> + sess->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(sess, num_formats, &format);
> +
> + sess->width = format.fmt.pix_mp.width;
> + sess->height = format.fmt.pix_mp.height;
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> + sess->fmt_out = fmt_out;
> + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> + sess->pixfmt_cap = format.fmt.pix_mp.pixelformat;
> +
> + return 0;
> +}
> +
> +static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> + const struct vdec_platform *platform = sess->core->platform;
> + const struct amvdec_format *fmt_out;
> +
> + memset(f->reserved, 0, sizeof(f->reserved));
> +
> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> + if (f->index >= platform->num_formats)
> + return -EINVAL;
> +
> + fmt_out = &platform->formats[f->index];
> + f->pixelformat = fmt_out->pixfmt;
> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> + fmt_out = sess->fmt_out;
> + if (f->index >= 4 || !fmt_out->pixfmts_cap[f->index])
> + return -EINVAL;
> +
> + f->pixelformat = fmt_out->pixfmts_cap[f->index];
> + } else {
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int vdec_enum_framesizes(struct file *file, void *fh,
> + struct v4l2_frmsizeenum *fsize)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> + const struct amvdec_format *formats = sess->core->platform->formats;
> + const struct amvdec_format *fmt;
> + u32 num_formats = sess->core->platform->num_formats;
> +
> + fmt = find_format(formats, num_formats, fsize->pixel_format);
> + if (!fmt || fsize->index)
> + return -EINVAL;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +
> + fsize->stepwise.min_width = 256;
> + fsize->stepwise.max_width = fmt->max_width;
> + fsize->stepwise.step_width = 1;
> + fsize->stepwise.min_height = 144;
> + fsize->stepwise.max_height = fmt->max_height;
> + fsize->stepwise.step_height = 1;
> +
> + return 0;
> +}
> +
> +static int
> +vdec_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd)
> +{
> + switch (cmd->cmd) {
> + case V4L2_DEC_CMD_STOP:
> + if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK)
> + return -EINVAL;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> + int ret;
> +
> + ret = vdec_try_decoder_cmd(file, fh, cmd);
> + if (ret)
> + return ret;
> +
> + if (!(sess->streamon_out & sess->streamon_cap))
> + goto unlock;
> +
> + dev_dbg(sess->core->dev, "Received V4L2_DEC_CMD_STOP\n");
> + sess->should_stop = 1;
> +
> + vdec_wait_inactive(sess);
> +
> + mutex_lock(&sess->lock);
> + if (codec_ops->drain)
> + codec_ops->drain(sess);
> + else
> + esparser_queue_eos(sess->core);
> +
> +unlock:
> + mutex_unlock(&sess->lock);
> + return ret;
> +}
> +
> +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);
> + 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_reqbufs = v4l2_m2m_ioctl_reqbufs,
> + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
> + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
> + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
> + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
> + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
> + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
> + .vidioc_streamon = v4l2_m2m_ioctl_streamon,
> + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
> + .vidioc_enum_framesizes = vdec_enum_framesizes,
> + .vidioc_subscribe_event = vdec_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> + .vidioc_try_decoder_cmd = vdec_try_decoder_cmd,
> + .vidioc_decoder_cmd = vdec_decoder_cmd,
> +};
> +
> +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq,
> + struct vb2_queue *dst_vq)
> +{
> + struct amvdec_session *sess = priv;
> + int ret;
> +
> + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> + src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> + src_vq->ops = &vdec_vb2_ops;
> + src_vq->mem_ops = &vb2_dma_contig_memops;
> + src_vq->drv_priv = sess;
> + src_vq->buf_struct_size = sizeof(struct dummy_buf);
> + src_vq->allow_zero_bytesused = 1;

This shouldn't be used for new drivers.

> + src_vq->min_buffers_needed = 1;
> + src_vq->dev = sess->core->dev;
> + ret = vb2_queue_init(src_vq);
> + if (ret)
> + return ret;
> +
> + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
> + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> + dst_vq->ops = &vdec_vb2_ops;
> + dst_vq->mem_ops = &vb2_dma_contig_memops;
> + dst_vq->drv_priv = sess;
> + dst_vq->buf_struct_size = sizeof(struct dummy_buf);
> + dst_vq->allow_zero_bytesused = 1;

And it definitely makes no sense for capture queues, since this
field applies to output queues only.

> + dst_vq->min_buffers_needed = 1;
> + dst_vq->dev = sess->core->dev;
> + ret = vb2_queue_init(dst_vq);

Please fill in the lock field of both queues as well.

> + if (ret) {
> + vb2_queue_release(src_vq);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int vdec_open(struct file *file)
> +{
> + struct amvdec_core *core = video_drvdata(file);
> + struct device *dev = core->dev;
> + const struct amvdec_format *formats = core->platform->formats;
> + struct amvdec_session *sess;
> + int ret;
> +
> + sess = kzalloc(sizeof(*sess), GFP_KERNEL);
> + if (!sess) {
> + mutex_unlock(&core->lock);
> + return -ENOMEM;
> + }
> +
> + sess->core = core;
> +
> + sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops);
> + if (IS_ERR(sess->m2m_dev)) {
> + dev_err(dev, "Fail to v4l2_m2m_init\n");
> + ret = PTR_ERR(sess->m2m_dev);
> + goto err_free_sess;
> + }
> +
> + sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init);
> + if (IS_ERR(sess->m2m_ctx)) {
> + dev_err(dev, "Fail to v4l2_m2m_ctx_init\n");
> + ret = PTR_ERR(sess->m2m_ctx);
> + goto err_m2m_release;
> + }
> +
> + sess->pixfmt_cap = formats[0].pixfmts_cap[0];
> + sess->fmt_out = &formats[0];
> + sess->width = 1280;
> + sess->height = 720;
> +
> + INIT_LIST_HEAD(&sess->timestamps);
> + INIT_LIST_HEAD(&sess->bufs_recycle);
> + INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src);
> + mutex_init(&sess->lock);
> + mutex_init(&sess->bufs_recycle_lock);
> + spin_lock_init(&sess->ts_spinlock);
> +
> + v4l2_fh_init(&sess->fh, core->vdev_dec);
> + v4l2_fh_add(&sess->fh);
> + sess->fh.m2m_ctx = sess->m2m_ctx;
> + file->private_data = &sess->fh;
> +
> + return 0;
> +
> +err_m2m_release:
> + v4l2_m2m_release(sess->m2m_dev);
> +err_free_sess:
> + kfree(sess);
> + return ret;
> +}
> +
> +static int vdec_close(struct file *file)
> +{
> + struct amvdec_session *sess =
> + container_of(file->private_data, struct amvdec_session, fh);
> + struct amvdec_core *core = sess->core;
> +
> + v4l2_m2m_ctx_release(sess->m2m_ctx);
> + v4l2_m2m_release(sess->m2m_dev);
> + v4l2_fh_del(&sess->fh);
> + v4l2_fh_exit(&sess->fh);
> +
> + mutex_destroy(&sess->lock);
> + mutex_destroy(&sess->bufs_recycle_lock);
> +
> + kfree(sess);
> +
> + if (core->cur_sess == sess)
> + core->cur_sess = NULL;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_file_operations vdec_fops = {
> + .owner = THIS_MODULE,
> + .open = vdec_open,
> + .release = vdec_close,
> + .unlocked_ioctl = video_ioctl2,
> + .poll = v4l2_m2m_fop_poll,
> + .mmap = v4l2_m2m_fop_mmap,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl32 = v4l2_compat_ioctl32,
> +#endif

Not needed. It's only needed if you have custom ioctls, and you don't.

> +};
> +
> +static irqreturn_t vdec_isr(int irq, void *data)
> +{
> + struct amvdec_core *core = data;
> + struct amvdec_session *sess = core->cur_sess;
> +
> + sess->last_irq_jiffies = get_jiffies_64();
> +
> + return sess->fmt_out->codec_ops->isr(sess);
> +}
> +
> +static irqreturn_t vdec_threaded_isr(int irq, void *data)
> +{
> + struct amvdec_core *core = data;
> + struct amvdec_session *sess = core->cur_sess;
> +
> + return sess->fmt_out->codec_ops->threaded_isr(sess);
> +}
> +
> +static const struct of_device_id vdec_dt_match[] = {
> + { .compatible = "amlogic,gxbb-vdec",
> + .data = &vdec_platform_gxbb },
> + { .compatible = "amlogic,gxm-vdec",
> + .data = &vdec_platform_gxm },
> + { .compatible = "amlogic,gxl-vdec",
> + .data = &vdec_platform_gxl },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, vdec_dt_match);
> +
> +static int vdec_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct video_device *vdev;
> + struct amvdec_core *core;
> + struct resource *r;
> + const struct of_device_id *of_id;
> + int irq;
> + int ret;
> +
> + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
> + if (!core)
> + return -ENOMEM;
> +
> + core->dev = dev;
> + platform_set_drvdata(pdev, core);
> +
> + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dos");
> + core->dos_base = devm_ioremap_resource(dev, r);
> + if (IS_ERR(core->dos_base)) {
> + dev_err(dev, "Couldn't remap DOS memory\n");
> + return PTR_ERR(core->dos_base);
> + }
> +
> + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "esparser");
> + core->esparser_base = devm_ioremap_resource(dev, r);
> + if (IS_ERR(core->esparser_base)) {
> + dev_err(dev, "Couldn't remap ESPARSER memory\n");
> + return PTR_ERR(core->esparser_base);
> + }
> +
> + core->regmap_ao = syscon_regmap_lookup_by_phandle(dev->of_node,
> + "amlogic,ao-sysctrl");
> + if (IS_ERR(core->regmap_ao)) {
> + dev_err(dev, "Couldn't regmap AO sysctrl\n");
> + return PTR_ERR(core->regmap_ao);
> + }
> +
> + core->canvas = meson_canvas_get(dev);
> + if (!core->canvas)
> + return PTR_ERR(core->canvas);
> +
> + core->dos_parser_clk = devm_clk_get(dev, "dos_parser");
> + if (IS_ERR(core->dos_parser_clk))
> + return -EPROBE_DEFER;
> +
> + core->dos_clk = devm_clk_get(dev, "dos");
> + if (IS_ERR(core->dos_clk))
> + return -EPROBE_DEFER;
> +
> + core->vdec_1_clk = devm_clk_get(dev, "vdec_1");
> + if (IS_ERR(core->vdec_1_clk))
> + return -EPROBE_DEFER;
> +
> + core->vdec_hevc_clk = devm_clk_get(dev, "vdec_hevc");
> + if (IS_ERR(core->vdec_hevc_clk))
> + return -EPROBE_DEFER;
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0)
> + return irq;
> +
> + ret = devm_request_threaded_irq(core->dev, irq, vdec_isr,
> + vdec_threaded_isr, IRQF_ONESHOT,
> + "vdec", core);
> + if (ret)
> + return ret;
> +
> + ret = esparser_init(pdev, core);
> + if (ret)
> + return ret;
> +
> + ret = v4l2_device_register(dev, &core->v4l2_dev);
> + if (ret) {
> + dev_err(dev, "Couldn't register v4l2 device\n");
> + return -ENOMEM;
> + }
> +
> + vdev = video_device_alloc();
> + if (!vdev) {
> + ret = -ENOMEM;
> + goto err_vdev_release;
> + }
> +
> + strlcpy(vdev->name, "meson-video-decoder", sizeof(vdev->name));
> + vdev->release = video_device_release;
> + vdev->fops = &vdec_fops;
> + vdev->ioctl_ops = &vdec_ioctl_ops;
> + vdev->vfl_dir = VFL_DIR_M2M;
> + vdev->v4l2_dev = &core->v4l2_dev;

Please fill in vdev->lock, you probably want to set it to &core->lock
(not sure, though). Otherwise you would have to serialize all ioctls yourself.

> + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
> +
> + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
> + if (ret) {
> + dev_err(dev, "Failed registering video device\n");
> + goto err_vdev_release;
> + }
> +
> + of_id = of_match_node(vdec_dt_match, dev->of_node);
> + core->platform = of_id->data;
> + core->vdev_dec = vdev;
> + core->dev_dec = dev;
> + mutex_init(&core->lock);
> +
> + video_set_drvdata(vdev, core);

I'd move all this to before the video_register_device() call. Otherwise the video device
can appear and used immediately without this initialization being done.

> +
> + return 0;
> +
> +err_vdev_release:
> + video_device_release(vdev);
> + return ret;
> +}
> +
> +static int vdec_remove(struct platform_device *pdev)
> +{
> + struct amvdec_core *core = platform_get_drvdata(pdev);
> +
> + video_unregister_device(core->vdev_dec);
> +
> + return 0;
> +}
> +
> +static struct platform_driver meson_vdec_driver = {
> + .probe = vdec_probe,
> + .remove = vdec_remove,
> + .driver = {
> + .name = "meson-vdec",
> + .of_match_table = vdec_dt_match,
> + },
> +};
> +module_platform_driver(meson_vdec_driver);
> +
> +MODULE_DESCRIPTION("Meson video decoder driver for GXBB/GXL/GXM");
> +MODULE_AUTHOR("Maxime Jourdan <[email protected]>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/meson/vdec/vdec.h b/drivers/media/platform/meson/vdec/vdec.h
> new file mode 100644
> index 000000000000..8250fb82dfab
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec.h
> @@ -0,0 +1,234 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_CORE_H_
> +#define __MESON_VDEC_CORE_H_
> +
> +/* 32 buffers in 3-plane YUV420 */
> +#define MAX_CANVAS (32 * 3)
> +
> +#include <linux/regmap.h>
> +#include <linux/list.h>
> +#include <media/videobuf2-v4l2.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <linux/soc/amlogic/meson-canvas.h>
> +
> +#include "vdec_platform.h"
> +
> +struct amvdec_buffer {
> + struct list_head list;
> + struct vb2_buffer *vb;
> +};
> +
> +struct amvdec_timestamp {
> + struct list_head list;
> + u64 ts;
> +};
> +
> +struct amvdec_session;
> +
> +/**
> + * struct amvdec_core - device parameters, singleton
> + *
> + * @dos_base: DOS memory base address
> + * @esparser_base: PARSER memory base address
> + * @regmap_ao: regmap for the AO bus
> + * @dev: core device
> + * @dev_dec: decoder device
> + * @platform: platform-specific data
> + * @canvas: canvas provider reference
> + * @dos_parser_clk: DOS_PARSER clock
> + * @dos_clk: DOS clock
> + * @vdec_1_clk: VDEC_1 clock
> + * @vdec_hevc_clk: VDEC_HEVC clock
> + * @esparser_reset: RESET for the PARSER
> + * @vdec_dec: video device for the decoder
> + * @v4l2_dev: v4l2 device
> + * @cur_sess: current decoding session
> + * @lock: lock for this structure
> + */
> +struct amvdec_core {
> + void __iomem *dos_base;
> + void __iomem *esparser_base;
> + struct regmap *regmap_ao;
> +
> + struct device *dev;
> + struct device *dev_dec;
> + const struct vdec_platform *platform;
> +
> + struct meson_canvas *canvas;
> +
> + struct clk *dos_parser_clk;
> + struct clk *dos_clk;
> + struct clk *vdec_1_clk;
> + struct clk *vdec_hevc_clk;
> +
> + struct reset_control *esparser_reset;
> +
> + struct video_device *vdev_dec;
> + struct v4l2_device v4l2_dev;
> +
> + struct amvdec_session *cur_sess;
> + struct mutex lock;
> +};
> +
> +/**
> + * struct amvdec_ops - vdec operations
> + *
> + * @start: mandatory call when the vdec needs to initialize
> + * @stop: mandatory call when the vdec needs to stop
> + * @conf_esparser: mandatory call to let the vdec configure the ESPARSER
> + * @vififo_level: mandatory call to get the current amount of data
> + * in the VIFIFO
> + */
> +struct amvdec_ops {
> + int (*start)(struct amvdec_session *sess);
> + int (*stop)(struct amvdec_session *sess);
> + void (*conf_esparser)(struct amvdec_session *sess);
> + u32 (*vififo_level)(struct amvdec_session *sess);
> +};
> +
> +/**
> + * struct amvdec_codec_ops - codec operations
> + *
> + * @start: mandatory call when the codec needs to initialize
> + * @stop: mandatory call when the codec needs to stop
> + * @load_extended_firmware: optional call to load additional firmware bits
> + * @num_pending_bufs: optional call to get the number of dst buffers on hold
> + * @can_recycle: optional call to know if the codec is ready to recycle
> + * a dst buffer
> + * @recycle: optional call to tell the codec to recycle a dst buffer. Must go
> + * in pair with can_recycle
> + * @drain: optional call if the codec has a custom way of draining
> + * @isr: mandatory call when the ISR triggers
> + * @threaded_isr: mandatory call for the threaded ISR
> + */
> +struct amvdec_codec_ops {
> + int (*start)(struct amvdec_session *sess);
> + int (*stop)(struct amvdec_session *sess);
> + int (*load_extended_firmware)(struct amvdec_session *sess,
> + const u8 *data, u32 len);
> + u32 (*num_pending_bufs)(struct amvdec_session *sess);
> + int (*can_recycle)(struct amvdec_core *core);
> + void (*recycle)(struct amvdec_core *core, u32 buf_idx);
> + void (*drain)(struct amvdec_session *sess);
> + irqreturn_t (*isr)(struct amvdec_session *sess);
> + irqreturn_t (*threaded_isr)(struct amvdec_session *sess);
> +};
> +
> +/**
> + * struct amvdec_format - describes one of the OUTPUT (src) format supported
> + *
> + * @pixfmt: V4L2 pixel format
> + * @min_buffers: minimum amount of CAPTURE (dst) buffers
> + * @max_buffers: maximum amount of CAPTURE (dst) buffers
> + * @max_width: maximum picture width supported
> + * @max_height: maximum picture height supported
> + * @vdec_ops: the VDEC operations that support this format
> + * @codec_ops: the codec operations that support this format
> + * @firmware_path: Path to the firmware that supports this format
> + * @pixfmts_cap: list of CAPTURE pixel formats available with pixfmt
> + */
> +struct amvdec_format {
> + u32 pixfmt;
> + u32 min_buffers;
> + u32 max_buffers;
> + u32 max_width;
> + u32 max_height;
> +
> + struct amvdec_ops *vdec_ops;
> + struct amvdec_codec_ops *codec_ops;
> +
> + char *firmware_path;
> + u32 pixfmts_cap[4];
> +};
> +
> +/**
> + * struct amvdec_session - decoding session parameters
> + *
> + * @core: reference to the vdec core struct
> + * @fh: v4l2 file handle
> + * @m2m_dev: v4l2 m2m device
> + * @m2m_ctx: v4l2 m2m context
> + * @lock: session lock
> + * @fmt_out: vdec pixel format for the OUTPUT queue
> + * @pixfmt_cap: V4L2 pixel format for the CAPTURE queue
> + * @width: current picture width
> + * @height: current picture height
> + * @colorspace: current colorspace
> + * @ycbcr_enc: current ycbcr_enc
> + * @quantization: current quantization
> + * @xfer_func: current transfer function
> + * @esparser_queued_bufs: number of buffers currently queued into ESPARSER
> + * @esparser_queue_work: work struct for the ESPARSER to process src buffers
> + * @streamon_cap: stream on flag for capture queue
> + * @streamon_out: stream on flag for output queue
> + * @sequence_cap: capture sequence counter
> + * @should_stop: flag set is userspacec signaled EOS via command
> + * or empty buffer
> + * @keyframe_found: flag set once a keyframe has been parsed
> + * @canvas_alloc: array of all the canvas IDs allocated
> + * @canvas_num: number of canvas IDs allocated
> + * @vififo_vaddr: virtual address for the VIFIFO
> + * @vififo_paddr: physical address for the VIFIFO
> + * @vififo_size: size of the VIFIFO dma alloc
> + * @bufs_recycle: list of buffers that need to be recycled
> + * @bufs_recycle_lock: lock for the bufs_recycle list
> + * @recycle_thread: task struct for the recycling thread
> + * @timestamps: chronological list of src timestamps
> + * @ts_spinlock: spinlock for the timestamps list
> + * @last_irq_jiffies: tracks last time the vdec triggered an IRQ
> + * @priv: codec private data
> + */
> +struct amvdec_session {
> + struct amvdec_core *core;
> +
> + struct v4l2_fh fh;
> + struct v4l2_m2m_dev *m2m_dev;
> + struct v4l2_m2m_ctx *m2m_ctx;
> + struct mutex lock;
> +
> + const struct amvdec_format *fmt_out;
> + u32 pixfmt_cap;
> +
> + u32 width;
> + u32 height;
> + u32 colorspace;
> + u8 ycbcr_enc;
> + u8 quantization;
> + u8 xfer_func;
> +
> + atomic_t esparser_queued_bufs;
> + struct work_struct esparser_queue_work;
> +
> + unsigned int streamon_cap, streamon_out;
> + unsigned int sequence_cap;
> + unsigned int should_stop;
> + unsigned int keyframe_found;
> +
> + u8 canvas_alloc[MAX_CANVAS];
> + u32 canvas_num;
> +
> + void *vififo_vaddr;
> + dma_addr_t vififo_paddr;
> + u32 vififo_size;
> +
> + struct list_head bufs_recycle;
> + struct mutex bufs_recycle_lock;
> + struct task_struct *recycle_thread;
> +
> + struct list_head timestamps;
> + spinlock_t ts_spinlock;
> +
> + u64 last_irq_jiffies;
> +
> + void *priv;
> +};
> +
> +u32 amvdec_get_output_size(struct amvdec_session *sess);
> +
> +#endif
> diff --git a/drivers/media/platform/meson/vdec/vdec_1.c b/drivers/media/platform/meson/vdec/vdec_1.c
> new file mode 100644
> index 000000000000..29f6305a6276
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec_1.c
> @@ -0,0 +1,228 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + *
> + * VDEC_1 is a video decoding block that allows decoding of
> + * MPEG 1/2/4, H.263, H.264, MJPEG, VC1
> + */
> +
> +#include <linux/firmware.h>
> +#include <linux/clk.h>
> +
> +#include "vdec_1.h"
> +#include "vdec_helpers.h"
> +#include "dos_regs.h"
> +
> +/* AO Registers */
> +#define AO_RTI_GEN_PWR_SLEEP0 0xe8
> +#define AO_RTI_GEN_PWR_ISO0 0xec
> + #define GEN_PWR_VDEC_1 (BIT(3) | BIT(2))
> +
> +#define MC_SIZE (4096 * 4)
> +
> +static int
> +vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname)
> +{
> + const struct firmware *fw;
> + struct amvdec_core *core = sess->core;
> + struct device *dev = core->dev_dec;
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> + static void *mc_addr;
> + static dma_addr_t mc_addr_map;
> + int ret;
> + u32 i = 1000;
> +
> + ret = request_firmware(&fw, fwname, dev);
> + if (ret < 0)
> + return -EINVAL;
> +
> + if (fw->size < MC_SIZE) {
> + dev_err(dev, "Firmware size %zu is too small. Expected %u.\n",
> + fw->size, MC_SIZE);
> + ret = -EINVAL;
> + goto release_firmware;
> + }
> +
> + mc_addr = dma_alloc_coherent(core->dev, MC_SIZE,
> + &mc_addr_map, GFP_KERNEL);
> + if (!mc_addr) {
> + dev_err(dev,
> + "Failed allocating memory for firmware loading\n");
> + ret = -ENOMEM;
> + goto release_firmware;
> + }
> +
> + memcpy(mc_addr, fw->data, MC_SIZE);
> +
> + amvdec_write_dos(core, MPSR, 0);
> + amvdec_write_dos(core, CPSR, 0);
> +
> + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31));
> +
> + amvdec_write_dos(core, IMEM_DMA_ADR, mc_addr_map);
> + amvdec_write_dos(core, IMEM_DMA_COUNT, MC_SIZE / 4);
> + amvdec_write_dos(core, IMEM_DMA_CTRL, (0x8000 | (7 << 16)));
> +
> + while (--i && amvdec_read_dos(core, IMEM_DMA_CTRL) & 0x8000) { }
> +
> + if (i == 0) {
> + dev_err(dev, "Firmware load fail (DMA hang?)\n");
> + ret = -EINVAL;
> + goto free_mc;
> + }
> +
> + if (codec_ops->load_extended_firmware)
> + codec_ops->load_extended_firmware(sess, fw->data + MC_SIZE,
> + fw->size - MC_SIZE);
> +
> +free_mc:
> + dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map);
> +release_firmware:
> + release_firmware(fw);
> + return ret;
> +}
> +
> +int vdec_1_stbuf_power_up(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> +
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_CONTROL, 0);
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_WRAP_COUNT, 0);
> + amvdec_write_dos(core, POWER_CTL_VLD, BIT(4));
> +
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_START_PTR, sess->vififo_paddr);
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_CURR_PTR, sess->vififo_paddr);
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_END_PTR,
> + sess->vififo_paddr + sess->vififo_size - 8);
> +
> + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1);
> + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1);
> +
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, MEM_BUFCTRL_MANUAL);
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_WP, sess->vififo_paddr);
> +
> + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
> + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
> +
> + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL,
> + (0x11 << MEM_FIFO_CNT_BIT) | MEM_FILL_ON_LEVEL |
> + MEM_CTRL_FILL_EN | MEM_CTRL_EMPTY_EN);
> +
> + return 0;
> +}
> +
> +static void vdec_1_conf_esparser(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> +
> + /* VDEC_1 specific ESPARSER stuff */
> + amvdec_write_dos(core, DOS_GEN_CTRL0, 0);
> + amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
> + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
> +}
> +
> +static u32 vdec_1_vififo_level(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> +
> + return amvdec_read_dos(core, VLD_MEM_VIFIFO_LEVEL);
> +}
> +
> +static int vdec_1_stop(struct amvdec_session *sess)
> +{
> + struct amvdec_core *core = sess->core;
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> +
> + amvdec_write_dos(core, MPSR, 0);
> + amvdec_write_dos(core, CPSR, 0);
> + amvdec_write_dos(core, ASSIST_MBOX1_MASK, 0);
> +
> + amvdec_write_dos(core, DOS_SW_RESET0, BIT(12) | BIT(11));
> + amvdec_write_dos(core, DOS_SW_RESET0, 0);
> + amvdec_read_dos(core, DOS_SW_RESET0);
> +
> + /* enable vdec1 isolation */
> + regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0xc0);
> + /* power off vdec1 memories */
> + amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0xffffffff);
> + /* power off vdec1 */
> + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0,
> + GEN_PWR_VDEC_1, GEN_PWR_VDEC_1);
> +
> + clk_disable_unprepare(core->vdec_1_clk);
> +
> + if (sess->priv)
> + codec_ops->stop(sess);
> +
> + return 0;
> +}
> +
> +static int vdec_1_start(struct amvdec_session *sess)
> +{
> + int ret;
> + struct amvdec_core *core = sess->core;
> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
> +
> + /* Configure the vdec clk to the maximum available */
> + clk_set_rate(core->vdec_1_clk, 666666666);
> + ret = clk_prepare_enable(core->vdec_1_clk);
> + if (ret)
> + return ret;
> +
> + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0,
> + GEN_PWR_VDEC_1, 0);
> + udelay(10);
> +
> + /* Reset VDEC1 */
> + amvdec_write_dos(core, DOS_SW_RESET0, 0xfffffffc);
> + amvdec_write_dos(core, DOS_SW_RESET0, 0x00000000);
> +
> + amvdec_write_dos(core, DOS_GCLK_EN0, 0x3ff);
> +
> + /* enable VDEC Memories */
> + amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0);
> + /* Remove VDEC1 Isolation */
> + regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0);
> + /* Reset DOS top registers */
> + amvdec_write_dos(core, DOS_VDEC_MCRCC_STALL_CTRL, 0);
> +
> + amvdec_write_dos(core, GCLK_EN, 0x3ff);
> + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31));
> +
> + vdec_1_stbuf_power_up(sess);
> +
> + ret = vdec_1_load_firmware(sess, sess->fmt_out->firmware_path);
> + if (ret)
> + goto stop;
> +
> + ret = codec_ops->start(sess);
> + if (ret)
> + goto stop;
> +
> + /* Enable IRQ */
> + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1);
> + amvdec_write_dos(core, ASSIST_MBOX1_MASK, 1);
> +
> + /* Enable 2-plane output */
> + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M)
> + amvdec_write_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17));
> +
> + /* Enable firmware processor */
> + amvdec_write_dos(core, MPSR, 1);
> + /* Let the firmware settle */
> + udelay(10);
> +
> + return 0;
> +
> +stop:
> + vdec_1_stop(sess);
> + return ret;
> +}
> +
> +struct amvdec_ops vdec_1_ops = {
> + .start = vdec_1_start,
> + .stop = vdec_1_stop,
> + .conf_esparser = vdec_1_conf_esparser,
> + .vififo_level = vdec_1_vififo_level,
> +};
> diff --git a/drivers/media/platform/meson/vdec/vdec_1.h b/drivers/media/platform/meson/vdec/vdec_1.h
> new file mode 100644
> index 000000000000..042d930c40d7
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec_1.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_VDEC_1_H_
> +#define __MESON_VDEC_VDEC_1_H_
> +
> +#include "vdec.h"
> +
> +extern struct amvdec_ops vdec_1_ops;
> +
> +#endif
> diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.c b/drivers/media/platform/meson/vdec/vdec_helpers.c
> new file mode 100644
> index 000000000000..615107629765
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec_helpers.c
> @@ -0,0 +1,354 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#include <media/v4l2-mem2mem.h>
> +#include <media/v4l2-event.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "vdec_helpers.h"
> +
> +#define NUM_CANVAS_NV12 2
> +#define NUM_CANVAS_YUV420 3
> +
> +u32 amvdec_read_dos(struct amvdec_core *core, u32 reg)
> +{
> + return readl_relaxed(core->dos_base + reg);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_read_dos);
> +
> +void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val)
> +{
> + writel_relaxed(val, core->dos_base + reg);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_write_dos);
> +
> +void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val)
> +{
> + amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) | val);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_write_dos_bits);
> +
> +void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val)
> +{
> + amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) & ~val);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_clear_dos_bits);
> +
> +u32 amvdec_read_parser(struct amvdec_core *core, u32 reg)
> +{
> + return readl_relaxed(core->esparser_base + reg);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_read_parser);
> +
> +void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val)
> +{
> + writel_relaxed(val, core->esparser_base + reg);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_write_parser);
> +
> +static int canvas_alloc(struct amvdec_session *sess, u8 *canvas_id)
> +{
> + int ret;
> +
> + if (sess->canvas_num >= MAX_CANVAS) {
> + dev_err(sess->core->dev, "Reached max number of canvas\n");
> + return -ENOMEM;
> + }
> +
> + ret = meson_canvas_alloc(sess->core->canvas, canvas_id);
> + if (ret)
> + return ret;
> +
> + sess->canvas_alloc[sess->canvas_num++] = *canvas_id;
> + return 0;
> +}
> +
> +static int set_canvas_yuv420m(struct amvdec_session *sess,
> + struct vb2_buffer *vb, u32 width,
> + u32 height, u32 reg)
> +{
> + struct amvdec_core *core = sess->core;
> + u8 canvas_id[NUM_CANVAS_YUV420]; /* Y U V */
> + dma_addr_t buf_paddr[NUM_CANVAS_YUV420]; /* Y U V */
> + int ret, i;
> +
> + for (i = 0; i < NUM_CANVAS_YUV420; ++i) {
> + ret = canvas_alloc(sess, &canvas_id[i]);
> + if (ret)
> + return ret;
> +
> + buf_paddr[i] =
> + vb2_dma_contig_plane_dma_addr(vb, i);
> + }
> +
> + /* Y plane */
> + meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0],
> + width, height, MESON_CANVAS_WRAP_NONE,
> + MESON_CANVAS_BLKMODE_LINEAR,
> + MESON_CANVAS_ENDIAN_SWAP64);
> +
> + /* U plane */
> + meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1],
> + width / 2, height / 2, MESON_CANVAS_WRAP_NONE,
> + MESON_CANVAS_BLKMODE_LINEAR,
> + MESON_CANVAS_ENDIAN_SWAP64);
> +
> + /* V plane */
> + meson_canvas_config(core->canvas, canvas_id[2], buf_paddr[2],
> + width / 2, height / 2, MESON_CANVAS_WRAP_NONE,
> + MESON_CANVAS_BLKMODE_LINEAR,
> + MESON_CANVAS_ENDIAN_SWAP64);
> +
> + amvdec_write_dos(core, reg,
> + ((canvas_id[2]) << 16) |
> + ((canvas_id[1]) << 8) |
> + (canvas_id[0]));
> +
> + return 0;
> +}
> +
> +static int set_canvas_nv12m(struct amvdec_session *sess,
> + struct vb2_buffer *vb, u32 width,
> + u32 height, u32 reg)
> +{
> + struct amvdec_core *core = sess->core;
> + u8 canvas_id[NUM_CANVAS_NV12]; /* Y U/V */
> + dma_addr_t buf_paddr[NUM_CANVAS_NV12]; /* Y U/V */
> + int ret, i;
> +
> + for (i = 0; i < NUM_CANVAS_NV12; ++i) {
> + ret = canvas_alloc(sess, &canvas_id[i]);
> + if (ret)
> + return ret;
> +
> + buf_paddr[i] =
> + vb2_dma_contig_plane_dma_addr(vb, i);
> + }
> +
> + /* Y plane */
> + meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0],
> + width, height, MESON_CANVAS_WRAP_NONE,
> + MESON_CANVAS_BLKMODE_LINEAR,
> + MESON_CANVAS_ENDIAN_SWAP64);
> +
> + /* U/V plane */
> + meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1],
> + width, height / 2, MESON_CANVAS_WRAP_NONE,
> + MESON_CANVAS_BLKMODE_LINEAR,
> + MESON_CANVAS_ENDIAN_SWAP64);
> +
> + amvdec_write_dos(core, reg,
> + ((canvas_id[1]) << 16) |
> + ((canvas_id[1]) << 8) |
> + (canvas_id[0]));
> +
> + return 0;
> +}
> +
> +int amvdec_set_canvases(struct amvdec_session *sess,
> + u32 reg_base[], u32 reg_num[])
> +{
> + struct v4l2_m2m_buffer *buf;
> + u32 pixfmt = sess->pixfmt_cap;
> + u32 width = ALIGN(sess->width, 64);
> + u32 height = ALIGN(sess->height, 64);
> + u32 reg_cur = reg_base[0];
> + u32 reg_num_cur = 0;
> + u32 reg_base_cur = 0;
> + int ret;
> +
> + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) {
> + if (!reg_base[reg_base_cur])
> + return -EINVAL;
> +
> + reg_cur = reg_base[reg_base_cur] + reg_num_cur * 4;
> +
> + switch (pixfmt) {
> + case V4L2_PIX_FMT_NV12M:
> + ret = set_canvas_nv12m(sess, &buf->vb.vb2_buf, width,
> + height, reg_cur);
> + if (ret)
> + return ret;
> + break;
> + case V4L2_PIX_FMT_YUV420M:
> + ret = set_canvas_yuv420m(sess, &buf->vb.vb2_buf, width,
> + height, reg_cur);
> + if (ret)
> + return ret;
> + break;
> + default:
> + dev_err(sess->core->dev, "Unsupported pixfmt %08X\n",
> + pixfmt);
> + return -EINVAL;
> + };
> +
> + reg_num_cur++;
> + if (reg_num_cur >= reg_num[reg_base_cur]) {
> + reg_base_cur++;
> + reg_num_cur = 0;
> + }
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(amvdec_set_canvases);
> +
> +void amvdec_dst_buf_done(struct amvdec_session *sess,
> + struct vb2_v4l2_buffer *vbuf, u32 field)
> +{
> + struct device *dev = sess->core->dev_dec;
> + struct amvdec_timestamp *tmp;
> + struct list_head *timestamps = &sess->timestamps;
> + u32 output_size = amvdec_get_output_size(sess);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&sess->ts_spinlock, flags);
> + if (list_empty(timestamps)) {
> + dev_err(dev, "Buffer %u done but list is empty\n",
> + vbuf->vb2_buf.index);
> +
> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
> + amvdec_abort(sess);
> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
> + goto end;
> + }
> +
> + tmp = list_first_entry(timestamps, struct amvdec_timestamp, list);
> +
> + switch (sess->pixfmt_cap) {
> + case V4L2_PIX_FMT_NV12M:
> + vbuf->vb2_buf.planes[0].bytesused = output_size;
> + vbuf->vb2_buf.planes[1].bytesused = output_size / 2;
> + break;
> + case V4L2_PIX_FMT_YUV420M:
> + vbuf->vb2_buf.planes[0].bytesused = output_size;
> + vbuf->vb2_buf.planes[1].bytesused = output_size / 4;
> + vbuf->vb2_buf.planes[2].bytesused = output_size / 4;
> + break;
> + }
> + vbuf->vb2_buf.timestamp = tmp->ts;
> + vbuf->sequence = sess->sequence_cap++;
> +
> + list_del(&tmp->list);
> + kfree(tmp);
> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
> +
> + atomic_dec(&sess->esparser_queued_bufs);
> +
> + if (sess->should_stop && list_empty(timestamps)) {
> + const struct v4l2_event ev = { .type = V4L2_EVENT_EOS };
> +
> + dev_dbg(dev, "Signaling EOS\n");
> + v4l2_event_queue_fh(&sess->fh, &ev);
> + vbuf->flags |= V4L2_BUF_FLAG_LAST;
> + } else if (sess->should_stop)
> + dev_dbg(dev, "should_stop, %u bufs remain\n",
> + atomic_read(&sess->esparser_queued_bufs));
> +
> + vbuf->field = field;
> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
> +
> +end:
> + /* Buffer done probably means the vififo got freed */
> + schedule_work(&sess->esparser_queue_work);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done);
> +
> +void
> +amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx, u32 field)
> +{
> + struct vb2_v4l2_buffer *vbuf;
> + struct device *dev = sess->core->dev_dec;
> +
> + vbuf = v4l2_m2m_dst_buf_remove_by_idx(sess->m2m_ctx, buf_idx);
> + if (!vbuf) {
> + dev_err(dev,
> + "Buffer %u done but it doesn't exist in m2m_ctx\n",
> + buf_idx);
> + amvdec_rm_first_ts(sess);
> + return;
> + }
> +
> + amvdec_dst_buf_done(sess, vbuf, field);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done_idx);
> +
> +void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts)
> +{
> + struct amvdec_timestamp *new_ts, *tmp;
> + unsigned long flags;
> +
> + new_ts = kmalloc(sizeof(*new_ts), GFP_KERNEL);
> + new_ts->ts = ts;
> +
> + spin_lock_irqsave(&sess->ts_spinlock, flags);
> +
> + if (list_empty(&sess->timestamps))
> + goto add_tail;
> +
> + list_for_each_entry(tmp, &sess->timestamps, list) {
> + if (ts < tmp->ts) {
> + list_add_tail(&new_ts->list, &tmp->list);
> + goto unlock;
> + }
> + }
> +
> +add_tail:
> + list_add_tail(&new_ts->list, &sess->timestamps);
> +unlock:
> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_add_ts_reorder);
> +
> +void amvdec_remove_ts(struct amvdec_session *sess, u64 ts)
> +{
> + struct amvdec_timestamp *tmp;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&sess->ts_spinlock, flags);
> + list_for_each_entry(tmp, &sess->timestamps, list) {
> + if (tmp->ts == ts) {
> + list_del(&tmp->list);
> + kfree(tmp);
> + goto unlock;
> + }
> + }
> + dev_warn(sess->core->dev_dec,
> + "Couldn't remove buffer with timestamp %llu from list\n", ts);
> +
> +unlock:
> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_remove_ts);
> +
> +void amvdec_rm_first_ts(struct amvdec_session *sess)
> +{
> + unsigned long flags;
> + struct amvdec_buffer *tmp;
> + struct device *dev = sess->core->dev_dec;
> +
> + spin_lock_irqsave(&sess->ts_spinlock, flags);
> + if (list_empty(&sess->timestamps)) {
> + dev_err(dev, "Can't rm first timestamp: list empty\n");
> + goto unlock;
> + }
> +
> + tmp = list_first_entry(&sess->timestamps, struct amvdec_buffer, list);
> + list_del(&tmp->list);
> + kfree(tmp);
> + atomic_dec(&sess->esparser_queued_bufs);
> +
> +unlock:
> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
> +}
> +
> +void amvdec_abort(struct amvdec_session *sess)
> +{
> + dev_info(sess->core->dev, "Aborting decoding session!\n");
> + vb2_queue_error(&sess->m2m_ctx->cap_q_ctx.q);
> + vb2_queue_error(&sess->m2m_ctx->out_q_ctx.q);
> +}
> +EXPORT_SYMBOL_GPL(amvdec_abort);
> diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.h b/drivers/media/platform/meson/vdec/vdec_helpers.h
> new file mode 100644
> index 000000000000..352c6b4c4b84
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec_helpers.h
> @@ -0,0 +1,45 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_HELPERS_H_
> +#define __MESON_VDEC_HELPERS_H_
> +
> +#include "vdec.h"
> +
> +/**
> + * amvdec_set_canvases() - Map VB2 buffers to canvases
> + *
> + * @sess: current session
> + * @reg_base: Registry bases of where to write the canvas indexes
> + * @reg_num: number of contiguous registers after each reg_base (including it)
> + */
> +int amvdec_set_canvases(struct amvdec_session *sess,
> + u32 reg_base[], u32 reg_num[]);
> +
> +u32 amvdec_read_dos(struct amvdec_core *core, u32 reg);
> +void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val);
> +void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val);
> +void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val);
> +u32 amvdec_read_parser(struct amvdec_core *core, u32 reg);
> +void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val);
> +
> +void amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx,
> + u32 field);
> +void amvdec_dst_buf_done(struct amvdec_session *sess,
> + struct vb2_v4l2_buffer *vbuf, u32 field);
> +
> +/**
> + * amvdec_add_ts_reorder() - Add a timestamp to the list in chronological order
> + *
> + * @sess: current session
> + * @ts: timestamp to add
> + */
> +void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts);
> +void amvdec_remove_ts(struct amvdec_session *sess, u64 ts);
> +void amvdec_rm_first_ts(struct amvdec_session *sess);
> +
> +void amvdec_abort(struct amvdec_session *sess);
> +#endif
> diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c
> new file mode 100644
> index 000000000000..46eeb7426f54
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec_platform.c
> @@ -0,0 +1,101 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#include "vdec_platform.h"
> +#include "vdec.h"
> +
> +#include "vdec_1.h"
> +#include "codec_mpeg12.h"
> +
> +static const struct amvdec_format vdec_formats_gxbb[] = {
> + {
> + .pixfmt = V4L2_PIX_FMT_MPEG1,
> + .min_buffers = 8,
> + .max_buffers = 8,
> + .max_width = 1920,
> + .max_height = 1080,
> + .vdec_ops = &vdec_1_ops,
> + .codec_ops = &codec_mpeg12_ops,
> + .firmware_path = "meson/gx/vmpeg12_mc",
> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
> + }, {
> + .pixfmt = V4L2_PIX_FMT_MPEG2,
> + .min_buffers = 8,
> + .max_buffers = 8,
> + .max_width = 1920,
> + .max_height = 1080,
> + .vdec_ops = &vdec_1_ops,
> + .codec_ops = &codec_mpeg12_ops,
> + .firmware_path = "meson/gx/vmpeg12_mc",
> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
> + },
> +};
> +
> +static const struct amvdec_format vdec_formats_gxl[] = {
> + {
> + .pixfmt = V4L2_PIX_FMT_MPEG1,
> + .min_buffers = 8,
> + .max_buffers = 8,
> + .max_width = 1920,
> + .max_height = 1080,
> + .vdec_ops = &vdec_1_ops,
> + .codec_ops = &codec_mpeg12_ops,
> + .firmware_path = "meson/gx/vmpeg12_mc",
> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
> + }, {
> + .pixfmt = V4L2_PIX_FMT_MPEG2,
> + .min_buffers = 8,
> + .max_buffers = 8,
> + .max_width = 1920,
> + .max_height = 1080,
> + .vdec_ops = &vdec_1_ops,
> + .codec_ops = &codec_mpeg12_ops,
> + .firmware_path = "meson/gx/vmpeg12_mc",
> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
> + },
> +};
> +
> +static const struct amvdec_format vdec_formats_gxm[] = {
> + {
> + .pixfmt = V4L2_PIX_FMT_MPEG1,
> + .min_buffers = 8,
> + .max_buffers = 8,
> + .max_width = 1920,
> + .max_height = 1080,
> + .vdec_ops = &vdec_1_ops,
> + .codec_ops = &codec_mpeg12_ops,
> + .firmware_path = "meson/gx/vmpeg12_mc",
> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
> + }, {
> + .pixfmt = V4L2_PIX_FMT_MPEG2,
> + .min_buffers = 8,
> + .max_buffers = 8,
> + .max_width = 1920,
> + .max_height = 1080,
> + .vdec_ops = &vdec_1_ops,
> + .codec_ops = &codec_mpeg12_ops,
> + .firmware_path = "meson/gx/vmpeg12_mc",
> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
> + },
> +};
> +
> +const struct vdec_platform vdec_platform_gxbb = {
> + .formats = vdec_formats_gxbb,
> + .num_formats = ARRAY_SIZE(vdec_formats_gxbb),
> + .revision = VDEC_REVISION_GXBB,
> +};
> +
> +const struct vdec_platform vdec_platform_gxl = {
> + .formats = vdec_formats_gxl,
> + .num_formats = ARRAY_SIZE(vdec_formats_gxl),
> + .revision = VDEC_REVISION_GXL,
> +};
> +
> +const struct vdec_platform vdec_platform_gxm = {
> + .formats = vdec_formats_gxm,
> + .num_formats = ARRAY_SIZE(vdec_formats_gxm),
> + .revision = VDEC_REVISION_GXM,
> +};
> diff --git a/drivers/media/platform/meson/vdec/vdec_platform.h b/drivers/media/platform/meson/vdec/vdec_platform.h
> new file mode 100644
> index 000000000000..f6025326db1d
> --- /dev/null
> +++ b/drivers/media/platform/meson/vdec/vdec_platform.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2018 BayLibre, SAS
> + * Author: Maxime Jourdan <[email protected]>
> + */
> +
> +#ifndef __MESON_VDEC_PLATFORM_H_
> +#define __MESON_VDEC_PLATFORM_H_
> +
> +#include "vdec.h"
> +
> +struct amvdec_format;
> +
> +enum vdec_revision {
> + VDEC_REVISION_GXBB,
> + VDEC_REVISION_GXL,
> + VDEC_REVISION_GXM,
> +};
> +
> +struct vdec_platform {
> + const struct amvdec_format *formats;
> + const u32 num_formats;
> + enum vdec_revision revision;
> +};
> +
> +extern const struct vdec_platform vdec_platform_gxbb;
> +extern const struct vdec_platform vdec_platform_gxm;
> +extern const struct vdec_platform vdec_platform_gxl;
> +
> +#endif
>

Regards,

Hans

2018-09-03 12:06:40

by Maxime Jourdan

[permalink] [raw]
Subject: Re: [PATCH 2/4] media: meson: add v4l2 m2m video decoder driver

Hi Hans, thanks for the review!

2018-09-03 13:18 GMT+02:00 Hans Verkuil <[email protected]>:
> Hi Maxime,
>
> Thank you for this patch series, nice to see amlogic becoming supported as well!
>
> On 08/31/2018 10:52 AM, Maxime Jourdan wrote:
>> Amlogic SoCs feature a powerful video decoder unit able to
>> decode many formats, with a performance of usually up to 4k60.
>>
>> This is a driver for this IP that is based around the v4l2 m2m framework.
>>
>> It features decoding for:
>> - MPEG 1
>> - MPEG 2
>>
>> Supported SoCs are: GXBB (S905), GXL (S905X/W/D), GXM (S912)
>>
>> There is also a hardware bitstream parser (ESPARSER) that is handled here.
>>
>> Signed-off-by: Maxime Jourdan <[email protected]>
>> ---
>> drivers/media/platform/Kconfig | 10 +
>> drivers/media/platform/meson/Makefile | 1 +
>> drivers/media/platform/meson/vdec/Makefile | 8 +
>> .../media/platform/meson/vdec/codec_mpeg12.c | 170 +++
>> .../media/platform/meson/vdec/codec_mpeg12.h | 14 +
>> drivers/media/platform/meson/vdec/dos_regs.h | 98 ++
>> drivers/media/platform/meson/vdec/esparser.c | 368 +++++++
>> drivers/media/platform/meson/vdec/esparser.h | 28 +
>> drivers/media/platform/meson/vdec/vdec.c | 988 ++++++++++++++++++
>> drivers/media/platform/meson/vdec/vdec.h | 234 +++++
>> drivers/media/platform/meson/vdec/vdec_1.c | 228 ++++
>> drivers/media/platform/meson/vdec/vdec_1.h | 14 +
>> .../media/platform/meson/vdec/vdec_helpers.c | 354 +++++++
>> .../media/platform/meson/vdec/vdec_helpers.h | 45 +
>> .../media/platform/meson/vdec/vdec_platform.c | 101 ++
>> .../media/platform/meson/vdec/vdec_platform.h | 30 +
>
> Missing MAINTAINERS file update.

Ack.

>> 16 files changed, 2691 insertions(+)
>> create mode 100644 drivers/media/platform/meson/vdec/Makefile
>> create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.c
>> create mode 100644 drivers/media/platform/meson/vdec/codec_mpeg12.h
>> create mode 100644 drivers/media/platform/meson/vdec/dos_regs.h
>> create mode 100644 drivers/media/platform/meson/vdec/esparser.c
>> create mode 100644 drivers/media/platform/meson/vdec/esparser.h
>> create mode 100644 drivers/media/platform/meson/vdec/vdec.c
>> create mode 100644 drivers/media/platform/meson/vdec/vdec.h
>> create mode 100644 drivers/media/platform/meson/vdec/vdec_1.c
>> create mode 100644 drivers/media/platform/meson/vdec/vdec_1.h
>> create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.c
>> create mode 100644 drivers/media/platform/meson/vdec/vdec_helpers.h
>> create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.c
>> create mode 100644 drivers/media/platform/meson/vdec/vdec_platform.h
>>
>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
>> index 2728376b04b5..1c33d95dd92f 100644
>> --- a/drivers/media/platform/Kconfig
>> +++ b/drivers/media/platform/Kconfig
>> @@ -482,6 +482,16 @@ config VIDEO_QCOM_VENUS
>> on various Qualcomm SoCs.
>> To compile this driver as a module choose m here.
>>
>> +config VIDEO_MESON_VDEC
>> + tristate "Amlogic video decoder driver"
>> + depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA
>> + depends on (ARCH_MESON) || COMPILE_TEST
>
> Why is ARCH_MESON between parenthesis?

Oops. Ack.

>> + select VIDEOBUF2_DMA_CONTIG
>> + select V4L2_MEM2MEM_DEV
>> + select MESON_CANVAS
>> + help
>> + Support for the video decoder found in gxbb/gxl/gxm chips.
>> +
>> endif # V4L_MEM2MEM_DRIVERS
>>
>> # TI VIDEO PORT Helper Modules
>> diff --git a/drivers/media/platform/meson/Makefile b/drivers/media/platform/meson/Makefile
>> index 597beb8f34d1..f7c6e1031f25 100644
>> --- a/drivers/media/platform/meson/Makefile
>> +++ b/drivers/media/platform/meson/Makefile
>> @@ -1 +1,2 @@
>> obj-$(CONFIG_VIDEO_MESON_AO_CEC) += ao-cec.o
>> +obj-$(CONFIG_VIDEO_MESON_VDEC) += vdec/
>> diff --git a/drivers/media/platform/meson/vdec/Makefile b/drivers/media/platform/meson/vdec/Makefile
>> new file mode 100644
>> index 000000000000..6bea129084b7
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/Makefile
>> @@ -0,0 +1,8 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +# Makefile for Amlogic meson video decoder driver
>> +
>> +meson-vdec-objs = esparser.o vdec.o vdec_helpers.o vdec_platform.o
>> +meson-vdec-objs += vdec_1.o
>> +meson-vdec-objs += codec_mpeg12.o
>> +
>> +obj-$(CONFIG_VIDEO_MESON_VDEC) += meson-vdec.o
>> diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.c b/drivers/media/platform/meson/vdec/codec_mpeg12.c
>> new file mode 100644
>> index 000000000000..18709319cff7
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/codec_mpeg12.c
>> @@ -0,0 +1,170 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#include <media/v4l2-mem2mem.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "vdec_helpers.h"
>> +#include "dos_regs.h"
>> +
>> +#define SIZE_WORKSPACE SZ_128K
>> +/* Offset substracted by the firmware from the workspace paddr */
>> +#define WORKSPACE_OFFSET (5 * SZ_1K)
>> +
>> +/* map firmware registers to known MPEG1/2 functions */
>> +#define MREG_SEQ_INFO AV_SCRATCH_4
>> +#define MREG_PIC_INFO AV_SCRATCH_5
>> +#define MREG_PIC_WIDTH AV_SCRATCH_6
>> +#define MREG_PIC_HEIGHT AV_SCRATCH_7
>> +#define MREG_BUFFERIN AV_SCRATCH_8
>> +#define MREG_BUFFEROUT AV_SCRATCH_9
>> +#define MREG_CMD AV_SCRATCH_A
>> +#define MREG_CO_MV_START AV_SCRATCH_B
>> +#define MREG_ERROR_COUNT AV_SCRATCH_C
>> +#define MREG_FRAME_OFFSET AV_SCRATCH_D
>> +#define MREG_WAIT_BUFFER AV_SCRATCH_E
>> +#define MREG_FATAL_ERROR AV_SCRATCH_F
>> +
>> +#define PICINFO_PROG 0x00008000
>> +#define PICINFO_TOP_FIRST 0x00002000
>> +
>> +struct codec_mpeg12 {
>> + /* Buffer for the MPEG1/2 Workspace */
>> + void *workspace_vaddr;
>> + dma_addr_t workspace_paddr;
>> +};
>> +
>> +static int codec_mpeg12_can_recycle(struct amvdec_core *core)
>> +{
>> + return !amvdec_read_dos(core, MREG_BUFFERIN);
>> +}
>> +
>> +static void codec_mpeg12_recycle(struct amvdec_core *core, u32 buf_idx)
>> +{
>> + amvdec_write_dos(core, MREG_BUFFERIN, buf_idx + 1);
>> +}
>> +
>> +static int codec_mpeg12_start(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> + struct codec_mpeg12 *mpeg12 = sess->priv;
>> + int ret;
>> +
>> + mpeg12 = kzalloc(sizeof(*mpeg12), GFP_KERNEL);
>> + if (!mpeg12)
>> + return -ENOMEM;
>> +
>> + /* Allocate some memory for the MPEG1/2 decoder's state */
>> + mpeg12->workspace_vaddr = dma_alloc_coherent(core->dev, SIZE_WORKSPACE,
>> + &mpeg12->workspace_paddr,
>> + GFP_KERNEL);
>> + if (!mpeg12->workspace_vaddr) {
>> + dev_err(core->dev, "Failed to request MPEG 1/2 Workspace\n");
>> + ret = -ENOMEM;
>> + goto free_mpeg12;
>> + }
>> +
>> + ret = amvdec_set_canvases(sess, (u32[]){ AV_SCRATCH_0, 0 },
>> + (u32[]){ 8, 0 });
>> + if (ret)
>> + goto free_workspace;
>> +
>> + amvdec_write_dos(core, POWER_CTL_VLD, BIT(4));
>> + amvdec_write_dos(core, MREG_CO_MV_START,
>> + mpeg12->workspace_paddr + WORKSPACE_OFFSET);
>> +
>> + amvdec_write_dos(core, MPEG1_2_REG, 0);
>> + amvdec_write_dos(core, PSCALE_CTRL, 0);
>> + amvdec_write_dos(core, PIC_HEAD_INFO, 0x380);
>> + amvdec_write_dos(core, M4_CONTROL_REG, 0);
>> + amvdec_write_dos(core, MREG_BUFFERIN, 0);
>> + amvdec_write_dos(core, MREG_BUFFEROUT, 0);
>> + amvdec_write_dos(core, MREG_CMD, (sess->width << 16) | sess->height);
>> + amvdec_write_dos(core, MREG_ERROR_COUNT, 0);
>> + amvdec_write_dos(core, MREG_FATAL_ERROR, 0);
>> + amvdec_write_dos(core, MREG_WAIT_BUFFER, 0);
>> +
>> + sess->keyframe_found = 1;
>> + sess->priv = mpeg12;
>> +
>> + return 0;
>> +
>> +free_workspace:
>> + dma_free_coherent(core->dev, SIZE_WORKSPACE, mpeg12->workspace_vaddr,
>> + mpeg12->workspace_paddr);
>> +free_mpeg12:
>> + kfree(mpeg12);
>> +
>> + return ret;
>> +}
>> +
>> +static int codec_mpeg12_stop(struct amvdec_session *sess)
>> +{
>> + struct codec_mpeg12 *mpeg12 = sess->priv;
>> + struct amvdec_core *core = sess->core;
>> +
>> + if (mpeg12->workspace_vaddr)
>> + dma_free_coherent(core->dev, SIZE_WORKSPACE,
>> + mpeg12->workspace_vaddr,
>> + mpeg12->workspace_paddr);
>> +
>> + return 0;
>> +}
>> +
>> +static irqreturn_t codec_mpeg12_threaded_isr(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> + u32 reg;
>> + u32 pic_info;
>> + u32 is_progressive;
>> + u32 buffer_index;
>> + u32 field = V4L2_FIELD_NONE;
>> +
>> + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1);
>> + reg = amvdec_read_dos(core, MREG_FATAL_ERROR);
>> + if (reg == 1) {
>> + dev_err(core->dev, "MPEG1/2 fatal error\n");
>> + amvdec_abort(sess);
>> + return IRQ_HANDLED;
>> + }
>> +
>> + reg = amvdec_read_dos(core, MREG_BUFFEROUT);
>> + if (!reg)
>> + return IRQ_HANDLED;
>> +
>> + /* Unclear what this means */
>> + if ((reg & GENMASK(23, 17)) == GENMASK(23, 17))
>> + goto end;
>> +
>> + pic_info = amvdec_read_dos(core, MREG_PIC_INFO);
>> + is_progressive = pic_info & PICINFO_PROG;
>> +
>> + if (!is_progressive)
>> + field = (pic_info & PICINFO_TOP_FIRST) ?
>> + V4L2_FIELD_INTERLACED_TB :
>> + V4L2_FIELD_INTERLACED_BT;
>> +
>> + buffer_index = ((reg & 0xf) - 1) & 7;
>> + amvdec_dst_buf_done_idx(sess, buffer_index, field);
>> +
>> +end:
>> + amvdec_write_dos(core, MREG_BUFFEROUT, 0);
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t codec_mpeg12_isr(struct amvdec_session *sess)
>> +{
>> + return IRQ_WAKE_THREAD;
>> +}
>> +
>> +struct amvdec_codec_ops codec_mpeg12_ops = {
>> + .start = codec_mpeg12_start,
>> + .stop = codec_mpeg12_stop,
>> + .isr = codec_mpeg12_isr,
>> + .threaded_isr = codec_mpeg12_threaded_isr,
>> + .can_recycle = codec_mpeg12_can_recycle,
>> + .recycle = codec_mpeg12_recycle,
>> +};
>> diff --git a/drivers/media/platform/meson/vdec/codec_mpeg12.h b/drivers/media/platform/meson/vdec/codec_mpeg12.h
>> new file mode 100644
>> index 000000000000..43cab5f39ca0
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/codec_mpeg12.h
>> @@ -0,0 +1,14 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_CODEC_MPEG12_H_
>> +#define __MESON_VDEC_CODEC_MPEG12_H_
>> +
>> +#include "vdec.h"
>> +
>> +extern struct amvdec_codec_ops codec_mpeg12_ops;
>> +
>> +#endif
>> diff --git a/drivers/media/platform/meson/vdec/dos_regs.h b/drivers/media/platform/meson/vdec/dos_regs.h
>> new file mode 100644
>> index 000000000000..abd810542dbb
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/dos_regs.h
>> @@ -0,0 +1,98 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_DOS_REGS_H_
>> +#define __MESON_VDEC_DOS_REGS_H_
>> +
>> +/* DOS registers */
>> +#define VDEC_ASSIST_AMR1_INT8 0x00b4
>> +
>> +#define ASSIST_MBOX1_CLR_REG 0x01d4
>> +#define ASSIST_MBOX1_MASK 0x01d8
>> +
>> +#define MPSR 0x0c04
>> +#define MCPU_INTR_MSK 0x0c10
>> +#define CPSR 0x0c84
>> +
>> +#define IMEM_DMA_CTRL 0x0d00
>> +#define IMEM_DMA_ADR 0x0d04
>> +#define IMEM_DMA_COUNT 0x0d08
>> +#define LMEM_DMA_CTRL 0x0d40
>> +
>> +#define MC_STATUS0 0x2424
>> +#define MC_CTRL1 0x242c
>> +
>> +#define PSCALE_RST 0x2440
>> +#define PSCALE_CTRL 0x2444
>> +#define PSCALE_BMEM_ADDR 0x247c
>> +#define PSCALE_BMEM_DAT 0x2480
>> +
>> +#define DBLK_CTRL 0x2544
>> +#define DBLK_STATUS 0x254c
>> +
>> +#define GCLK_EN 0x260c
>> +#define MDEC_PIC_DC_CTRL 0x2638
>> +#define MDEC_PIC_DC_STATUS 0x263c
>> +#define ANC0_CANVAS_ADDR 0x2640
>> +#define MDEC_PIC_DC_THRESH 0x26e0
>> +
>> +/* Firmware interface registers */
>> +#define AV_SCRATCH_0 0x2700
>> +#define AV_SCRATCH_1 0x2704
>> +#define AV_SCRATCH_2 0x2708
>> +#define AV_SCRATCH_3 0x270c
>> +#define AV_SCRATCH_4 0x2710
>> +#define AV_SCRATCH_5 0x2714
>> +#define AV_SCRATCH_6 0x2718
>> +#define AV_SCRATCH_7 0x271c
>> +#define AV_SCRATCH_8 0x2720
>> +#define AV_SCRATCH_9 0x2724
>> +#define AV_SCRATCH_A 0x2728
>> +#define AV_SCRATCH_B 0x272c
>> +#define AV_SCRATCH_C 0x2730
>> +#define AV_SCRATCH_D 0x2734
>> +#define AV_SCRATCH_E 0x2738
>> +#define AV_SCRATCH_F 0x273c
>> +#define AV_SCRATCH_G 0x2740
>> +#define AV_SCRATCH_H 0x2744
>> +#define AV_SCRATCH_I 0x2748
>> +#define AV_SCRATCH_J 0x274c
>> +#define AV_SCRATCH_K 0x2750
>> +#define AV_SCRATCH_L 0x2754
>> +
>> +#define MPEG1_2_REG 0x3004
>> +#define PIC_HEAD_INFO 0x300c
>> +#define POWER_CTL_VLD 0x3020
>> +#define M4_CONTROL_REG 0x30a4
>> +
>> +/* Stream Buffer (stbuf) regs */
>> +#define VLD_MEM_VIFIFO_START_PTR 0x3100
>> +#define VLD_MEM_VIFIFO_CURR_PTR 0x3104
>> +#define VLD_MEM_VIFIFO_END_PTR 0x3108
>> +#define VLD_MEM_VIFIFO_CONTROL 0x3110
>> + #define MEM_FIFO_CNT_BIT 16
>> + #define MEM_FILL_ON_LEVEL BIT(10)
>> + #define MEM_CTRL_EMPTY_EN BIT(2)
>> + #define MEM_CTRL_FILL_EN BIT(1)
>> +#define VLD_MEM_VIFIFO_WP 0x3114
>> +#define VLD_MEM_VIFIFO_RP 0x3118
>> +#define VLD_MEM_VIFIFO_LEVEL 0x311c
>> +#define VLD_MEM_VIFIFO_BUF_CNTL 0x3120
>> + #define MEM_BUFCTRL_MANUAL BIT(1)
>> +#define VLD_MEM_VIFIFO_WRAP_COUNT 0x3144
>> +
>> +#define DCAC_DMA_CTRL 0x3848
>> +
>> +#define DOS_SW_RESET0 0xfc00
>> +#define DOS_GCLK_EN0 0xfc04
>> +#define DOS_GEN_CTRL0 0xfc08
>> +#define DOS_MEM_PD_VDEC 0xfcc0
>> +#define DOS_MEM_PD_HEVC 0xfccc
>> +#define DOS_SW_RESET3 0xfcd0
>> +#define DOS_GCLK_EN3 0xfcd4
>> +#define DOS_VDEC_MCRCC_STALL_CTRL 0xfd00
>> +
>> +#endif
>> diff --git a/drivers/media/platform/meson/vdec/esparser.c b/drivers/media/platform/meson/vdec/esparser.c
>> new file mode 100644
>> index 000000000000..098c7d76ad3f
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/esparser.c
>> @@ -0,0 +1,368 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + *
>> + * The Elementary Stream Parser is a HW bitstream parser.
>> + * It reads bitstream buffers and feeds them to the VIFIFO
>> + */
>> +
>> +#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/reset.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +#include <media/v4l2-mem2mem.h>
>> +
>> +#include "dos_regs.h"
>> +#include "esparser.h"
>> +#include "vdec_helpers.h"
>> +
>> +/* PARSER REGS (CBUS) */
>> +#define PARSER_CONTROL 0x00
>> + #define ES_PACK_SIZE_BIT 8
>> + #define ES_WRITE BIT(5)
>> + #define ES_SEARCH BIT(1)
>> + #define ES_PARSER_START BIT(0)
>> +#define PARSER_FETCH_ADDR 0x4
>> +#define PARSER_FETCH_CMD 0x8
>> +#define PARSER_CONFIG 0x14
>> + #define PS_CFG_MAX_FETCH_CYCLE_BIT 0
>> + #define PS_CFG_STARTCODE_WID_24_BIT 10
>> + #define PS_CFG_MAX_ES_WR_CYCLE_BIT 12
>> + #define PS_CFG_PFIFO_EMPTY_CNT_BIT 16
>> +#define PFIFO_WR_PTR 0x18
>> +#define PFIFO_RD_PTR 0x1c
>> +#define PARSER_SEARCH_PATTERN 0x24
>> + #define ES_START_CODE_PATTERN 0x00000100
>> +#define PARSER_SEARCH_MASK 0x28
>> + #define ES_START_CODE_MASK 0xffffff00
>> + #define FETCH_ENDIAN_BIT 27
>> +#define PARSER_INT_ENABLE 0x2c
>> + #define PARSER_INT_HOST_EN_BIT 8
>> +#define PARSER_INT_STATUS 0x30
>> + #define PARSER_INTSTAT_SC_FOUND 1
>> +#define PARSER_ES_CONTROL 0x5c
>> +#define PARSER_VIDEO_START_PTR 0x80
>> +#define PARSER_VIDEO_END_PTR 0x84
>> +#define PARSER_VIDEO_HOLE 0x90
>> +
>> +#define SEARCH_PATTERN_LEN 512
>> +#define MIN_PACKET_SIZE (4 * SZ_1K)
>> +
>> +/* Buffer to send to the ESPARSER to signal End Of Stream.
>> + * Credits to Endless Mobile.
>> + */
>> +#define EOS_TAIL_BUF_SIZE 1024
>> +static const u8 eos_tail_data[EOS_TAIL_BUF_SIZE] = {
>> + 0x00, 0x00, 0x00, 0x01, 0x06, 0x05, 0xff, 0xe4, 0xdc, 0x45, 0xe9, 0xbd,
>> + 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef,
>> + 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20,
>> + 0x36, 0x37, 0x20, 0x72, 0x31, 0x31, 0x33, 0x30, 0x20, 0x38, 0x34, 0x37,
>> + 0x35, 0x39, 0x37, 0x37, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34,
>> + 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20,
>> + 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79,
>> + 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30,
>> + 0x30, 0x39, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
>> + 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e,
>> + 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74,
>> + 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
>> + 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65,
>> + 0x66, 0x3d, 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d,
>> + 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73,
>> + 0x65, 0x3d, 0x30, 0x78, 0x31, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x31, 0x20,
>> + 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65,
>> + 0x3d, 0x36, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e,
>> + 0x30, 0x3a, 0x30, 0x2e, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f,
>> + 0x72, 0x65, 0x66, 0x3d, 0x30, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e,
>> + 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61,
>> + 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69,
>> + 0x73, 0x3d, 0x30, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x30,
>> + 0x20, 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a,
>> + 0x6f, 0x6e, 0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x63, 0x68,
>> + 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73,
>> + 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
>> + 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x63,
>> + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x6d, 0x62, 0x61, 0x66,
>> + 0x66, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d,
>> + 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30,
>> + 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d,
>> + 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d,
>> + 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69,
>> + 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x61, 0x74,
>> + 0x65, 0x74, 0x6f, 0x6c, 0x3d, 0x31, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f,
>> + 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69,
>> + 0x6e, 0x3d, 0x31, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x35,
>> + 0x31, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69,
>> + 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30,
>> + 0x20, 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80,
>> + 0x00, 0x00, 0x00, 0x01, 0x67, 0x4d, 0x40, 0x0a, 0x9a, 0x74, 0xf4, 0x20,
>> + 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x51, 0xe2, 0x44, 0xd4,
>> + 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x32, 0xc8, 0x00, 0x00, 0x00, 0x01,
>> + 0x65, 0x88, 0x80, 0x20, 0x00, 0x08, 0x7f, 0xea, 0x6a, 0xe2, 0x99, 0xb6,
>> + 0x57, 0xae, 0x49, 0x30, 0xf5, 0xfe, 0x5e, 0x46, 0x0b, 0x72, 0x44, 0xc4,
>> + 0xe1, 0xfc, 0x62, 0xda, 0xf1, 0xfb, 0xa2, 0xdb, 0xd6, 0xbe, 0x5c, 0xd7,
>> + 0x24, 0xa3, 0xf5, 0xb9, 0x2f, 0x57, 0x16, 0x49, 0x75, 0x47, 0x77, 0x09,
>> + 0x5c, 0xa1, 0xb4, 0xc3, 0x4f, 0x60, 0x2b, 0xb0, 0x0c, 0xc8, 0xd6, 0x66,
>> + 0xba, 0x9b, 0x82, 0x29, 0x33, 0x92, 0x26, 0x99, 0x31, 0x1c, 0x7f, 0x9b
>> +};
>> +
>> +static DECLARE_WAIT_QUEUE_HEAD(wq);
>> +static int search_done;
>> +
>> +static irqreturn_t esparser_isr(int irq, void *dev)
>> +{
>> + int int_status;
>> + struct amvdec_core *core = dev;
>> +
>> + int_status = amvdec_read_parser(core, PARSER_INT_STATUS);
>> + amvdec_write_parser(core, PARSER_INT_STATUS, int_status);
>> +
>> + if (int_status & PARSER_INTSTAT_SC_FOUND) {
>> + amvdec_write_parser(core, PFIFO_RD_PTR, 0);
>> + amvdec_write_parser(core, PFIFO_WR_PTR, 0);
>> + search_done = 1;
>> + wake_up_interruptible(&wq);
>> + }
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +/* Pad the packet to at least 4KiB bytes otherwise the VDEC unit won't trigger
>> + * ISRs.
>> + * Also append a start code 000001ff at the end to trigger
>> + * the ESPARSER interrupt.
>> + */
>> +static u32 esparser_pad_start_code(struct vb2_buffer *vb)
>> +{
>> + u32 payload_size = vb2_get_plane_payload(vb, 0);
>> + u32 pad_size = 0;
>> + u8 *vaddr = vb2_plane_vaddr(vb, 0) + payload_size;
>> +
>> + if (payload_size < MIN_PACKET_SIZE) {
>> + pad_size = MIN_PACKET_SIZE - payload_size;
>> + memset(vaddr, 0, pad_size);
>> + }
>> +
>> + memset(vaddr + pad_size, 0, SEARCH_PATTERN_LEN);
>> + vaddr[pad_size] = 0x00;
>> + vaddr[pad_size + 1] = 0x00;
>> + vaddr[pad_size + 2] = 0x01;
>> + vaddr[pad_size + 3] = 0xff;
>> +
>> + return pad_size;
>> +}
>> +
>> +static int
>> +esparser_write_data(struct amvdec_core *core, dma_addr_t addr, u32 size)
>> +{
>> + amvdec_write_parser(core, PFIFO_RD_PTR, 0);
>> + amvdec_write_parser(core, PFIFO_WR_PTR, 0);
>> + amvdec_write_parser(core, PARSER_CONTROL,
>> + ES_WRITE |
>> + ES_PARSER_START |
>> + ES_SEARCH |
>> + (size << ES_PACK_SIZE_BIT));
>> +
>> + amvdec_write_parser(core, PARSER_FETCH_ADDR, addr);
>> + amvdec_write_parser(core, PARSER_FETCH_CMD,
>> + (7 << FETCH_ENDIAN_BIT) |
>> + (size + SEARCH_PATTERN_LEN));
>> +
>> + search_done = 0;
>> + return wait_event_interruptible_timeout(wq, search_done, (HZ / 5));
>> +}
>> +
>> +static u32 esparser_vififo_get_free_space(struct amvdec_session *sess)
>> +{
>> + u32 vififo_usage;
>> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
>> + struct amvdec_core *core = sess->core;
>> +
>> + vififo_usage = vdec_ops->vififo_level(sess);
>> + vififo_usage += amvdec_read_parser(core, PARSER_VIDEO_HOLE);
>> + vififo_usage += (6 * SZ_1K);
>> +
>> + if (vififo_usage > sess->vififo_size) {
>> + dev_warn(sess->core->dev,
>> + "VIFIFO usage (%u) > VIFIFO size (%u)\n",
>> + vififo_usage, sess->vififo_size);
>> + return 0;
>> + }
>> +
>> + return sess->vififo_size - vififo_usage;
>> +}
>> +
>> +int esparser_queue_eos(struct amvdec_core *core)
>> +{
>> + struct device *dev = core->dev;
>> + void *eos_vaddr;
>> + dma_addr_t eos_paddr;
>> + int ret;
>> +
>> + eos_vaddr = dma_alloc_coherent(dev,
>> + EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN,
>> + &eos_paddr, GFP_KERNEL);
>> + if (!eos_vaddr)
>> + return -ENOMEM;
>> +
>> + memset(eos_vaddr, 0, EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN);
>> + memcpy(eos_vaddr, eos_tail_data, sizeof(eos_tail_data));
>> + ret = esparser_write_data(core, eos_paddr, EOS_TAIL_BUF_SIZE);
>> + dma_free_coherent(dev, EOS_TAIL_BUF_SIZE + SEARCH_PATTERN_LEN,
>> + eos_vaddr, eos_paddr);
>> +
>> + return ret;
>> +}
>> +
>> +static int
>> +esparser_queue(struct amvdec_session *sess, struct vb2_v4l2_buffer *vbuf)
>> +{
>> + int ret;
>> + struct vb2_buffer *vb = &vbuf->vb2_buf;
>> + struct amvdec_core *core = sess->core;
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> + u32 num_dst_bufs = 0;
>> + u32 payload_size = vb2_get_plane_payload(vb, 0);
>> + dma_addr_t phy = vb2_dma_contig_plane_dma_addr(vb, 0);
>> + u32 pad_size;
>> +
>> + if (!payload_size) {
>> + esparser_queue_eos(core);
>> + return 0;
>> + }
>> +
>> + if (codec_ops->num_pending_bufs)
>> + num_dst_bufs = codec_ops->num_pending_bufs(sess);
>> +
>> + num_dst_bufs += v4l2_m2m_num_dst_bufs_ready(sess->m2m_ctx);
>> +
>> + if (esparser_vififo_get_free_space(sess) < payload_size ||
>> + atomic_read(&sess->esparser_queued_bufs) >= num_dst_bufs)
>> + return -EAGAIN;
>> +
>> + v4l2_m2m_src_buf_remove_by_buf(sess->m2m_ctx, vbuf);
>> + amvdec_add_ts_reorder(sess, vb->timestamp);
>> + dev_dbg(core->dev, "esparser: Queuing ts = %llu ; pld_size = %u\n",
>> + vb->timestamp, payload_size);
>> +
>> + pad_size = esparser_pad_start_code(vb);
>> + ret = esparser_write_data(core, phy, payload_size + pad_size);
>> +
>> + if (ret > 0) {
>> + /* We need to wait until we parse/decode the first keyframe.
>> + * All buffers prior to the first keyframe must be dropped.
>> + */
>> + if (!sess->keyframe_found)
>> + usleep_range(1000, 2000);
>> +
>> + if (sess->keyframe_found)
>> + atomic_inc(&sess->esparser_queued_bufs);
>> + else
>> + amvdec_remove_ts(sess, vb->timestamp);
>> +
>> + vbuf->flags = 0;
>> + vbuf->field = V4L2_FIELD_NONE;
>> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
>> + return 0;
>> + }
>> +
>> + dev_warn(core->dev, "esparser: input parsing error\n");
>> + amvdec_remove_ts(sess, vb->timestamp);
>> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
>> + amvdec_write_parser(core, PARSER_FETCH_CMD, 0);
>> +
>> + return 0;
>> +}
>> +
>> +void esparser_queue_all_src(struct work_struct *work)
>> +{
>> + struct v4l2_m2m_buffer *buf, *n;
>> + struct amvdec_session *sess =
>> + container_of(work, struct amvdec_session, esparser_queue_work);
>> +
>> + mutex_lock(&sess->lock);
>> + v4l2_m2m_for_each_src_buf_safe(sess->m2m_ctx, buf, n) {
>> + if (esparser_queue(sess, &buf->vb) < 0)
>> + break;
>> +
>> + /* Some codecs don't like having data queued in too fast */
>
> This needs some more extensive explanation. Which codecs? Why is this is
> problem? Why is 1 ms delay sufficient?
>
> It's weird and unexpected, so that needs better documentation.

I'm not a big fan of it as well, but I've had cases where decoding
sessions froze without this sleep.

I'll make sure to determine exactly what is going on and what codecs
actually need that.

>> + usleep_range(1000, 2000);
>> + }
>> + mutex_unlock(&sess->lock);
>> +}
>> +
>> +int esparser_power_up(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
>> +
>> + reset_control_reset(core->esparser_reset);
>> + amvdec_write_parser(core, PARSER_CONFIG,
>> + (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) |
>> + (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) |
>> + (16 << PS_CFG_MAX_FETCH_CYCLE_BIT));
>> +
>> + amvdec_write_parser(core, PFIFO_RD_PTR, 0);
>> + amvdec_write_parser(core, PFIFO_WR_PTR, 0);
>> +
>> + amvdec_write_parser(core, PARSER_SEARCH_PATTERN,
>> + ES_START_CODE_PATTERN);
>> + amvdec_write_parser(core, PARSER_SEARCH_MASK, ES_START_CODE_MASK);
>> +
>> + amvdec_write_parser(core, PARSER_CONFIG,
>> + (10 << PS_CFG_PFIFO_EMPTY_CNT_BIT) |
>> + (1 << PS_CFG_MAX_ES_WR_CYCLE_BIT) |
>> + (16 << PS_CFG_MAX_FETCH_CYCLE_BIT) |
>> + (2 << PS_CFG_STARTCODE_WID_24_BIT));
>> +
>> + amvdec_write_parser(core, PARSER_CONTROL,
>> + (ES_SEARCH | ES_PARSER_START));
>> +
>> + amvdec_write_parser(core, PARSER_VIDEO_START_PTR, sess->vififo_paddr);
>> + amvdec_write_parser(core, PARSER_VIDEO_END_PTR,
>> + sess->vififo_paddr + sess->vififo_size - 8);
>> + amvdec_write_parser(core, PARSER_ES_CONTROL,
>> + amvdec_read_parser(core, PARSER_ES_CONTROL) & ~1);
>> +
>> + if (vdec_ops->conf_esparser)
>> + vdec_ops->conf_esparser(sess);
>> +
>> + amvdec_write_parser(core, PARSER_INT_STATUS, 0xffff);
>> + amvdec_write_parser(core, PARSER_INT_ENABLE,
>> + BIT(PARSER_INT_HOST_EN_BIT));
>> +
>> + return 0;
>> +}
>> +
>> +int esparser_init(struct platform_device *pdev, struct amvdec_core *core)
>> +{
>> + struct device *dev = &pdev->dev;
>> + int ret;
>> + int irq;
>> +
>> + irq = platform_get_irq(pdev, 1);
>> + if (irq < 0) {
>> + dev_err(dev, "Failed getting ESPARSER IRQ from dtb\n");
>> + return irq;
>> + }
>> +
>> + ret = devm_request_irq(dev, irq, esparser_isr, IRQF_SHARED,
>> + "esparserirq", core);
>> + if (ret) {
>> + dev_err(dev, "Failed requesting ESPARSER IRQ\n");
>> + return ret;
>> + }
>> +
>> + core->esparser_reset =
>> + devm_reset_control_get_exclusive(dev, "esparser");
>> + if (IS_ERR(core->esparser_reset)) {
>> + dev_err(dev, "Failed to get esparser_reset\n");
>> + return PTR_ERR(core->esparser_reset);
>> + }
>> +
>> + return 0;
>> +}
>> diff --git a/drivers/media/platform/meson/vdec/esparser.h b/drivers/media/platform/meson/vdec/esparser.h
>> new file mode 100644
>> index 000000000000..22c2ac5c6d35
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/esparser.h
>> @@ -0,0 +1,28 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_ESPARSER_H_
>> +#define __MESON_VDEC_ESPARSER_H_
>> +
>> +#include "vdec.h"
>> +
>> +int esparser_init(struct platform_device *pdev, struct amvdec_core *core);
>> +int esparser_power_up(struct amvdec_session *sess);
>> +
>> +/**
>> + * esparser_queue_eos() - write End Of Stream sequence to the ESPARSER
>> + *
>> + * @core vdec core struct
>> + */
>> +int esparser_queue_eos(struct amvdec_core *core);
>> +
>> +/**
>> + * esparser_queue_all_src() - work handler that writes as many src buffers
>> + * as possible to the ESPARSER
>> + */
>> +void esparser_queue_all_src(struct work_struct *work);
>> +
>> +#endif
>> diff --git a/drivers/media/platform/meson/vdec/vdec.c b/drivers/media/platform/meson/vdec/vdec.c
>> new file mode 100644
>> index 000000000000..32e1e2228297
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec.c
>> @@ -0,0 +1,988 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#include <linux/of_device.h>
>> +#include <linux/clk.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/slab.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-mem2mem.h>
>> +#include <media/v4l2-dev.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "vdec.h"
>> +#include "esparser.h"
>> +#include "vdec_helpers.h"
>> +
>> +struct dummy_buf {
>> + struct vb2_v4l2_buffer vb;
>> + struct list_head list;
>> +};
>> +
>> +/* 16 MiB for parsed bitstream swap exchange */
>> +#define SIZE_VIFIFO SZ_16M
>> +
>> +static u32 get_output_size(u32 width, u32 height)
>> +{
>> + return ALIGN(width * height, SZ_64K);
>> +}
>> +
>> +u32 amvdec_get_output_size(struct amvdec_session *sess)
>> +{
>> + return get_output_size(sess->width, sess->height);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_get_output_size);
>> +
>> +static int vdec_codec_needs_recycle(struct amvdec_session *sess)
>> +{
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> +
>> + return codec_ops->can_recycle && codec_ops->recycle;
>> +}
>> +
>> +static int vdec_recycle_thread(void *data)
>> +{
>> + struct amvdec_session *sess = data;
>> + struct amvdec_core *core = sess->core;
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> + struct amvdec_buffer *tmp, *n;
>> +
>> + while (!kthread_should_stop()) {
>> + mutex_lock(&sess->bufs_recycle_lock);
>> + list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
>> + if (!codec_ops->can_recycle(core))
>> + break;
>> +
>> + codec_ops->recycle(core, tmp->vb->index);
>> + dev_dbg(core->dev, "Buffer %d recycled\n",
>> + tmp->vb->index);
>> + list_del(&tmp->list);
>> + kfree(tmp);
>> + }
>> + mutex_unlock(&sess->bufs_recycle_lock);
>> +
>> + usleep_range(5000, 10000);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_poweron(struct amvdec_session *sess)
>> +{
>> + int ret;
>> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
>> +
>> + ret = clk_prepare_enable(sess->core->dos_parser_clk);
>> + if (ret)
>> + return ret;
>> +
>> + ret = clk_prepare_enable(sess->core->dos_clk);
>> + if (ret)
>> + goto disable_dos_parser;
>> +
>> + ret = vdec_ops->start(sess);
>> + if (ret)
>> + goto disable_dos;
>> +
>> + esparser_power_up(sess);
>> +
>> + return 0;
>> +
>> +disable_dos:
>> + clk_disable_unprepare(sess->core->dos_clk);
>> +disable_dos_parser:
>> + clk_disable_unprepare(sess->core->dos_parser_clk);
>> +
>> + return ret;
>> +}
>> +
>> +static void vdec_wait_inactive(struct amvdec_session *sess)
>> +{
>> + /* We consider 50ms with no IRQ to be inactive. */
>> + while (time_is_after_jiffies64(sess->last_irq_jiffies +
>> + msecs_to_jiffies(50)))
>> + msleep(25);
>> +}
>> +
>> +static void vdec_poweroff(struct amvdec_session *sess)
>> +{
>> + struct amvdec_ops *vdec_ops = sess->fmt_out->vdec_ops;
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> +
>> + vdec_wait_inactive(sess);
>> + if (codec_ops->drain)
>> + codec_ops->drain(sess);
>> +
>> + vdec_ops->stop(sess);
>> + clk_disable_unprepare(sess->core->dos_clk);
>> + clk_disable_unprepare(sess->core->dos_parser_clk);
>> +}
>> +
>> +static void
>> +vdec_queue_recycle(struct amvdec_session *sess, struct vb2_buffer *vb)
>> +{
>> + struct amvdec_buffer *new_buf;
>> +
>> + new_buf = kmalloc(sizeof(*new_buf), GFP_KERNEL);
>> + new_buf->vb = vb;
>> +
>> + mutex_lock(&sess->bufs_recycle_lock);
>> + list_add_tail(&new_buf->list, &sess->bufs_recycle);
>> + mutex_unlock(&sess->bufs_recycle_lock);
>> +}
>> +
>> +static void vdec_m2m_device_run(void *priv)
>> +{
>> + struct amvdec_session *sess = priv;
>> +
>> + schedule_work(&sess->esparser_queue_work);
>> +}
>> +
>> +static void vdec_m2m_job_abort(void *priv)
>> +{
>> + struct amvdec_session *sess = priv;
>> +
>> + v4l2_m2m_job_finish(sess->m2m_dev, sess->m2m_ctx);
>> +}
>> +
>> +static const struct v4l2_m2m_ops vdec_m2m_ops = {
>> + .device_run = vdec_m2m_device_run,
>> + .job_abort = vdec_m2m_job_abort,
>> +};
>> +
>> +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 amvdec_session *sess = vb2_get_drv_priv(q);
>> + struct amvdec_core *core = sess->core;
>> + const struct amvdec_format *fmt_out = sess->fmt_out;
>> + u32 pixfmt_cap = sess->pixfmt_cap;
>> +
>> + switch (q->type) {
>> + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
>> + sizes[0] = amvdec_get_output_size(sess);
>> + *num_planes = 1;
>> + break;
>> + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
>> + if (pixfmt_cap == V4L2_PIX_FMT_NV12M) {
>> + sizes[0] = amvdec_get_output_size(sess);
>> + sizes[1] = amvdec_get_output_size(sess) / 2;
>> + *num_planes = 2;
>> + } else if (pixfmt_cap == V4L2_PIX_FMT_YUV420M) {
>> + sizes[0] = amvdec_get_output_size(sess);
>> + sizes[1] = amvdec_get_output_size(sess) / 4;
>> + sizes[2] = amvdec_get_output_size(sess) / 4;
>> + *num_planes = 3;
>> + }
>> + *num_buffers = min(max(*num_buffers, fmt_out->min_buffers),
>> + fmt_out->max_buffers);
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + mutex_lock(&core->lock);
>> + if (core->cur_sess && core->cur_sess != sess) {
>> + mutex_unlock(&core->lock);
>> + return -EBUSY;
>> + }
>> +
>> + core->cur_sess = sess;
>> + mutex_unlock(&core->lock);
>> +
>> + return 0;
>> +}
>> +
>> +static void vdec_vb2_buf_queue(struct vb2_buffer *vb)
>> +{
>> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
>> + struct amvdec_session *sess = vb2_get_drv_priv(vb->vb2_queue);
>> + struct v4l2_m2m_ctx *m2m_ctx = sess->m2m_ctx;
>> +
>> + mutex_lock(&sess->lock);
>> + v4l2_m2m_buf_queue(m2m_ctx, vbuf);
>> +
>> + if (!sess->streamon_out || !sess->streamon_cap)
>> + goto unlock;
>> +
>> + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
>> + vdec_codec_needs_recycle(sess))
>> + vdec_queue_recycle(sess, vb);
>> +
>> + schedule_work(&sess->esparser_queue_work);
>> +unlock:
>> + mutex_unlock(&sess->lock);
>> +}
>> +
>> +static int vdec_start_streaming(struct vb2_queue *q, unsigned int count)
>> +{
>> + struct amvdec_session *sess = vb2_get_drv_priv(q);
>> + struct vb2_v4l2_buffer *buf;
>> + int ret;
>> +
>> + mutex_lock(&sess->lock);
>> +
>> + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
>> + sess->streamon_out = 1;
>> + else
>> + sess->streamon_cap = 1;
>> +
>> + if (!sess->streamon_out || !sess->streamon_cap) {
>> + mutex_unlock(&sess->lock);
>> + return 0;
>> + }
>> +
>> + sess->vififo_size = SIZE_VIFIFO;
>> + sess->vififo_vaddr =
>> + dma_alloc_coherent(sess->core->dev, sess->vififo_size,
>> + &sess->vififo_paddr, GFP_KERNEL);
>> + if (!sess->vififo_vaddr) {
>> + dev_err(sess->core->dev, "Failed to request VIFIFO buffer\n");
>> + ret = -ENOMEM;
>> + goto bufs_done;
>> + }
>> +
>> + sess->should_stop = 0;
>> + sess->keyframe_found = 0;
>> + atomic_set(&sess->esparser_queued_bufs, 0);
>> + ret = vdec_poweron(sess);
>> + if (ret)
>> + goto vififo_free;
>> +
>> + sess->sequence_cap = 0;
>> + if (vdec_codec_needs_recycle(sess))
>> + sess->recycle_thread = kthread_run(vdec_recycle_thread, sess,
>> + "vdec_recycle");
>> + mutex_unlock(&sess->lock);
>> +
>> + return 0;
>> +
>> +vififo_free:
>> + dma_free_coherent(sess->core->dev, sess->vififo_size,
>> + sess->vififo_vaddr, sess->vififo_paddr);
>> +bufs_done:
>> + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
>> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
>> + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
>> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED);
>> +
>> + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
>> + sess->streamon_out = 0;
>> + else
>> + sess->streamon_cap = 0;
>> + mutex_unlock(&sess->lock);
>> + return ret;
>> +}
>> +
>> +static void vdec_free_canvas(struct amvdec_session *sess)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < sess->canvas_num; ++i)
>> + meson_canvas_free(sess->core->canvas, sess->canvas_alloc[i]);
>> +
>> + sess->canvas_num = 0;
>> +}
>> +
>> +static void vdec_reset_timestamps(struct amvdec_session *sess)
>> +{
>> + struct amvdec_timestamp *tmp, *n;
>> +
>> + list_for_each_entry_safe(tmp, n, &sess->timestamps, list) {
>> + list_del(&tmp->list);
>> + kfree(tmp);
>> + }
>> +}
>> +
>> +static void vdec_reset_bufs_recycle(struct amvdec_session *sess)
>> +{
>> + struct amvdec_buffer *tmp, *n;
>> +
>> + list_for_each_entry_safe(tmp, n, &sess->bufs_recycle, list) {
>> + list_del(&tmp->list);
>> + kfree(tmp);
>> + }
>> +}
>> +
>> +static void vdec_stop_streaming(struct vb2_queue *q)
>> +{
>> + struct amvdec_session *sess = vb2_get_drv_priv(q);
>> + struct vb2_v4l2_buffer *buf;
>> +
>> + mutex_lock(&sess->lock);
>> +
>> + if (sess->streamon_out && sess->streamon_cap) {
>> + if (vdec_codec_needs_recycle(sess))
>> + kthread_stop(sess->recycle_thread);
>> +
>> + vdec_poweroff(sess);
>> + vdec_free_canvas(sess);
>> + dma_free_coherent(sess->core->dev, sess->vififo_size,
>> + sess->vififo_vaddr, sess->vififo_paddr);
>> + vdec_reset_timestamps(sess);
>> + vdec_reset_bufs_recycle(sess);
>> + kfree(sess->priv);
>> + sess->priv = NULL;
>> + }
>> +
>> + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
>> + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx)))
>> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
>> +
>> + sess->streamon_out = 0;
>> + } else {
>> + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx)))
>> + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR);
>> +
>> + sess->streamon_cap = 0;
>> + }
>> +
>> + mutex_unlock(&sess->lock);
>> +}
>> +
>> +static const struct vb2_ops vdec_vb2_ops = {
>> + .queue_setup = vdec_queue_setup,
>> + .start_streaming = vdec_start_streaming,
>> + .stop_streaming = vdec_stop_streaming,
>> + .buf_queue = vdec_vb2_buf_queue,
>
> You need to add:
>
> .wait_prepare = vb2_ops_wait_prepare,
> .wait_finish = vb2_ops_wait_finish,
>
> and set the lock field for the vb2_queues.
>
> Otherwise a DQBUF ioctl can block other ioctls issued from another thread
> if DQBUF does a blocking wait.

Ack.

>> +};
>> +
>> +static int
>> +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
>> +{
>> + strlcpy(cap->driver, "meson-vdec", sizeof(cap->driver));
>> + strlcpy(cap->card, "Amlogic Video Decoder", sizeof(cap->card));
>> + strlcpy(cap->bus_info, "platform:meson-vdec", sizeof(cap->bus_info));
>> +
>> + return 0;
>> +}
>> +
>> +static const struct amvdec_format *
>> +find_format(const struct amvdec_format *fmts, u32 size, u32 pixfmt)
>> +{
>> + unsigned int i;
>> +
>> + for (i = 0; i < size; i++) {
>> + if (fmts[i].pixfmt == pixfmt)
>> + return &fmts[i];
>> + }
>> +
>> + return NULL;
>> +}
>> +
>> +static unsigned int
>> +vdec_supports_pixfmt_cap(const struct amvdec_format *fmt_out, u32 pixfmt_cap)
>> +{
>> + int i;
>> +
>> + for (i = 0; fmt_out->pixfmts_cap[i]; i++)
>> + if (fmt_out->pixfmts_cap[i] == pixfmt_cap)
>> + return 1;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct amvdec_format *
>> +vdec_try_fmt_common(struct amvdec_session *sess, u32 size,
>> + struct v4l2_format *f)
>> +{
>> + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
>> + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt;
>> + const struct amvdec_format *fmts = sess->core->platform->formats;
>> + const struct amvdec_format *fmt_out;
>> +
>> + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved));
>> + memset(pixmp->reserved, 0, sizeof(pixmp->reserved));
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
>> + fmt_out = find_format(fmts, size, pixmp->pixelformat);
>> + if (!fmt_out) {
>> + pixmp->pixelformat = V4L2_PIX_FMT_MPEG2;
>> + fmt_out = find_format(fmts, size, pixmp->pixelformat);
>> + pixmp->width = 1280;
>> + pixmp->height = 720;
>
> Why set the width and height here? You normally keep that as-is.

True. And it's clamped afterwards in the same function so there's
really no need.

>> + }
>> +
>> + pfmt[0].sizeimage =
>> + get_output_size(pixmp->width, pixmp->height);
>> + pfmt[0].bytesperline = 0;
>> + pixmp->num_planes = 1;
>> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
>> + fmt_out = sess->fmt_out;
>> + if (!vdec_supports_pixfmt_cap(fmt_out, pixmp->pixelformat))
>> + pixmp->pixelformat = fmt_out->pixfmts_cap[0];
>> +
>> + memset(pfmt[1].reserved, 0, sizeof(pfmt[1].reserved));
>> + if (pixmp->pixelformat == V4L2_PIX_FMT_NV12M) {
>> + pfmt[0].sizeimage =
>> + get_output_size(pixmp->width, pixmp->height);
>> + pfmt[0].bytesperline = ALIGN(pixmp->width, 64);
>> +
>> + pfmt[1].sizeimage =
>> + get_output_size(pixmp->width, pixmp->height) / 2;
>> + pfmt[1].bytesperline = ALIGN(pixmp->width, 64);
>> + pixmp->num_planes = 2;
>> + } else if (pixmp->pixelformat == V4L2_PIX_FMT_YUV420M) {
>> + pfmt[0].sizeimage =
>> + get_output_size(pixmp->width, pixmp->height);
>> + pfmt[0].bytesperline = ALIGN(pixmp->width, 64);
>> +
>> + pfmt[1].sizeimage =
>> + get_output_size(pixmp->width, pixmp->height) / 4;
>> + pfmt[1].bytesperline = ALIGN(pixmp->width, 64) / 2;
>> +
>> + pfmt[2].sizeimage =
>> + get_output_size(pixmp->width, pixmp->height) / 4;
>> + pfmt[2].bytesperline = ALIGN(pixmp->width, 64) / 2;
>> + pixmp->num_planes = 3;
>> + }
>> + } else {
>> + return NULL;
>> + }
>> +
>> + pixmp->width = clamp(pixmp->width, (u32)256, fmt_out->max_width);
>> + pixmp->height = clamp(pixmp->height, (u32)144, fmt_out->max_height);
>> +
>> + if (pixmp->field == V4L2_FIELD_ANY)
>> + pixmp->field = V4L2_FIELD_NONE;
>> +
>> + pixmp->flags = 0;
>
> Shouldn't be necessary, the core takes care of that if I remember correctly.

Ack.

>> +
>> + return fmt_out;
>> +}
>> +
>> +static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> +
>> + vdec_try_fmt_common(sess, sess->core->platform->num_formats, f);
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
>> + pixmp->pixelformat = sess->pixfmt_cap;
>> + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
>> + pixmp->pixelformat = sess->fmt_out->pixfmt;
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
>> + pixmp->width = sess->width;
>> + pixmp->height = sess->height;
>> + pixmp->colorspace = sess->colorspace;
>> + pixmp->ycbcr_enc = sess->ycbcr_enc;
>> + pixmp->quantization = sess->quantization;
>> + pixmp->xfer_func = sess->xfer_func;
>> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
>> + pixmp->width = sess->width;
>> + pixmp->height = sess->height;
>> + }
>> +
>> + vdec_try_fmt_common(sess, sess->core->platform->num_formats, f);
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp;
>> + u32 num_formats = sess->core->platform->num_formats;
>> + const struct amvdec_format *fmt_out;
>> + struct v4l2_pix_format_mplane orig_pixmp;
>> + struct v4l2_format format;
>> + u32 pixfmt_out = 0, pixfmt_cap = 0;
>> +
>> + orig_pixmp = *pixmp;
>> +
>> + fmt_out = vdec_try_fmt_common(sess, num_formats, f);
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
>> + pixfmt_out = pixmp->pixelformat;
>> + pixfmt_cap = sess->pixfmt_cap;
>> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
>> + pixfmt_cap = pixmp->pixelformat;
>> + pixfmt_out = sess->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(sess, num_formats, &format);
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
>> + sess->width = format.fmt.pix_mp.width;
>> + sess->height = format.fmt.pix_mp.height;
>> + sess->colorspace = pixmp->colorspace;
>> + sess->ycbcr_enc = pixmp->ycbcr_enc;
>> + sess->quantization = pixmp->quantization;
>> + sess->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(sess, num_formats, &format);
>> +
>> + sess->width = format.fmt.pix_mp.width;
>> + sess->height = format.fmt.pix_mp.height;
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
>> + sess->fmt_out = fmt_out;
>> + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
>> + sess->pixfmt_cap = format.fmt.pix_mp.pixelformat;
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> + const struct vdec_platform *platform = sess->core->platform;
>> + const struct amvdec_format *fmt_out;
>> +
>> + memset(f->reserved, 0, sizeof(f->reserved));
>> +
>> + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
>> + if (f->index >= platform->num_formats)
>> + return -EINVAL;
>> +
>> + fmt_out = &platform->formats[f->index];
>> + f->pixelformat = fmt_out->pixfmt;
>> + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
>> + fmt_out = sess->fmt_out;
>> + if (f->index >= 4 || !fmt_out->pixfmts_cap[f->index])
>> + return -EINVAL;
>> +
>> + f->pixelformat = fmt_out->pixfmts_cap[f->index];
>> + } else {
>> + return -EINVAL;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_enum_framesizes(struct file *file, void *fh,
>> + struct v4l2_frmsizeenum *fsize)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> + const struct amvdec_format *formats = sess->core->platform->formats;
>> + const struct amvdec_format *fmt;
>> + u32 num_formats = sess->core->platform->num_formats;
>> +
>> + fmt = find_format(formats, num_formats, fsize->pixel_format);
>> + if (!fmt || fsize->index)
>> + return -EINVAL;
>> +
>> + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
>> +
>> + fsize->stepwise.min_width = 256;
>> + fsize->stepwise.max_width = fmt->max_width;
>> + fsize->stepwise.step_width = 1;
>> + fsize->stepwise.min_height = 144;
>> + fsize->stepwise.max_height = fmt->max_height;
>> + fsize->stepwise.step_height = 1;
>> +
>> + return 0;
>> +}
>> +
>> +static int
>> +vdec_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd)
>> +{
>> + switch (cmd->cmd) {
>> + case V4L2_DEC_CMD_STOP:
>> + if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK)
>> + return -EINVAL;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int
>> +vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> + int ret;
>> +
>> + ret = vdec_try_decoder_cmd(file, fh, cmd);
>> + if (ret)
>> + return ret;
>> +
>> + if (!(sess->streamon_out & sess->streamon_cap))
>> + goto unlock;
>> +
>> + dev_dbg(sess->core->dev, "Received V4L2_DEC_CMD_STOP\n");
>> + sess->should_stop = 1;
>> +
>> + vdec_wait_inactive(sess);
>> +
>> + mutex_lock(&sess->lock);
>> + if (codec_ops->drain)
>> + codec_ops->drain(sess);
>> + else
>> + esparser_queue_eos(sess->core);
>> +
>> +unlock:
>> + mutex_unlock(&sess->lock);
>> + return ret;
>> +}
>> +
>> +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);
>> + 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_reqbufs = v4l2_m2m_ioctl_reqbufs,
>> + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
>> + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
>> + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
>> + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
>> + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
>> + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
>> + .vidioc_streamon = v4l2_m2m_ioctl_streamon,
>> + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
>> + .vidioc_enum_framesizes = vdec_enum_framesizes,
>> + .vidioc_subscribe_event = vdec_subscribe_event,
>> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>> + .vidioc_try_decoder_cmd = vdec_try_decoder_cmd,
>> + .vidioc_decoder_cmd = vdec_decoder_cmd,
>> +};
>> +
>> +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq,
>> + struct vb2_queue *dst_vq)
>> +{
>> + struct amvdec_session *sess = priv;
>> + int ret;
>> +
>> + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
>> + src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
>> + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
>> + src_vq->ops = &vdec_vb2_ops;
>> + src_vq->mem_ops = &vb2_dma_contig_memops;
>> + src_vq->drv_priv = sess;
>> + src_vq->buf_struct_size = sizeof(struct dummy_buf);
>> + src_vq->allow_zero_bytesused = 1;
>
> This shouldn't be used for new drivers.

Ack.

>> + src_vq->min_buffers_needed = 1;
>> + src_vq->dev = sess->core->dev;
>> + ret = vb2_queue_init(src_vq);
>> + if (ret)
>> + return ret;
>> +
>> + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
>> + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
>> + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
>> + dst_vq->ops = &vdec_vb2_ops;
>> + dst_vq->mem_ops = &vb2_dma_contig_memops;
>> + dst_vq->drv_priv = sess;
>> + dst_vq->buf_struct_size = sizeof(struct dummy_buf);
>> + dst_vq->allow_zero_bytesused = 1;
>
> And it definitely makes no sense for capture queues, since this
> field applies to output queues only.

Ack.

>> + dst_vq->min_buffers_needed = 1;
>> + dst_vq->dev = sess->core->dev;
>> + ret = vb2_queue_init(dst_vq);
>
> Please fill in the lock field of both queues as well.

Ack.

>> + if (ret) {
>> + vb2_queue_release(src_vq);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_open(struct file *file)
>> +{
>> + struct amvdec_core *core = video_drvdata(file);
>> + struct device *dev = core->dev;
>> + const struct amvdec_format *formats = core->platform->formats;
>> + struct amvdec_session *sess;
>> + int ret;
>> +
>> + sess = kzalloc(sizeof(*sess), GFP_KERNEL);
>> + if (!sess) {
>> + mutex_unlock(&core->lock);
>> + return -ENOMEM;
>> + }
>> +
>> + sess->core = core;
>> +
>> + sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops);
>> + if (IS_ERR(sess->m2m_dev)) {
>> + dev_err(dev, "Fail to v4l2_m2m_init\n");
>> + ret = PTR_ERR(sess->m2m_dev);
>> + goto err_free_sess;
>> + }
>> +
>> + sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init);
>> + if (IS_ERR(sess->m2m_ctx)) {
>> + dev_err(dev, "Fail to v4l2_m2m_ctx_init\n");
>> + ret = PTR_ERR(sess->m2m_ctx);
>> + goto err_m2m_release;
>> + }
>> +
>> + sess->pixfmt_cap = formats[0].pixfmts_cap[0];
>> + sess->fmt_out = &formats[0];
>> + sess->width = 1280;
>> + sess->height = 720;
>> +
>> + INIT_LIST_HEAD(&sess->timestamps);
>> + INIT_LIST_HEAD(&sess->bufs_recycle);
>> + INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src);
>> + mutex_init(&sess->lock);
>> + mutex_init(&sess->bufs_recycle_lock);
>> + spin_lock_init(&sess->ts_spinlock);
>> +
>> + v4l2_fh_init(&sess->fh, core->vdev_dec);
>> + v4l2_fh_add(&sess->fh);
>> + sess->fh.m2m_ctx = sess->m2m_ctx;
>> + file->private_data = &sess->fh;
>> +
>> + return 0;
>> +
>> +err_m2m_release:
>> + v4l2_m2m_release(sess->m2m_dev);
>> +err_free_sess:
>> + kfree(sess);
>> + return ret;
>> +}
>> +
>> +static int vdec_close(struct file *file)
>> +{
>> + struct amvdec_session *sess =
>> + container_of(file->private_data, struct amvdec_session, fh);
>> + struct amvdec_core *core = sess->core;
>> +
>> + v4l2_m2m_ctx_release(sess->m2m_ctx);
>> + v4l2_m2m_release(sess->m2m_dev);
>> + v4l2_fh_del(&sess->fh);
>> + v4l2_fh_exit(&sess->fh);
>> +
>> + mutex_destroy(&sess->lock);
>> + mutex_destroy(&sess->bufs_recycle_lock);
>> +
>> + kfree(sess);
>> +
>> + if (core->cur_sess == sess)
>> + core->cur_sess = NULL;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_file_operations vdec_fops = {
>> + .owner = THIS_MODULE,
>> + .open = vdec_open,
>> + .release = vdec_close,
>> + .unlocked_ioctl = video_ioctl2,
>> + .poll = v4l2_m2m_fop_poll,
>> + .mmap = v4l2_m2m_fop_mmap,
>> +#ifdef CONFIG_COMPAT
>> + .compat_ioctl32 = v4l2_compat_ioctl32,
>> +#endif
>
> Not needed. It's only needed if you have custom ioctls, and you don't.
>

Ack.

>> +};
>> +
>> +static irqreturn_t vdec_isr(int irq, void *data)
>> +{
>> + struct amvdec_core *core = data;
>> + struct amvdec_session *sess = core->cur_sess;
>> +
>> + sess->last_irq_jiffies = get_jiffies_64();
>> +
>> + return sess->fmt_out->codec_ops->isr(sess);
>> +}
>> +
>> +static irqreturn_t vdec_threaded_isr(int irq, void *data)
>> +{
>> + struct amvdec_core *core = data;
>> + struct amvdec_session *sess = core->cur_sess;
>> +
>> + return sess->fmt_out->codec_ops->threaded_isr(sess);
>> +}
>> +
>> +static const struct of_device_id vdec_dt_match[] = {
>> + { .compatible = "amlogic,gxbb-vdec",
>> + .data = &vdec_platform_gxbb },
>> + { .compatible = "amlogic,gxm-vdec",
>> + .data = &vdec_platform_gxm },
>> + { .compatible = "amlogic,gxl-vdec",
>> + .data = &vdec_platform_gxl },
>> + {}
>> +};
>> +MODULE_DEVICE_TABLE(of, vdec_dt_match);
>> +
>> +static int vdec_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct video_device *vdev;
>> + struct amvdec_core *core;
>> + struct resource *r;
>> + const struct of_device_id *of_id;
>> + int irq;
>> + int ret;
>> +
>> + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
>> + if (!core)
>> + return -ENOMEM;
>> +
>> + core->dev = dev;
>> + platform_set_drvdata(pdev, core);
>> +
>> + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dos");
>> + core->dos_base = devm_ioremap_resource(dev, r);
>> + if (IS_ERR(core->dos_base)) {
>> + dev_err(dev, "Couldn't remap DOS memory\n");
>> + return PTR_ERR(core->dos_base);
>> + }
>> +
>> + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "esparser");
>> + core->esparser_base = devm_ioremap_resource(dev, r);
>> + if (IS_ERR(core->esparser_base)) {
>> + dev_err(dev, "Couldn't remap ESPARSER memory\n");
>> + return PTR_ERR(core->esparser_base);
>> + }
>> +
>> + core->regmap_ao = syscon_regmap_lookup_by_phandle(dev->of_node,
>> + "amlogic,ao-sysctrl");
>> + if (IS_ERR(core->regmap_ao)) {
>> + dev_err(dev, "Couldn't regmap AO sysctrl\n");
>> + return PTR_ERR(core->regmap_ao);
>> + }
>> +
>> + core->canvas = meson_canvas_get(dev);
>> + if (!core->canvas)
>> + return PTR_ERR(core->canvas);
>> +
>> + core->dos_parser_clk = devm_clk_get(dev, "dos_parser");
>> + if (IS_ERR(core->dos_parser_clk))
>> + return -EPROBE_DEFER;
>> +
>> + core->dos_clk = devm_clk_get(dev, "dos");
>> + if (IS_ERR(core->dos_clk))
>> + return -EPROBE_DEFER;
>> +
>> + core->vdec_1_clk = devm_clk_get(dev, "vdec_1");
>> + if (IS_ERR(core->vdec_1_clk))
>> + return -EPROBE_DEFER;
>> +
>> + core->vdec_hevc_clk = devm_clk_get(dev, "vdec_hevc");
>> + if (IS_ERR(core->vdec_hevc_clk))
>> + return -EPROBE_DEFER;
>> +
>> + irq = platform_get_irq(pdev, 0);
>> + if (irq < 0)
>> + return irq;
>> +
>> + ret = devm_request_threaded_irq(core->dev, irq, vdec_isr,
>> + vdec_threaded_isr, IRQF_ONESHOT,
>> + "vdec", core);
>> + if (ret)
>> + return ret;
>> +
>> + ret = esparser_init(pdev, core);
>> + if (ret)
>> + return ret;
>> +
>> + ret = v4l2_device_register(dev, &core->v4l2_dev);
>> + if (ret) {
>> + dev_err(dev, "Couldn't register v4l2 device\n");
>> + return -ENOMEM;
>> + }
>> +
>> + vdev = video_device_alloc();
>> + if (!vdev) {
>> + ret = -ENOMEM;
>> + goto err_vdev_release;
>> + }
>> +
>> + strlcpy(vdev->name, "meson-video-decoder", sizeof(vdev->name));
>> + vdev->release = video_device_release;
>> + vdev->fops = &vdec_fops;
>> + vdev->ioctl_ops = &vdec_ioctl_ops;
>> + vdev->vfl_dir = VFL_DIR_M2M;
>> + vdev->v4l2_dev = &core->v4l2_dev;
>
> Please fill in vdev->lock, you probably want to set it to &core->lock
> (not sure, though). Otherwise you would have to serialize all ioctls yourself.

Ack.

>> + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
>> +
>> + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
>> + if (ret) {
>> + dev_err(dev, "Failed registering video device\n");
>> + goto err_vdev_release;
>> + }
>> +
>> + of_id = of_match_node(vdec_dt_match, dev->of_node);
>> + core->platform = of_id->data;
>> + core->vdev_dec = vdev;
>> + core->dev_dec = dev;
>> + mutex_init(&core->lock);
>> +
>> + video_set_drvdata(vdev, core);
>
> I'd move all this to before the video_register_device() call. Otherwise the video device
> can appear and used immediately without this initialization being done.

Ack.

>> +
>> + return 0;
>> +
>> +err_vdev_release:
>> + video_device_release(vdev);
>> + return ret;
>> +}
>> +
>> +static int vdec_remove(struct platform_device *pdev)
>> +{
>> + struct amvdec_core *core = platform_get_drvdata(pdev);
>> +
>> + video_unregister_device(core->vdev_dec);
>> +
>> + return 0;
>> +}
>> +
>> +static struct platform_driver meson_vdec_driver = {
>> + .probe = vdec_probe,
>> + .remove = vdec_remove,
>> + .driver = {
>> + .name = "meson-vdec",
>> + .of_match_table = vdec_dt_match,
>> + },
>> +};
>> +module_platform_driver(meson_vdec_driver);
>> +
>> +MODULE_DESCRIPTION("Meson video decoder driver for GXBB/GXL/GXM");
>> +MODULE_AUTHOR("Maxime Jourdan <[email protected]>");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/media/platform/meson/vdec/vdec.h b/drivers/media/platform/meson/vdec/vdec.h
>> new file mode 100644
>> index 000000000000..8250fb82dfab
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec.h
>> @@ -0,0 +1,234 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_CORE_H_
>> +#define __MESON_VDEC_CORE_H_
>> +
>> +/* 32 buffers in 3-plane YUV420 */
>> +#define MAX_CANVAS (32 * 3)
>> +
>> +#include <linux/regmap.h>
>> +#include <linux/list.h>
>> +#include <media/videobuf2-v4l2.h>
>> +#include <media/v4l2-ctrls.h>
>> +#include <media/v4l2-device.h>
>> +#include <linux/soc/amlogic/meson-canvas.h>
>> +
>> +#include "vdec_platform.h"
>> +
>> +struct amvdec_buffer {
>> + struct list_head list;
>> + struct vb2_buffer *vb;
>> +};
>> +
>> +struct amvdec_timestamp {
>> + struct list_head list;
>> + u64 ts;
>> +};
>> +
>> +struct amvdec_session;
>> +
>> +/**
>> + * struct amvdec_core - device parameters, singleton
>> + *
>> + * @dos_base: DOS memory base address
>> + * @esparser_base: PARSER memory base address
>> + * @regmap_ao: regmap for the AO bus
>> + * @dev: core device
>> + * @dev_dec: decoder device
>> + * @platform: platform-specific data
>> + * @canvas: canvas provider reference
>> + * @dos_parser_clk: DOS_PARSER clock
>> + * @dos_clk: DOS clock
>> + * @vdec_1_clk: VDEC_1 clock
>> + * @vdec_hevc_clk: VDEC_HEVC clock
>> + * @esparser_reset: RESET for the PARSER
>> + * @vdec_dec: video device for the decoder
>> + * @v4l2_dev: v4l2 device
>> + * @cur_sess: current decoding session
>> + * @lock: lock for this structure
>> + */
>> +struct amvdec_core {
>> + void __iomem *dos_base;
>> + void __iomem *esparser_base;
>> + struct regmap *regmap_ao;
>> +
>> + struct device *dev;
>> + struct device *dev_dec;
>> + const struct vdec_platform *platform;
>> +
>> + struct meson_canvas *canvas;
>> +
>> + struct clk *dos_parser_clk;
>> + struct clk *dos_clk;
>> + struct clk *vdec_1_clk;
>> + struct clk *vdec_hevc_clk;
>> +
>> + struct reset_control *esparser_reset;
>> +
>> + struct video_device *vdev_dec;
>> + struct v4l2_device v4l2_dev;
>> +
>> + struct amvdec_session *cur_sess;
>> + struct mutex lock;
>> +};
>> +
>> +/**
>> + * struct amvdec_ops - vdec operations
>> + *
>> + * @start: mandatory call when the vdec needs to initialize
>> + * @stop: mandatory call when the vdec needs to stop
>> + * @conf_esparser: mandatory call to let the vdec configure the ESPARSER
>> + * @vififo_level: mandatory call to get the current amount of data
>> + * in the VIFIFO
>> + */
>> +struct amvdec_ops {
>> + int (*start)(struct amvdec_session *sess);
>> + int (*stop)(struct amvdec_session *sess);
>> + void (*conf_esparser)(struct amvdec_session *sess);
>> + u32 (*vififo_level)(struct amvdec_session *sess);
>> +};
>> +
>> +/**
>> + * struct amvdec_codec_ops - codec operations
>> + *
>> + * @start: mandatory call when the codec needs to initialize
>> + * @stop: mandatory call when the codec needs to stop
>> + * @load_extended_firmware: optional call to load additional firmware bits
>> + * @num_pending_bufs: optional call to get the number of dst buffers on hold
>> + * @can_recycle: optional call to know if the codec is ready to recycle
>> + * a dst buffer
>> + * @recycle: optional call to tell the codec to recycle a dst buffer. Must go
>> + * in pair with can_recycle
>> + * @drain: optional call if the codec has a custom way of draining
>> + * @isr: mandatory call when the ISR triggers
>> + * @threaded_isr: mandatory call for the threaded ISR
>> + */
>> +struct amvdec_codec_ops {
>> + int (*start)(struct amvdec_session *sess);
>> + int (*stop)(struct amvdec_session *sess);
>> + int (*load_extended_firmware)(struct amvdec_session *sess,
>> + const u8 *data, u32 len);
>> + u32 (*num_pending_bufs)(struct amvdec_session *sess);
>> + int (*can_recycle)(struct amvdec_core *core);
>> + void (*recycle)(struct amvdec_core *core, u32 buf_idx);
>> + void (*drain)(struct amvdec_session *sess);
>> + irqreturn_t (*isr)(struct amvdec_session *sess);
>> + irqreturn_t (*threaded_isr)(struct amvdec_session *sess);
>> +};
>> +
>> +/**
>> + * struct amvdec_format - describes one of the OUTPUT (src) format supported
>> + *
>> + * @pixfmt: V4L2 pixel format
>> + * @min_buffers: minimum amount of CAPTURE (dst) buffers
>> + * @max_buffers: maximum amount of CAPTURE (dst) buffers
>> + * @max_width: maximum picture width supported
>> + * @max_height: maximum picture height supported
>> + * @vdec_ops: the VDEC operations that support this format
>> + * @codec_ops: the codec operations that support this format
>> + * @firmware_path: Path to the firmware that supports this format
>> + * @pixfmts_cap: list of CAPTURE pixel formats available with pixfmt
>> + */
>> +struct amvdec_format {
>> + u32 pixfmt;
>> + u32 min_buffers;
>> + u32 max_buffers;
>> + u32 max_width;
>> + u32 max_height;
>> +
>> + struct amvdec_ops *vdec_ops;
>> + struct amvdec_codec_ops *codec_ops;
>> +
>> + char *firmware_path;
>> + u32 pixfmts_cap[4];
>> +};
>> +
>> +/**
>> + * struct amvdec_session - decoding session parameters
>> + *
>> + * @core: reference to the vdec core struct
>> + * @fh: v4l2 file handle
>> + * @m2m_dev: v4l2 m2m device
>> + * @m2m_ctx: v4l2 m2m context
>> + * @lock: session lock
>> + * @fmt_out: vdec pixel format for the OUTPUT queue
>> + * @pixfmt_cap: V4L2 pixel format for the CAPTURE queue
>> + * @width: current picture width
>> + * @height: current picture height
>> + * @colorspace: current colorspace
>> + * @ycbcr_enc: current ycbcr_enc
>> + * @quantization: current quantization
>> + * @xfer_func: current transfer function
>> + * @esparser_queued_bufs: number of buffers currently queued into ESPARSER
>> + * @esparser_queue_work: work struct for the ESPARSER to process src buffers
>> + * @streamon_cap: stream on flag for capture queue
>> + * @streamon_out: stream on flag for output queue
>> + * @sequence_cap: capture sequence counter
>> + * @should_stop: flag set is userspacec signaled EOS via command
>> + * or empty buffer
>> + * @keyframe_found: flag set once a keyframe has been parsed
>> + * @canvas_alloc: array of all the canvas IDs allocated
>> + * @canvas_num: number of canvas IDs allocated
>> + * @vififo_vaddr: virtual address for the VIFIFO
>> + * @vififo_paddr: physical address for the VIFIFO
>> + * @vififo_size: size of the VIFIFO dma alloc
>> + * @bufs_recycle: list of buffers that need to be recycled
>> + * @bufs_recycle_lock: lock for the bufs_recycle list
>> + * @recycle_thread: task struct for the recycling thread
>> + * @timestamps: chronological list of src timestamps
>> + * @ts_spinlock: spinlock for the timestamps list
>> + * @last_irq_jiffies: tracks last time the vdec triggered an IRQ
>> + * @priv: codec private data
>> + */
>> +struct amvdec_session {
>> + struct amvdec_core *core;
>> +
>> + struct v4l2_fh fh;
>> + struct v4l2_m2m_dev *m2m_dev;
>> + struct v4l2_m2m_ctx *m2m_ctx;
>> + struct mutex lock;
>> +
>> + const struct amvdec_format *fmt_out;
>> + u32 pixfmt_cap;
>> +
>> + u32 width;
>> + u32 height;
>> + u32 colorspace;
>> + u8 ycbcr_enc;
>> + u8 quantization;
>> + u8 xfer_func;
>> +
>> + atomic_t esparser_queued_bufs;
>> + struct work_struct esparser_queue_work;
>> +
>> + unsigned int streamon_cap, streamon_out;
>> + unsigned int sequence_cap;
>> + unsigned int should_stop;
>> + unsigned int keyframe_found;
>> +
>> + u8 canvas_alloc[MAX_CANVAS];
>> + u32 canvas_num;
>> +
>> + void *vififo_vaddr;
>> + dma_addr_t vififo_paddr;
>> + u32 vififo_size;
>> +
>> + struct list_head bufs_recycle;
>> + struct mutex bufs_recycle_lock;
>> + struct task_struct *recycle_thread;
>> +
>> + struct list_head timestamps;
>> + spinlock_t ts_spinlock;
>> +
>> + u64 last_irq_jiffies;
>> +
>> + void *priv;
>> +};
>> +
>> +u32 amvdec_get_output_size(struct amvdec_session *sess);
>> +
>> +#endif
>> diff --git a/drivers/media/platform/meson/vdec/vdec_1.c b/drivers/media/platform/meson/vdec/vdec_1.c
>> new file mode 100644
>> index 000000000000..29f6305a6276
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec_1.c
>> @@ -0,0 +1,228 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + *
>> + * VDEC_1 is a video decoding block that allows decoding of
>> + * MPEG 1/2/4, H.263, H.264, MJPEG, VC1
>> + */
>> +
>> +#include <linux/firmware.h>
>> +#include <linux/clk.h>
>> +
>> +#include "vdec_1.h"
>> +#include "vdec_helpers.h"
>> +#include "dos_regs.h"
>> +
>> +/* AO Registers */
>> +#define AO_RTI_GEN_PWR_SLEEP0 0xe8
>> +#define AO_RTI_GEN_PWR_ISO0 0xec
>> + #define GEN_PWR_VDEC_1 (BIT(3) | BIT(2))
>> +
>> +#define MC_SIZE (4096 * 4)
>> +
>> +static int
>> +vdec_1_load_firmware(struct amvdec_session *sess, const char *fwname)
>> +{
>> + const struct firmware *fw;
>> + struct amvdec_core *core = sess->core;
>> + struct device *dev = core->dev_dec;
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> + static void *mc_addr;
>> + static dma_addr_t mc_addr_map;
>> + int ret;
>> + u32 i = 1000;
>> +
>> + ret = request_firmware(&fw, fwname, dev);
>> + if (ret < 0)
>> + return -EINVAL;
>> +
>> + if (fw->size < MC_SIZE) {
>> + dev_err(dev, "Firmware size %zu is too small. Expected %u.\n",
>> + fw->size, MC_SIZE);
>> + ret = -EINVAL;
>> + goto release_firmware;
>> + }
>> +
>> + mc_addr = dma_alloc_coherent(core->dev, MC_SIZE,
>> + &mc_addr_map, GFP_KERNEL);
>> + if (!mc_addr) {
>> + dev_err(dev,
>> + "Failed allocating memory for firmware loading\n");
>> + ret = -ENOMEM;
>> + goto release_firmware;
>> + }
>> +
>> + memcpy(mc_addr, fw->data, MC_SIZE);
>> +
>> + amvdec_write_dos(core, MPSR, 0);
>> + amvdec_write_dos(core, CPSR, 0);
>> +
>> + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31));
>> +
>> + amvdec_write_dos(core, IMEM_DMA_ADR, mc_addr_map);
>> + amvdec_write_dos(core, IMEM_DMA_COUNT, MC_SIZE / 4);
>> + amvdec_write_dos(core, IMEM_DMA_CTRL, (0x8000 | (7 << 16)));
>> +
>> + while (--i && amvdec_read_dos(core, IMEM_DMA_CTRL) & 0x8000) { }
>> +
>> + if (i == 0) {
>> + dev_err(dev, "Firmware load fail (DMA hang?)\n");
>> + ret = -EINVAL;
>> + goto free_mc;
>> + }
>> +
>> + if (codec_ops->load_extended_firmware)
>> + codec_ops->load_extended_firmware(sess, fw->data + MC_SIZE,
>> + fw->size - MC_SIZE);
>> +
>> +free_mc:
>> + dma_free_coherent(core->dev, MC_SIZE, mc_addr, mc_addr_map);
>> +release_firmware:
>> + release_firmware(fw);
>> + return ret;
>> +}
>> +
>> +int vdec_1_stbuf_power_up(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> +
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_CONTROL, 0);
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_WRAP_COUNT, 0);
>> + amvdec_write_dos(core, POWER_CTL_VLD, BIT(4));
>> +
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_START_PTR, sess->vififo_paddr);
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_CURR_PTR, sess->vififo_paddr);
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_END_PTR,
>> + sess->vififo_paddr + sess->vififo_size - 8);
>> +
>> + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1);
>> + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_CONTROL, 1);
>> +
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, MEM_BUFCTRL_MANUAL);
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_WP, sess->vififo_paddr);
>> +
>> + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
>> + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
>> +
>> + amvdec_write_dos_bits(core, VLD_MEM_VIFIFO_CONTROL,
>> + (0x11 << MEM_FIFO_CNT_BIT) | MEM_FILL_ON_LEVEL |
>> + MEM_CTRL_FILL_EN | MEM_CTRL_EMPTY_EN);
>> +
>> + return 0;
>> +}
>> +
>> +static void vdec_1_conf_esparser(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> +
>> + /* VDEC_1 specific ESPARSER stuff */
>> + amvdec_write_dos(core, DOS_GEN_CTRL0, 0);
>> + amvdec_write_dos(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
>> + amvdec_clear_dos_bits(core, VLD_MEM_VIFIFO_BUF_CNTL, 1);
>> +}
>> +
>> +static u32 vdec_1_vififo_level(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> +
>> + return amvdec_read_dos(core, VLD_MEM_VIFIFO_LEVEL);
>> +}
>> +
>> +static int vdec_1_stop(struct amvdec_session *sess)
>> +{
>> + struct amvdec_core *core = sess->core;
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> +
>> + amvdec_write_dos(core, MPSR, 0);
>> + amvdec_write_dos(core, CPSR, 0);
>> + amvdec_write_dos(core, ASSIST_MBOX1_MASK, 0);
>> +
>> + amvdec_write_dos(core, DOS_SW_RESET0, BIT(12) | BIT(11));
>> + amvdec_write_dos(core, DOS_SW_RESET0, 0);
>> + amvdec_read_dos(core, DOS_SW_RESET0);
>> +
>> + /* enable vdec1 isolation */
>> + regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0xc0);
>> + /* power off vdec1 memories */
>> + amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0xffffffff);
>> + /* power off vdec1 */
>> + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0,
>> + GEN_PWR_VDEC_1, GEN_PWR_VDEC_1);
>> +
>> + clk_disable_unprepare(core->vdec_1_clk);
>> +
>> + if (sess->priv)
>> + codec_ops->stop(sess);
>> +
>> + return 0;
>> +}
>> +
>> +static int vdec_1_start(struct amvdec_session *sess)
>> +{
>> + int ret;
>> + struct amvdec_core *core = sess->core;
>> + struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops;
>> +
>> + /* Configure the vdec clk to the maximum available */
>> + clk_set_rate(core->vdec_1_clk, 666666666);
>> + ret = clk_prepare_enable(core->vdec_1_clk);
>> + if (ret)
>> + return ret;
>> +
>> + regmap_update_bits(core->regmap_ao, AO_RTI_GEN_PWR_SLEEP0,
>> + GEN_PWR_VDEC_1, 0);
>> + udelay(10);
>> +
>> + /* Reset VDEC1 */
>> + amvdec_write_dos(core, DOS_SW_RESET0, 0xfffffffc);
>> + amvdec_write_dos(core, DOS_SW_RESET0, 0x00000000);
>> +
>> + amvdec_write_dos(core, DOS_GCLK_EN0, 0x3ff);
>> +
>> + /* enable VDEC Memories */
>> + amvdec_write_dos(core, DOS_MEM_PD_VDEC, 0);
>> + /* Remove VDEC1 Isolation */
>> + regmap_write(core->regmap_ao, AO_RTI_GEN_PWR_ISO0, 0);
>> + /* Reset DOS top registers */
>> + amvdec_write_dos(core, DOS_VDEC_MCRCC_STALL_CTRL, 0);
>> +
>> + amvdec_write_dos(core, GCLK_EN, 0x3ff);
>> + amvdec_clear_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(31));
>> +
>> + vdec_1_stbuf_power_up(sess);
>> +
>> + ret = vdec_1_load_firmware(sess, sess->fmt_out->firmware_path);
>> + if (ret)
>> + goto stop;
>> +
>> + ret = codec_ops->start(sess);
>> + if (ret)
>> + goto stop;
>> +
>> + /* Enable IRQ */
>> + amvdec_write_dos(core, ASSIST_MBOX1_CLR_REG, 1);
>> + amvdec_write_dos(core, ASSIST_MBOX1_MASK, 1);
>> +
>> + /* Enable 2-plane output */
>> + if (sess->pixfmt_cap == V4L2_PIX_FMT_NV12M)
>> + amvdec_write_dos_bits(core, MDEC_PIC_DC_CTRL, BIT(17));
>> +
>> + /* Enable firmware processor */
>> + amvdec_write_dos(core, MPSR, 1);
>> + /* Let the firmware settle */
>> + udelay(10);
>> +
>> + return 0;
>> +
>> +stop:
>> + vdec_1_stop(sess);
>> + return ret;
>> +}
>> +
>> +struct amvdec_ops vdec_1_ops = {
>> + .start = vdec_1_start,
>> + .stop = vdec_1_stop,
>> + .conf_esparser = vdec_1_conf_esparser,
>> + .vififo_level = vdec_1_vififo_level,
>> +};
>> diff --git a/drivers/media/platform/meson/vdec/vdec_1.h b/drivers/media/platform/meson/vdec/vdec_1.h
>> new file mode 100644
>> index 000000000000..042d930c40d7
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec_1.h
>> @@ -0,0 +1,14 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_VDEC_1_H_
>> +#define __MESON_VDEC_VDEC_1_H_
>> +
>> +#include "vdec.h"
>> +
>> +extern struct amvdec_ops vdec_1_ops;
>> +
>> +#endif
>> diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.c b/drivers/media/platform/meson/vdec/vdec_helpers.c
>> new file mode 100644
>> index 000000000000..615107629765
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec_helpers.c
>> @@ -0,0 +1,354 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#include <media/v4l2-mem2mem.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "vdec_helpers.h"
>> +
>> +#define NUM_CANVAS_NV12 2
>> +#define NUM_CANVAS_YUV420 3
>> +
>> +u32 amvdec_read_dos(struct amvdec_core *core, u32 reg)
>> +{
>> + return readl_relaxed(core->dos_base + reg);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_read_dos);
>> +
>> +void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val)
>> +{
>> + writel_relaxed(val, core->dos_base + reg);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_write_dos);
>> +
>> +void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val)
>> +{
>> + amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) | val);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_write_dos_bits);
>> +
>> +void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val)
>> +{
>> + amvdec_write_dos(core, reg, amvdec_read_dos(core, reg) & ~val);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_clear_dos_bits);
>> +
>> +u32 amvdec_read_parser(struct amvdec_core *core, u32 reg)
>> +{
>> + return readl_relaxed(core->esparser_base + reg);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_read_parser);
>> +
>> +void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val)
>> +{
>> + writel_relaxed(val, core->esparser_base + reg);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_write_parser);
>> +
>> +static int canvas_alloc(struct amvdec_session *sess, u8 *canvas_id)
>> +{
>> + int ret;
>> +
>> + if (sess->canvas_num >= MAX_CANVAS) {
>> + dev_err(sess->core->dev, "Reached max number of canvas\n");
>> + return -ENOMEM;
>> + }
>> +
>> + ret = meson_canvas_alloc(sess->core->canvas, canvas_id);
>> + if (ret)
>> + return ret;
>> +
>> + sess->canvas_alloc[sess->canvas_num++] = *canvas_id;
>> + return 0;
>> +}
>> +
>> +static int set_canvas_yuv420m(struct amvdec_session *sess,
>> + struct vb2_buffer *vb, u32 width,
>> + u32 height, u32 reg)
>> +{
>> + struct amvdec_core *core = sess->core;
>> + u8 canvas_id[NUM_CANVAS_YUV420]; /* Y U V */
>> + dma_addr_t buf_paddr[NUM_CANVAS_YUV420]; /* Y U V */
>> + int ret, i;
>> +
>> + for (i = 0; i < NUM_CANVAS_YUV420; ++i) {
>> + ret = canvas_alloc(sess, &canvas_id[i]);
>> + if (ret)
>> + return ret;
>> +
>> + buf_paddr[i] =
>> + vb2_dma_contig_plane_dma_addr(vb, i);
>> + }
>> +
>> + /* Y plane */
>> + meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0],
>> + width, height, MESON_CANVAS_WRAP_NONE,
>> + MESON_CANVAS_BLKMODE_LINEAR,
>> + MESON_CANVAS_ENDIAN_SWAP64);
>> +
>> + /* U plane */
>> + meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1],
>> + width / 2, height / 2, MESON_CANVAS_WRAP_NONE,
>> + MESON_CANVAS_BLKMODE_LINEAR,
>> + MESON_CANVAS_ENDIAN_SWAP64);
>> +
>> + /* V plane */
>> + meson_canvas_config(core->canvas, canvas_id[2], buf_paddr[2],
>> + width / 2, height / 2, MESON_CANVAS_WRAP_NONE,
>> + MESON_CANVAS_BLKMODE_LINEAR,
>> + MESON_CANVAS_ENDIAN_SWAP64);
>> +
>> + amvdec_write_dos(core, reg,
>> + ((canvas_id[2]) << 16) |
>> + ((canvas_id[1]) << 8) |
>> + (canvas_id[0]));
>> +
>> + return 0;
>> +}
>> +
>> +static int set_canvas_nv12m(struct amvdec_session *sess,
>> + struct vb2_buffer *vb, u32 width,
>> + u32 height, u32 reg)
>> +{
>> + struct amvdec_core *core = sess->core;
>> + u8 canvas_id[NUM_CANVAS_NV12]; /* Y U/V */
>> + dma_addr_t buf_paddr[NUM_CANVAS_NV12]; /* Y U/V */
>> + int ret, i;
>> +
>> + for (i = 0; i < NUM_CANVAS_NV12; ++i) {
>> + ret = canvas_alloc(sess, &canvas_id[i]);
>> + if (ret)
>> + return ret;
>> +
>> + buf_paddr[i] =
>> + vb2_dma_contig_plane_dma_addr(vb, i);
>> + }
>> +
>> + /* Y plane */
>> + meson_canvas_config(core->canvas, canvas_id[0], buf_paddr[0],
>> + width, height, MESON_CANVAS_WRAP_NONE,
>> + MESON_CANVAS_BLKMODE_LINEAR,
>> + MESON_CANVAS_ENDIAN_SWAP64);
>> +
>> + /* U/V plane */
>> + meson_canvas_config(core->canvas, canvas_id[1], buf_paddr[1],
>> + width, height / 2, MESON_CANVAS_WRAP_NONE,
>> + MESON_CANVAS_BLKMODE_LINEAR,
>> + MESON_CANVAS_ENDIAN_SWAP64);
>> +
>> + amvdec_write_dos(core, reg,
>> + ((canvas_id[1]) << 16) |
>> + ((canvas_id[1]) << 8) |
>> + (canvas_id[0]));
>> +
>> + return 0;
>> +}
>> +
>> +int amvdec_set_canvases(struct amvdec_session *sess,
>> + u32 reg_base[], u32 reg_num[])
>> +{
>> + struct v4l2_m2m_buffer *buf;
>> + u32 pixfmt = sess->pixfmt_cap;
>> + u32 width = ALIGN(sess->width, 64);
>> + u32 height = ALIGN(sess->height, 64);
>> + u32 reg_cur = reg_base[0];
>> + u32 reg_num_cur = 0;
>> + u32 reg_base_cur = 0;
>> + int ret;
>> +
>> + v4l2_m2m_for_each_dst_buf(sess->m2m_ctx, buf) {
>> + if (!reg_base[reg_base_cur])
>> + return -EINVAL;
>> +
>> + reg_cur = reg_base[reg_base_cur] + reg_num_cur * 4;
>> +
>> + switch (pixfmt) {
>> + case V4L2_PIX_FMT_NV12M:
>> + ret = set_canvas_nv12m(sess, &buf->vb.vb2_buf, width,
>> + height, reg_cur);
>> + if (ret)
>> + return ret;
>> + break;
>> + case V4L2_PIX_FMT_YUV420M:
>> + ret = set_canvas_yuv420m(sess, &buf->vb.vb2_buf, width,
>> + height, reg_cur);
>> + if (ret)
>> + return ret;
>> + break;
>> + default:
>> + dev_err(sess->core->dev, "Unsupported pixfmt %08X\n",
>> + pixfmt);
>> + return -EINVAL;
>> + };
>> +
>> + reg_num_cur++;
>> + if (reg_num_cur >= reg_num[reg_base_cur]) {
>> + reg_base_cur++;
>> + reg_num_cur = 0;
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_set_canvases);
>> +
>> +void amvdec_dst_buf_done(struct amvdec_session *sess,
>> + struct vb2_v4l2_buffer *vbuf, u32 field)
>> +{
>> + struct device *dev = sess->core->dev_dec;
>> + struct amvdec_timestamp *tmp;
>> + struct list_head *timestamps = &sess->timestamps;
>> + u32 output_size = amvdec_get_output_size(sess);
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&sess->ts_spinlock, flags);
>> + if (list_empty(timestamps)) {
>> + dev_err(dev, "Buffer %u done but list is empty\n",
>> + vbuf->vb2_buf.index);
>> +
>> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
>> + amvdec_abort(sess);
>> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
>> + goto end;
>> + }
>> +
>> + tmp = list_first_entry(timestamps, struct amvdec_timestamp, list);
>> +
>> + switch (sess->pixfmt_cap) {
>> + case V4L2_PIX_FMT_NV12M:
>> + vbuf->vb2_buf.planes[0].bytesused = output_size;
>> + vbuf->vb2_buf.planes[1].bytesused = output_size / 2;
>> + break;
>> + case V4L2_PIX_FMT_YUV420M:
>> + vbuf->vb2_buf.planes[0].bytesused = output_size;
>> + vbuf->vb2_buf.planes[1].bytesused = output_size / 4;
>> + vbuf->vb2_buf.planes[2].bytesused = output_size / 4;
>> + break;
>> + }
>> + vbuf->vb2_buf.timestamp = tmp->ts;
>> + vbuf->sequence = sess->sequence_cap++;
>> +
>> + list_del(&tmp->list);
>> + kfree(tmp);
>> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
>> +
>> + atomic_dec(&sess->esparser_queued_bufs);
>> +
>> + if (sess->should_stop && list_empty(timestamps)) {
>> + const struct v4l2_event ev = { .type = V4L2_EVENT_EOS };
>> +
>> + dev_dbg(dev, "Signaling EOS\n");
>> + v4l2_event_queue_fh(&sess->fh, &ev);
>> + vbuf->flags |= V4L2_BUF_FLAG_LAST;
>> + } else if (sess->should_stop)
>> + dev_dbg(dev, "should_stop, %u bufs remain\n",
>> + atomic_read(&sess->esparser_queued_bufs));
>> +
>> + vbuf->field = field;
>> + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
>> +
>> +end:
>> + /* Buffer done probably means the vififo got freed */
>> + schedule_work(&sess->esparser_queue_work);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done);
>> +
>> +void
>> +amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx, u32 field)
>> +{
>> + struct vb2_v4l2_buffer *vbuf;
>> + struct device *dev = sess->core->dev_dec;
>> +
>> + vbuf = v4l2_m2m_dst_buf_remove_by_idx(sess->m2m_ctx, buf_idx);
>> + if (!vbuf) {
>> + dev_err(dev,
>> + "Buffer %u done but it doesn't exist in m2m_ctx\n",
>> + buf_idx);
>> + amvdec_rm_first_ts(sess);
>> + return;
>> + }
>> +
>> + amvdec_dst_buf_done(sess, vbuf, field);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_dst_buf_done_idx);
>> +
>> +void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts)
>> +{
>> + struct amvdec_timestamp *new_ts, *tmp;
>> + unsigned long flags;
>> +
>> + new_ts = kmalloc(sizeof(*new_ts), GFP_KERNEL);
>> + new_ts->ts = ts;
>> +
>> + spin_lock_irqsave(&sess->ts_spinlock, flags);
>> +
>> + if (list_empty(&sess->timestamps))
>> + goto add_tail;
>> +
>> + list_for_each_entry(tmp, &sess->timestamps, list) {
>> + if (ts < tmp->ts) {
>> + list_add_tail(&new_ts->list, &tmp->list);
>> + goto unlock;
>> + }
>> + }
>> +
>> +add_tail:
>> + list_add_tail(&new_ts->list, &sess->timestamps);
>> +unlock:
>> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_add_ts_reorder);
>> +
>> +void amvdec_remove_ts(struct amvdec_session *sess, u64 ts)
>> +{
>> + struct amvdec_timestamp *tmp;
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&sess->ts_spinlock, flags);
>> + list_for_each_entry(tmp, &sess->timestamps, list) {
>> + if (tmp->ts == ts) {
>> + list_del(&tmp->list);
>> + kfree(tmp);
>> + goto unlock;
>> + }
>> + }
>> + dev_warn(sess->core->dev_dec,
>> + "Couldn't remove buffer with timestamp %llu from list\n", ts);
>> +
>> +unlock:
>> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_remove_ts);
>> +
>> +void amvdec_rm_first_ts(struct amvdec_session *sess)
>> +{
>> + unsigned long flags;
>> + struct amvdec_buffer *tmp;
>> + struct device *dev = sess->core->dev_dec;
>> +
>> + spin_lock_irqsave(&sess->ts_spinlock, flags);
>> + if (list_empty(&sess->timestamps)) {
>> + dev_err(dev, "Can't rm first timestamp: list empty\n");
>> + goto unlock;
>> + }
>> +
>> + tmp = list_first_entry(&sess->timestamps, struct amvdec_buffer, list);
>> + list_del(&tmp->list);
>> + kfree(tmp);
>> + atomic_dec(&sess->esparser_queued_bufs);
>> +
>> +unlock:
>> + spin_unlock_irqrestore(&sess->ts_spinlock, flags);
>> +}
>> +
>> +void amvdec_abort(struct amvdec_session *sess)
>> +{
>> + dev_info(sess->core->dev, "Aborting decoding session!\n");
>> + vb2_queue_error(&sess->m2m_ctx->cap_q_ctx.q);
>> + vb2_queue_error(&sess->m2m_ctx->out_q_ctx.q);
>> +}
>> +EXPORT_SYMBOL_GPL(amvdec_abort);
>> diff --git a/drivers/media/platform/meson/vdec/vdec_helpers.h b/drivers/media/platform/meson/vdec/vdec_helpers.h
>> new file mode 100644
>> index 000000000000..352c6b4c4b84
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec_helpers.h
>> @@ -0,0 +1,45 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_HELPERS_H_
>> +#define __MESON_VDEC_HELPERS_H_
>> +
>> +#include "vdec.h"
>> +
>> +/**
>> + * amvdec_set_canvases() - Map VB2 buffers to canvases
>> + *
>> + * @sess: current session
>> + * @reg_base: Registry bases of where to write the canvas indexes
>> + * @reg_num: number of contiguous registers after each reg_base (including it)
>> + */
>> +int amvdec_set_canvases(struct amvdec_session *sess,
>> + u32 reg_base[], u32 reg_num[]);
>> +
>> +u32 amvdec_read_dos(struct amvdec_core *core, u32 reg);
>> +void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val);
>> +void amvdec_write_dos_bits(struct amvdec_core *core, u32 reg, u32 val);
>> +void amvdec_clear_dos_bits(struct amvdec_core *core, u32 reg, u32 val);
>> +u32 amvdec_read_parser(struct amvdec_core *core, u32 reg);
>> +void amvdec_write_parser(struct amvdec_core *core, u32 reg, u32 val);
>> +
>> +void amvdec_dst_buf_done_idx(struct amvdec_session *sess, u32 buf_idx,
>> + u32 field);
>> +void amvdec_dst_buf_done(struct amvdec_session *sess,
>> + struct vb2_v4l2_buffer *vbuf, u32 field);
>> +
>> +/**
>> + * amvdec_add_ts_reorder() - Add a timestamp to the list in chronological order
>> + *
>> + * @sess: current session
>> + * @ts: timestamp to add
>> + */
>> +void amvdec_add_ts_reorder(struct amvdec_session *sess, u64 ts);
>> +void amvdec_remove_ts(struct amvdec_session *sess, u64 ts);
>> +void amvdec_rm_first_ts(struct amvdec_session *sess);
>> +
>> +void amvdec_abort(struct amvdec_session *sess);
>> +#endif
>> diff --git a/drivers/media/platform/meson/vdec/vdec_platform.c b/drivers/media/platform/meson/vdec/vdec_platform.c
>> new file mode 100644
>> index 000000000000..46eeb7426f54
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec_platform.c
>> @@ -0,0 +1,101 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#include "vdec_platform.h"
>> +#include "vdec.h"
>> +
>> +#include "vdec_1.h"
>> +#include "codec_mpeg12.h"
>> +
>> +static const struct amvdec_format vdec_formats_gxbb[] = {
>> + {
>> + .pixfmt = V4L2_PIX_FMT_MPEG1,
>> + .min_buffers = 8,
>> + .max_buffers = 8,
>> + .max_width = 1920,
>> + .max_height = 1080,
>> + .vdec_ops = &vdec_1_ops,
>> + .codec_ops = &codec_mpeg12_ops,
>> + .firmware_path = "meson/gx/vmpeg12_mc",
>> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
>> + }, {
>> + .pixfmt = V4L2_PIX_FMT_MPEG2,
>> + .min_buffers = 8,
>> + .max_buffers = 8,
>> + .max_width = 1920,
>> + .max_height = 1080,
>> + .vdec_ops = &vdec_1_ops,
>> + .codec_ops = &codec_mpeg12_ops,
>> + .firmware_path = "meson/gx/vmpeg12_mc",
>> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
>> + },
>> +};
>> +
>> +static const struct amvdec_format vdec_formats_gxl[] = {
>> + {
>> + .pixfmt = V4L2_PIX_FMT_MPEG1,
>> + .min_buffers = 8,
>> + .max_buffers = 8,
>> + .max_width = 1920,
>> + .max_height = 1080,
>> + .vdec_ops = &vdec_1_ops,
>> + .codec_ops = &codec_mpeg12_ops,
>> + .firmware_path = "meson/gx/vmpeg12_mc",
>> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
>> + }, {
>> + .pixfmt = V4L2_PIX_FMT_MPEG2,
>> + .min_buffers = 8,
>> + .max_buffers = 8,
>> + .max_width = 1920,
>> + .max_height = 1080,
>> + .vdec_ops = &vdec_1_ops,
>> + .codec_ops = &codec_mpeg12_ops,
>> + .firmware_path = "meson/gx/vmpeg12_mc",
>> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
>> + },
>> +};
>> +
>> +static const struct amvdec_format vdec_formats_gxm[] = {
>> + {
>> + .pixfmt = V4L2_PIX_FMT_MPEG1,
>> + .min_buffers = 8,
>> + .max_buffers = 8,
>> + .max_width = 1920,
>> + .max_height = 1080,
>> + .vdec_ops = &vdec_1_ops,
>> + .codec_ops = &codec_mpeg12_ops,
>> + .firmware_path = "meson/gx/vmpeg12_mc",
>> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
>> + }, {
>> + .pixfmt = V4L2_PIX_FMT_MPEG2,
>> + .min_buffers = 8,
>> + .max_buffers = 8,
>> + .max_width = 1920,
>> + .max_height = 1080,
>> + .vdec_ops = &vdec_1_ops,
>> + .codec_ops = &codec_mpeg12_ops,
>> + .firmware_path = "meson/gx/vmpeg12_mc",
>> + .pixfmts_cap = { V4L2_PIX_FMT_NV12M, V4L2_PIX_FMT_YUV420M, 0 },
>> + },
>> +};
>> +
>> +const struct vdec_platform vdec_platform_gxbb = {
>> + .formats = vdec_formats_gxbb,
>> + .num_formats = ARRAY_SIZE(vdec_formats_gxbb),
>> + .revision = VDEC_REVISION_GXBB,
>> +};
>> +
>> +const struct vdec_platform vdec_platform_gxl = {
>> + .formats = vdec_formats_gxl,
>> + .num_formats = ARRAY_SIZE(vdec_formats_gxl),
>> + .revision = VDEC_REVISION_GXL,
>> +};
>> +
>> +const struct vdec_platform vdec_platform_gxm = {
>> + .formats = vdec_formats_gxm,
>> + .num_formats = ARRAY_SIZE(vdec_formats_gxm),
>> + .revision = VDEC_REVISION_GXM,
>> +};
>> diff --git a/drivers/media/platform/meson/vdec/vdec_platform.h b/drivers/media/platform/meson/vdec/vdec_platform.h
>> new file mode 100644
>> index 000000000000..f6025326db1d
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/vdec/vdec_platform.h
>> @@ -0,0 +1,30 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2018 BayLibre, SAS
>> + * Author: Maxime Jourdan <[email protected]>
>> + */
>> +
>> +#ifndef __MESON_VDEC_PLATFORM_H_
>> +#define __MESON_VDEC_PLATFORM_H_
>> +
>> +#include "vdec.h"
>> +
>> +struct amvdec_format;
>> +
>> +enum vdec_revision {
>> + VDEC_REVISION_GXBB,
>> + VDEC_REVISION_GXL,
>> + VDEC_REVISION_GXM,
>> +};
>> +
>> +struct vdec_platform {
>> + const struct amvdec_format *formats;
>> + const u32 num_formats;
>> + enum vdec_revision revision;
>> +};
>> +
>> +extern const struct vdec_platform vdec_platform_gxbb;
>> +extern const struct vdec_platform vdec_platform_gxm;
>> +extern const struct vdec_platform vdec_platform_gxl;
>> +
>> +#endif
>>
>
> Regards,
>
> Hans

Cheers,

Maxime