* [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4
@ 2026-08-12 2:41 Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
` (5 more replies)
0 siblings, 6 replies; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
Introduce initial driver support for Amlogic's new video acceleration
hardware architecture, designed for video stream decoding.
Compared to the current Amlogic video decoder hardware architecture,
this new implementation eliminates the Esparser hardware component,
enabling direct vb2 buffer input. The driver is designed to support
the V4L2 M2M stateless decoder API. The initial phase includes support
for H.264 decoding on Amlogic S805X2 platform.
The driver needs to work alongside with a signed firmware. The loading process of
the signed fw is as follow.
Stage1: Decypt and decompose the full firmware package when the driver is probed.
+---------------------+ +---------------------+
| Decoder Driver | | TEE Shared Memory |
| (Kernel Space) | | |
| +---------------+ | | +---------------+ |
| | video_ucode | | | | firmware | |
| | .bin | | Copy payload to SHM | | payload | |
| | (from fs) | | ---------------------> | | (Secure RAM) | |
| +---------------+ | | +---------------+ |
+---------------------+ +----------+----------+
|
| PTA Invocation
v
+-------------------------------+
| BL32 |
| +-------------------------+ |
| | Decrypt Firmware | |
| +-----------+-------------+ |
| | |
| v |
| +-------------------------+ |
| | Decompose the full | |
| | firmware pacakge | |
| +-----------+-------------+ |
| | |
| v |
| +-------------------------+ |
| | Store decomposed .bin | |
| | in Secure Memory | |
| +-------------------------+ |
+-------------------------------+
Stage2: When a decode job is scheduled, load decrypted fw via secure monitor.
+---------------------+
| V4L2 M2M Framework |
| +---------------+ |
| | device_run | |
| +------+--------+ |
+---------+-----------+
|
v
+---------------------+ +---------------------+
| Decoder Driver | | Secure Monitor |
| (Kernel Space) | | (bl32) |
| +---------------+ | SMC Call | +---------------+ |
| | Select Codec | | ---------------> | | Select & Load | |
| | Specific FW | | | | firmware.bin | |
| +---------------+ | | | to AMRISC | |
+---------------------+ | +-------+-------+ |
+----------+----------+
|
v
+---------------------+
| AMRISC Core |
| +---------------+ |
| | Running fw on | |
| | AMRISC | |
| +---------------+ |
+---------------------+
The driver is capable of:
- Supporting stateless H.264 decoding up to a resolution 1920x1088(on the S805X2 platform).
- Supporting I/P/B frame handling.
- Supporting vb2 mmap and dma-buf modes.
- Supporting frame-based decode mode. (Note that some H.264 bitstreams require
DPB reordering to generate reference lists, the stateless decoder driver
cannot access reordered reference lists in this mode, requiring the driver
to perform reference list reordering itself)
- Supporting NV12/NV21 output.
- Supporting Annex B start codes.
This driver is tested with Gstreamer.
Example:
gst-launch-1.0 filesrc location=/tmp/video_640x360_mp4_hevc_450kbps_no_b.mp4 !
parsebin ! v4l2slh264dec ! filesink location=/tmp/output.yuv
Retry the compliance test based on kernel 7.1.0:
v4l2-compliance 1.30.1, 64 bits, 64-bit time_t
Compliance test for aml-vdec-drv device /dev/video0:
Driver Info:
Driver name : aml-vdec-drv
Card type : platform:aml-vdec-drv
Bus info : platform:fe320000.video-codec
Driver version : 7.1.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
Detected Stateless Decoder
Media Driver Info:
Driver name : aml-vdec-drv
Model : aml-vdec-drv
Serial :
Bus info : platform:fe320000.video-codec
Media version : 7.1.0
Hardware revision: 0x00000000 (0)
Driver version : 7.1.0
Interface Info:
ID : 0x0300000c
Type : V4L Video
Entity Info:
ID : 0x00000001 (1)
Name : aml_dev_drv-source
Function : V4L2 I/O
Pad 0x01000002 : 0: Source
Link 0x02000008: to remote pad 0x1000004 of entity 'aml_dev_drv-proc' (Video Decoder): Data, Enabled, Immutable
Required ioctls:
test MC information (see 'Media Driver Info' above): OK
test VIDIOC_QUERYCAP: OK
test invalid ioctls: 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
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 6 Private Controls: 0
Standard Compound Controls: 4 Private Compound 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 (Not Supported)
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 CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK
test Requests: OK
test blocking wait: OK
Total for aml-vdec-drv device /dev/video0: 49, Succeeded: 49, Failed: 0, Warnings: 0
Fluster test result of JVT-AVC_V1.
Result:
Ran 77/135 tests successfully
- 52 test vectors failed due to interlaced or mbaff clips: The Amlogic stateless
decoder driver only support bitstreams with frame_mbs_only_flags == 1.
Test Vectors:
cabac_mot_fld0_full
cabac_mot_mbaff0_full
cabac_mot_picaff0_full
CABREF3_Sand_D
CAFI1_SVA_C
CAMA1_Sony_C
CAMA1_TOSHIBA_B
cama1_vtc_c
cama2_vtc_b
CAMA3_Sand_E
cama3_vtc_b
CAMACI3_Sony_C
CAMANL1_TOSHIBA_B
CAMANL2_TOSHIBA_B
CAMANL3_Sand_E
CAMASL3_Sony_B
CAMP_MOT_MBAFF_L30
CAMP_MOT_MBAFF_L31
CANLMA2_Sony_C
CANLMA3_Sony_C
CAPA1_TOSHIBA_B
CAPAMA3_Sand_F
cavlc_mot_fld0_full_B
cavlc_mot_mbaff0_full_B
cavlc_mot_picaff0_full_B
CVCANLMA2_Sony_C
CVFI1_Sony_D
CVFI1_SVA_C
CVFI2_Sony_H
CVFI2_SVA_C
CVMA1_Sony_D
CVMA1_TOSHIBA_B
CVMANL1_TOSHIBA_B
CVMANL2_TOSHIBA_B
CVMAPAQP3_Sony_E
CVMAQP2_Sony_G
CVMAQP3_Sony_D
CVMP_MOT_FLD_L30_B
CVNLFI1_Sony_C
CVNLFI2_Sony_H
CVPA1_TOSHIBA_B
FI1_Sony_E
MR6_BT_B
MR7_BT_B
MR8_BT_B
MR9_BT_B
Sharp_MP_Field_1_B
Sharp_MP_Field_2_B
Sharp_MP_Field_3_B
Sharp_MP_PAFF_1r2
Sharp_MP_PAFF_2r
CVMP_MOT_FRM_L31_B
- 3 test vectors failed due to unsupported bitstream.
num_slice_group_minus1 greater than zero is not supported by the
hardware.
Test Vectors:
FM1_BT_B
FM1_FT_E
FM2_SVA_C
- 2 test vectors failed because SP_SLICE type is not supported by the
hardware.
Test Vectors:
SP1_BT_A
sp2_bt_b
One remain failure is CVFC1_Sony_C, which contains crop information. The md5sum of every decoded YUV indicates that original output from the decoder was correct. The YUV was cropped by gstreamer. The correct cropping method for this bitstream should be to crop 30*2 rows of pixels from both the top and bottom of the image, and 13*2 columns of pixels from both the left and right sides.However, gstreamer cropped 13*4 columns of pixels from the right side and 30*4 rows of pixels from the bottom. We are trying to find out the cause of this. Other failuers mentioned in V1 and V2 were resolved.
Changes in v7:
- Addressed Krzysztof's review comments: fixed DTS coding style, added board info requiring the config to patch 6/6's commit message.
- Improvements made per sashiko bot review, including:
1.Fixed memory out-of-bounds access / buffer overflow issues.
2.Fixed concurrency and race conditions related issues.
3.Improved the error handling paths to avoid the risk of resource leakage.
4.Fixed the improper V4L2 framework usage.
- Link to v6: https://lore.kernel.org/r/20260724-b4-s4-vdec-upstream-v6-0-6072a7ee7428@amlogic.com
Changes in v6:
- Rename the HEVC core and the associated power, clock, and interrupt names. 'HEVC' was a development codename of the decoder IP which accelerates h265 vp9 av1 decoding, but it coincidentally matches the acronym for High Efficiency Video Coding. To avoid naming ambiguity, HEVC core is renamed to HVDEC core(H stands for HEVC).
- Get rid of the unused function and interface.
- Link to v5: https://lore.kernel.org/r/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com
Changes in v5:
- Rename the compatible and the clock item accroding to Krzysztof's feedback.
- Use tee & meson_sm helpers to decrypt load the signed decoder firmware. Add the meson_sm describsion and reference to dt-binding and dts.
- Link to v4: https://lore.kernel.org/r/20260213-b4-s4-vdec-upstream-v4-0-c7112d00d662@amlogic.com
Changes in v4:
- Use %pad to print dma_addr_t type instead of using %llx.
- Add initial values to some local variables.
- Link to v3: https://lore.kernel.org/r/20260121-b4-s4-vdec-upstream-v3-0-4496aec3d79e@amlogic.com
Changes in v3:
- Fixed the DT check error:
arch/arm64/boot/dts/amlogic/meson-s4-s805x2-aq222.dtb: video-codec@fe320000 (amlogic,s4-vcodec-dec): 'amlogic,canvas' does not match any of the regexes: '^pinctrl-[0-9]+$'
from schema $id: http://devicetree.org/schemas/media/amlogic,vcodec-dec.yaml
- Added DOS reset lines to dtsi and dt-binding.
- Fixed the issue where some B-frames were not decoded correctly(The fluster failures mentioned in patch V1 and V2 were mostly caused by this).
- Fixed the issue where canvas_index leaks occurred during the decoding of some bitstreams.
- Rework the src/dst format storage. Use v4l2_pix_format_mplane to store formats that related to bitstreams into the context. Add the reset format function to reset all the formats to default value.
- Store decoding parameters related to chip platforms, such as maximum width/height and alignment requirement, organized by chip platform.
- Link to v2: https://lore.kernel.org/r/20251124-b4-s4-vdec-upstream-v2-0-bdbbce3f11a6@amlogic.com
Changes in v2:
- Fixed incorrect generation of the reference lists for some B-frames.
- Rename or get rid of some properties in DTS and dt-binding.
- Remove some useless code or helper functions, (eg. clk helper functions, reg I/O macros, and some superfluous print messages) replace these functions with existing ones.
- Replace all the printk messages with dev_err/dev_info/dev_dbg
- Use the helper functions from the existing meson-canvas driver.
- Use clk_bulk_data to map clocks from DTS.
- Retry the V4L2 Compliance test on 6.18-rc6, fix a newly introduced bug.
- Link to v1: https://lore.kernel.org/r/20251027-b4-s4-vdec-upstream-v1-0-620401813b5d@amlogic.com
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
Zhentao Guo (6):
firmware: meson: sm: video firmware loading via secure monitor
firmware: meson: sm: Add video firmware loading SMC call
media: dt-bindings: Add Amlogic V4L2 video decoder
decoder: Add V4L2 stateless H.264 decoder driver
arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC
.../devicetree/bindings/media/amlogic,s4-vdec.yaml | 111 ++
MAINTAINERS | 7 +
arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 35 +
arch/arm64/configs/defconfig | 1 +
drivers/firmware/meson/meson_sm.c | 1 +
drivers/media/platform/amlogic/Kconfig | 1 +
drivers/media/platform/amlogic/Makefile | 1 +
drivers/media/platform/amlogic/vdec/Kconfig | 18 +
drivers/media/platform/amlogic/vdec/Makefile | 4 +
drivers/media/platform/amlogic/vdec/TODO | 7 +
drivers/media/platform/amlogic/vdec/aml_vdec.c | 808 ++++++++
drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 +
drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 231 +++
drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 168 ++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 530 +++++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 155 ++
.../platform/amlogic/vdec/aml_vdec_platform.c | 81 +
.../platform/amlogic/vdec/aml_vdec_platform.h | 46 +
.../media/platform/amlogic/vdec/aml_vdec_tee_fw.c | 240 +++
.../media/platform/amlogic/vdec/aml_vdec_tee_fw.h | 27 +
drivers/media/platform/amlogic/vdec/h264.c | 2082 ++++++++++++++++++++
drivers/media/platform/amlogic/vdec/h264.h | 299 +++
drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++
include/linux/firmware/meson/meson_sm.h | 1 +
24 files changed, 5064 insertions(+)
---
base-commit: 3d08ff75a47a3e7e2ab45a3bcab6723b4d906422
change-id: 20251027-b4-s4-vdec-upstream-0603c1a4c84a
Best regards,
--
Zhentao Guo <zhentao.guo@amlogic.com>
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
@ 2026-08-12 2:41 ` Zhentao Guo via B4 Relay
2026-08-12 2:50 ` sashiko-bot
2026-08-12 2:41 ` [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
` (4 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
From: Zhentao Guo <zhentao.guo@amlogic.com>
Add SM_LOAD_VIDEO_FW to the secure monitor command enum
to allow decoder drivers to load firmware through the meson_sm
interface.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
include/linux/firmware/meson/meson_sm.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h
index 3ebc2bd9a976..3b90b49d0552 100644
--- a/include/linux/firmware/meson/meson_sm.h
+++ b/include/linux/firmware/meson/meson_sm.h
@@ -15,6 +15,7 @@ enum {
SM_THERMAL_CALIB_READ,
SM_A1_PWRC_SET,
SM_A1_PWRC_GET,
+ SM_LOAD_VIDEO_FW,
};
struct meson_sm_firmware;
--
2.42.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
@ 2026-08-12 2:41 ` Zhentao Guo via B4 Relay
2026-08-12 2:51 ` sashiko-bot
2026-08-12 2:41 ` [PATCH v7 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
` (3 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
From: Zhentao Guo <zhentao.guo@amlogic.com>
Add SM_LOAD_VIDEO_FW at SMC ID 0xb200000f in the command
table to load video firmware.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
drivers/firmware/meson/meson_sm.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
index ab9751a59b55..887caa5c625d 100644
--- a/drivers/firmware/meson/meson_sm.c
+++ b/drivers/firmware/meson/meson_sm.c
@@ -48,6 +48,7 @@ static const struct meson_sm_chip gxbb_chip = {
CMD(SM_THERMAL_CALIB_READ, 0x82000047),
CMD(SM_A1_PWRC_SET, 0x82000093),
CMD(SM_A1_PWRC_GET, 0x82000095),
+ CMD(SM_LOAD_VIDEO_FW, 0xb200000f),
{ /* sentinel */ },
},
};
--
2.42.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
@ 2026-08-12 2:41 ` Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
` (2 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
From: Zhentao Guo <zhentao.guo@amlogic.com>
Describe the initial support for the V4L2 stateless video decoder
driver used with the Amlogic S4 (S805X2) platform.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
.../devicetree/bindings/media/amlogic,s4-vdec.yaml | 111 +++++++++++++++++++++
1 file changed, 111 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml
new file mode 100644
index 000000000000..b5af3f931b26
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2025 Amlogic, Inc. All rights reserved
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/amlogic,s4-vdec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Amlogic Video Decode Accelerator
+
+maintainers:
+ - Zhentao Guo <zhentao.guo@amlogic.com>
+
+description:
+ The Video Decoder Accelerator present on Amlogic SOCs.
+ It supports stateless h264 decoding.
+
+properties:
+ compatible:
+ const: amlogic,s4-vdec
+
+ reg:
+ maxItems: 2
+
+ reg-names:
+ items:
+ - const: dos
+ - const: dmc
+
+ interrupts:
+ maxItems: 2
+
+ interrupt-names:
+ items:
+ - const: hvdec
+ - const: vdec
+
+ clocks:
+ maxItems: 3
+
+ clock-names:
+ items:
+ - const: dos
+ - const: vdec
+ - const: hevcf
+
+ power-domains:
+ maxItems: 2
+
+ power-domain-names:
+ items:
+ - const: vdec
+ - const: hvdec
+
+ resets:
+ maxItems: 1
+
+ amlogic,canvas:
+ description: should point to a canvas provider node
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+ secure-monitor:
+ description: phandle to the secure-monitor node
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - interrupt-names
+ - clocks
+ - clock-names
+ - power-domains
+ - power-domain-names
+ - resets
+ - amlogic,canvas
+ - secure-monitor
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/amlogic,s4-pll-clkc.h>
+ #include <dt-bindings/clock/amlogic,s4-peripherals-clkc.h>
+ #include <dt-bindings/power/meson-s4-power.h>
+ #include <dt-bindings/reset/amlogic,meson-s4-reset.h>
+ video-codec@fe320000 {
+ compatible = "amlogic,s4-vdec";
+ reg = <0xfe320000 0x10000>,
+ <0xfe036000 0x20>;
+ reg-names = "dos",
+ "dmc";
+ interrupts = <GIC_SPI 91 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 92 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "hvdec",
+ "vdec";
+ clocks = <&clkc_periphs CLKID_DOS>,
+ <&clkc_periphs CLKID_VDEC_SEL>,
+ <&clkc_periphs CLKID_HEVCF_SEL>;
+ clock-names = "dos",
+ "vdec",
+ "hevcf";
+ power-domains = <&pwrc PWRC_S4_DOS_VDEC_ID>,
+ <&pwrc PWRC_S4_DOS_HEVC_ID>;
+ power-domain-names = "vdec",
+ "hvdec";
+ resets = <&reset RESET_DOS>;
+ amlogic,canvas = <&canvas>;
+ secure-monitor = <&sm>;
+ };
--
2.42.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
` (2 preceding siblings ...)
2026-08-12 2:41 ` [PATCH v7 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
@ 2026-08-12 2:41 ` Zhentao Guo via B4 Relay
2026-08-12 3:04 ` sashiko-bot
2026-08-12 2:41 ` [PATCH v7 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
5 siblings, 1 reply; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
From: Zhentao Guo <zhentao.guo@amlogic.com>
Add initial support for V4L2 stateless video decoder
driver on Amlogic S4(S805X2) platform. In phase 1,
it supports 8bit H.264 bitstreams decoding. Currently
only progressive streams are supported.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
MAINTAINERS | 7 +
drivers/media/platform/amlogic/Kconfig | 1 +
drivers/media/platform/amlogic/Makefile | 1 +
drivers/media/platform/amlogic/vdec/Kconfig | 18 +
drivers/media/platform/amlogic/vdec/Makefile | 4 +
drivers/media/platform/amlogic/vdec/TODO | 7 +
drivers/media/platform/amlogic/vdec/aml_vdec.c | 808 ++++++++
drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 +
drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 231 +++
drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 168 ++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 530 +++++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 155 ++
.../platform/amlogic/vdec/aml_vdec_platform.c | 81 +
.../platform/amlogic/vdec/aml_vdec_platform.h | 46 +
.../media/platform/amlogic/vdec/aml_vdec_tee_fw.c | 240 +++
.../media/platform/amlogic/vdec/aml_vdec_tee_fw.h | 27 +
drivers/media/platform/amlogic/vdec/h264.c | 2082 ++++++++++++++++++++
drivers/media/platform/amlogic/vdec/h264.h | 299 +++
drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++
19 files changed, 4915 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index da14a58df9e8..e14be98ce2a2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1430,6 +1430,13 @@ S: Maintained
F: Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml
F: drivers/spi/spi-amlogic-spisg.c
+AMLOGIC VDEC DRIVER
+M: Zhentao Guo <zhentao.guo@amlogic.com>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/media/amlogic,s4-vcodec-dec.yaml
+F: drivers/media/platform/amlogic/vdec/
+
AMPHENOL CHIPCAP 2 DRIVER
M: Javier Carrasco <javier.carrasco.cruz@gmail.com>
L: linux-hwmon@vger.kernel.org
diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig
index 458acf3d5fa8..7c541ac0d0c3 100644
--- a/drivers/media/platform/amlogic/Kconfig
+++ b/drivers/media/platform/amlogic/Kconfig
@@ -4,3 +4,4 @@ comment "Amlogic media platform drivers"
source "drivers/media/platform/amlogic/c3/Kconfig"
source "drivers/media/platform/amlogic/meson-ge2d/Kconfig"
+source "drivers/media/platform/amlogic/vdec/Kconfig"
diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile
index c744afcd1b9e..7409de674c0b 100644
--- a/drivers/media/platform/amlogic/Makefile
+++ b/drivers/media/platform/amlogic/Makefile
@@ -2,3 +2,4 @@
obj-y += c3/
obj-y += meson-ge2d/
+obj-y += vdec/
diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig
new file mode 100644
index 000000000000..2c5dd8452962
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+
+config VIDEO_AMLOGIC_VDEC
+ tristate "Amlogic Video Decoder Driver"
+ depends on ARCH_MESON || COMPILE_TEST
+ depends on VIDEO_DEV
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on TEE
+ depends on MESON_SM
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_H264
+ select V4L2_MEM2MEM_DEV
+ select MESON_CANVAS
+ help
+ This is a v4l2 driver for Amlogic video decoder driver.
+ This driver is designed to support V4L2 M2M STATELESS
+ interface.
+ To compile this driver as a module choose m here.
diff --git a/drivers/media/platform/amlogic/vdec/Makefile b/drivers/media/platform/amlogic/vdec/Makefile
new file mode 100644
index 000000000000..f752716cbf9e
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+aml-vdec-drv-objs := aml_vdec.o aml_vdec_drv.o aml_vdec_hw.o aml_vdec_platform.o h264.o aml_vdec_tee_fw.o\
+
+obj-$(CONFIG_VIDEO_AMLOGIC_VDEC) += aml-vdec-drv.o
diff --git a/drivers/media/platform/amlogic/vdec/TODO b/drivers/media/platform/amlogic/vdec/TODO
new file mode 100644
index 000000000000..54c60145770e
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/TODO
@@ -0,0 +1,7 @@
+TODO list for Amlogic V4L2 stateless decoder driver:
+
+1. Support decoding for HEVC, VP9, AV1, and MPEG-2.
+2. Support more SoCs, including the new T7/S7 series and legacy SoCs (e.g., GXL, SM1, G12B).
+3. Support 10-bit decoding and P010 output.
+ Note: P010 output requires hardware support.
+4. Support interlaced stream decoding for H.264, HEVC, and MPEG-2.
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.c b/drivers/media/platform/amlogic/vdec/aml_vdec.c
new file mode 100644
index 000000000000..e2edf4a3ac0c
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c
@@ -0,0 +1,808 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "aml_vdec.h"
+#include "aml_vdec_hw.h"
+#include "aml_vdec_platform.h"
+#include "aml_vdec_tee_fw.h"
+
+#define VCODEC_DRV_NAME "aml-vdec-drv"
+
+static const struct aml_vdec_v4l2_ctrl controls[] = {
+ {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_STATELESS_H264_SPS,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_STATELESS_H264_PPS,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_STATELESS_H264_SCALING_MATRIX,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_STATELESS_H264_DECODE_MODE,
+ .min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED,
+ .def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED,
+ .max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_STATELESS_H264_START_CODE,
+ .min = V4L2_STATELESS_H264_START_CODE_ANNEX_B,
+ .def = V4L2_STATELESS_H264_START_CODE_ANNEX_B,
+ .max = V4L2_STATELESS_H264_START_CODE_ANNEX_B,
+ },
+ }, {
+ .codec_type = CODEC_TYPE_H264,
+ .cfg = {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+ .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN,
+ },
+ }
+};
+
+static const struct aml_dec_type dec_type_name[] = {
+ {
+ .codec_type = CODEC_TYPE_H264,
+ .name = "H264",
+ },
+};
+
+static const char *dec_type_to_name(unsigned int type)
+{
+ int i;
+ int size = ARRAY_SIZE(dec_type_name);
+
+ for (i = 0; i < size; i++) {
+ if (dec_type_name[i].codec_type == type)
+ return dec_type_name[i].name;
+ }
+
+ return "ERR";
+}
+
+int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx)
+{
+ int i;
+ int ctrls_size = ARRAY_SIZE(controls);
+
+ v4l2_ctrl_handler_init(&ctx->ctrl_handler, ctrls_size);
+ for (i = 0; i < ctrls_size; i++) {
+ v4l2_ctrl_new_custom(&ctx->ctrl_handler, &controls[i].cfg, NULL);
+ if (ctx->ctrl_handler.error) {
+ dev_info(&ctx->dev->plat_dev->dev, "add ctrl for (%d) failed%d\n",
+ controls[i].cfg.id, ctx->ctrl_handler.error);
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ return ctx->ctrl_handler.error;
+ }
+ }
+ ctx->fh.ctrl_handler = &ctx->ctrl_handler;
+ return v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
+}
+
+static void m2mops_vdec_device_run(void *m2m_priv)
+{
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)m2m_priv;
+ struct aml_vdec_dev *dev = ctx->dev;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ struct media_request *src_req;
+ int ret = 0;
+
+ src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ dev_dbg(&dev->plat_dev->dev, "device run : src buf : %d dst buf %d\n",
+ src_buf->vb2_buf.index, dst_buf->vb2_buf.index);
+ if (WARN_ON_ONCE(!ctx->codec_ops->run))
+ goto err_cancel_job;
+
+ src_req = src_buf->vb2_buf.req_obj.req;
+ if (src_req)
+ v4l2_ctrl_request_setup(src_req, &ctx->ctrl_handler);
+ dos_enable(dev->dec_hw);
+ /* incase of bus hang in stop_streaming */
+ ctx->dos_clk_en = 1;
+ ctx->curr_frm_err_flag = 0;
+
+ if (ctx->curr_dec_type == CODEC_TYPE_H264)
+ aml_vdec_reset_core(dev->dec_hw);
+
+ if (load_firmware(dev->dec_hw, ctx->curr_dec_type) < 0)
+ goto err_cancel_job;
+
+ ret = ctx->codec_ops->run(ctx);
+
+ v4l2_m2m_buf_copy_metadata(src_buf, dst_buf);
+ if (src_req)
+ v4l2_ctrl_request_complete(src_req, &ctx->ctrl_handler);
+ if (ret < 0 || ctx->curr_frm_err_flag)
+ goto err_cancel_job;
+ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx,
+ VB2_BUF_STATE_DONE);
+
+ return;
+
+err_cancel_job:
+ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx,
+ VB2_BUF_STATE_ERROR);
+}
+
+const struct v4l2_m2m_ops aml_vdec_m2m_ops = {
+ .device_run = m2mops_vdec_device_run,
+};
+
+static int vidioc_vdec_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, VCODEC_DRV_NAME, sizeof(cap->driver));
+ strscpy(cap->card, "platform:" VCODEC_DRV_NAME, sizeof(cap->card));
+
+ return 0;
+}
+
+static int vidioc_vdec_enum_fmt(struct aml_vdec_ctx *ctx,
+ struct v4l2_fmtdesc *f, bool is_output)
+{
+ struct aml_vdec_dev *dev = ctx->dev;
+ const struct aml_video_fmt *fmt;
+ int i = 0, j = 0;
+
+ for (; i < dev->pvdec_data->num_fmts; i++) {
+ fmt = &dev->pvdec_data->dec_fmt[i];
+ if (is_output && fmt->type != AML_FMT_DEC)
+ continue;
+ if (!is_output && fmt->type != AML_FMT_FRAME)
+ continue;
+
+ if (j == f->index) {
+ f->pixelformat = fmt->fourcc;
+ strscpy(f->description, fmt->name,
+ sizeof(f->description));
+ return 0;
+ }
+ ++j;
+ }
+ return -EINVAL;
+}
+
+static const struct aml_video_fmt *aml_vdec_get_video_fmt(struct aml_vdec_dev
+ *dev, u32 format)
+{
+ const struct aml_video_fmt *fmt;
+ unsigned int k;
+
+ for (k = 0; k < dev->pvdec_data->num_fmts; k++) {
+ fmt = &dev->pvdec_data->dec_fmt[k];
+ if (fmt->fourcc == format)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static int aml_vdec_init_dec_inst(struct aml_vdec_ctx *ctx)
+{
+ struct aml_vdec_dev *dev = ctx->dev;
+ struct aml_video_fmt *fmt_out = &ctx->dec_fmt[AML_FMT_SRC];
+ int ret = -1;
+
+ ctx->codec_ops = &dev->pvdec_data->codec_ops[fmt_out->codec_type];
+ if (ctx->codec_ops->init) {
+ ret = ctx->codec_ops->init(ctx);
+ if (ret < 0)
+ return ret;
+ }
+ ctx->curr_dec_type = fmt_out->codec_type;
+ dev_info(&dev->plat_dev->dev, "%s set curr_dec_type %s\n",
+ __func__, dec_type_to_name(ctx->curr_dec_type));
+
+ return ret;
+}
+
+static void set_pic_info(struct aml_vdec_ctx *ctx,
+ struct v4l2_pix_format_mplane *pix_mp,
+ enum v4l2_buf_type type)
+{
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ ctx->pic_info.output_pix_fmt = pix_mp->pixelformat;
+ ctx->pic_info.coded_width = ALIGN(pix_mp->width, 64);
+ ctx->pic_info.coded_height = ALIGN(pix_mp->height, 64);
+ ctx->pic_info.fb_size[0] =
+ ctx->pic_info.coded_width * ctx->pic_info.coded_height;
+ ctx->pic_info.fb_size[1] = ctx->pic_info.fb_size[0] / 2;
+ }
+}
+
+static int vidioc_vdec_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ const struct aml_video_fmt *fmt;
+ struct aml_vdec_dev *dev = video_drvdata(file);
+
+ if (fsize->index != 0)
+ return -EINVAL;
+
+ fmt = aml_vdec_get_video_fmt(dev, fsize->pixel_format);
+ if (!fmt)
+ return -EINVAL;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+ fsize->stepwise = fmt->stepwise;
+
+ return 0;
+}
+
+static int vdec_try_fmt_mp(struct aml_vdec_ctx *ctx, enum v4l2_buf_type type,
+ struct v4l2_pix_format_mplane *pix_mp,
+ const struct aml_video_fmt *dec_fmt)
+{
+ int i, align;
+
+ pix_mp->field = V4L2_FIELD_NONE;
+ align = ctx->dev->pvdec_data->dec_fmt->align;
+
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ pix_mp->num_planes = dec_fmt->num_planes;
+ pix_mp->pixelformat = dec_fmt->fourcc;
+ if (!pix_mp->plane_fmt[0].sizeimage)
+ pix_mp->plane_fmt[0].sizeimage =
+ (pix_mp->height * pix_mp->width * 3) / 2;
+ pix_mp->height = ALIGN(pix_mp->height, align);
+ pix_mp->width = ALIGN(pix_mp->width, align);
+ } else {
+ pix_mp->height = ctx->pix_fmt[AML_FMT_SRC].height;
+ pix_mp->width = ctx->pix_fmt[AML_FMT_SRC].width;
+ }
+
+ v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height,
+ &dec_fmt->stepwise);
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s type %d four_cc %d pix_mp->width %d pix_mp->height %d\n",
+ __func__, type, dec_fmt->fourcc, pix_mp->width, pix_mp->height);
+
+ v4l2_fill_pixfmt_mp(pix_mp, dec_fmt->fourcc, pix_mp->width,
+ pix_mp->height);
+
+ for (i = 0; i < pix_mp->num_planes; i++)
+ memset(&pix_mp->plane_fmt[i].reserved[0], 0x0,
+ sizeof(pix_mp->plane_fmt[0].reserved));
+
+ memset(pix_mp->reserved, 0x0, sizeof(pix_mp->reserved));
+ pix_mp->flags = 0;
+
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s type %d fmt %d num_plane %d sizeimage0 %d sizeimage1 %d\n",
+ __func__, type, pix_mp->pixelformat, pix_mp->num_planes,
+ pix_mp->plane_fmt[0].sizeimage, pix_mp->plane_fmt[1].sizeimage);
+
+ return 0;
+}
+
+static int vdec_s_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ struct v4l2_pix_format_mplane *cap_fmt = &ctx->pix_fmt[AML_FMT_DST];
+ const struct aml_video_fmt *out_fmt;
+ struct vb2_queue *vq, *peer_vq;
+ int ret;
+
+ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+ if (vb2_is_busy(vq) &&
+ pix_mp->pixelformat != ctx->pix_fmt[AML_FMT_SRC].pixelformat)
+ return -EBUSY;
+
+ /*
+ * Since s_fmt output will reset the width and height in the capture queue,
+ * s_fmt_output with resolution change should not take effect when capture
+ * queue is busy.
+ */
+ peer_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (vb2_is_busy(peer_vq) &&
+ (pix_mp->width != cap_fmt->width ||
+ pix_mp->height != cap_fmt->height))
+ return -EBUSY;
+
+ out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat);
+ if (out_fmt) {
+ ctx->dec_fmt[AML_FMT_SRC] = *out_fmt;
+ } else {
+ out_fmt = &ctx->dec_fmt[AML_FMT_SRC];
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s fmt %d not supported, use default\n", __func__,
+ pix_mp->pixelformat);
+ }
+
+ ret = vdec_try_fmt_mp(ctx, f->type, pix_mp, out_fmt);
+ set_pic_info(ctx, pix_mp, f->type);
+
+ ctx->pix_fmt[AML_FMT_SRC] = *pix_mp;
+ cap_fmt->width = pix_mp->width;
+ cap_fmt->height = pix_mp->height;
+ v4l2_fill_pixfmt_mp(cap_fmt, ctx->dec_fmt[AML_FMT_DST].fourcc,
+ cap_fmt->width, cap_fmt->height);
+ cap_fmt->colorspace = pix_mp->colorspace;
+ cap_fmt->quantization = pix_mp->quantization;
+ cap_fmt->ycbcr_enc = pix_mp->ycbcr_enc;
+ cap_fmt->xfer_func = pix_mp->xfer_func;
+
+ return ret;
+}
+
+static int vdec_s_fmt_capture(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ const struct aml_video_fmt *cap_fmt;
+ struct vb2_queue *vq;
+ int ret;
+
+ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (vb2_is_busy(vq))
+ return -EBUSY;
+
+ cap_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat);
+ if (cap_fmt) {
+ ctx->dec_fmt[AML_FMT_DST] = *cap_fmt;
+ } else {
+ cap_fmt = &ctx->dec_fmt[AML_FMT_DST];
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s fmt %d not supported, use default\n", __func__,
+ pix_mp->pixelformat);
+ }
+
+ ret = vdec_try_fmt_mp(ctx, f->type, pix_mp, cap_fmt);
+
+ ctx->pix_fmt[AML_FMT_DST] = *pix_mp;
+
+ return ret;
+}
+
+static int vdec_try_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ const struct aml_video_fmt *out_fmt;
+ int ret;
+
+ out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat);
+ if (!out_fmt) {
+ out_fmt = &ctx->dec_fmt[AML_FMT_SRC];
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s fmt %d not supported, use default\n", __func__,
+ pix_mp->pixelformat);
+ }
+
+ ret = vdec_try_fmt_mp(ctx, f->type, pix_mp, out_fmt);
+
+ return ret;
+}
+
+static int vdec_try_fmt_capture(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ const struct aml_video_fmt *cap_fmt;
+ int ret;
+
+ cap_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat);
+ if (!cap_fmt) {
+ cap_fmt = &ctx->dec_fmt[AML_FMT_DST];
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s fmt %d not supported, use default\n", __func__,
+ pix_mp->pixelformat);
+ }
+
+ ret = vdec_try_fmt_mp(ctx, f->type, pix_mp, cap_fmt);
+
+ return ret;
+}
+
+static void reset_output_fmts(struct aml_vdec_ctx *ctx)
+{
+ struct aml_vdec_dev *dev = ctx->dev;
+ const struct aml_video_fmt *out_fmt;
+ struct v4l2_pix_format_mplane fmt;
+
+ /* reset default output fmt to V4L2_PIX_FMT_H264_SLICE */
+ out_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_H264_SLICE);
+ if (!out_fmt)
+ return;
+
+ ctx->dec_fmt[AML_FMT_SRC] = *out_fmt;
+
+ memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane));
+
+ fmt.height = out_fmt->stepwise.min_height;
+ fmt.width = out_fmt->stepwise.min_width;
+ /* reset bytesperline to 0 for output fmts */
+ fmt.plane_fmt[0].bytesperline = 0;
+ fmt.colorspace = V4L2_COLORSPACE_DEFAULT;
+ fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
+ fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &fmt,
+ out_fmt);
+
+ ctx->pix_fmt[AML_FMT_SRC] = fmt;
+}
+
+static void reset_capture_fmts(struct aml_vdec_ctx *ctx)
+{
+ struct aml_vdec_dev *dev = ctx->dev;
+ const struct aml_video_fmt *cap_fmt;
+ struct v4l2_pix_format_mplane fmt;
+
+ /* reset default output fmt to V4L2_PIX_FMT_NV12 */
+ cap_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_NV12);
+ if (!cap_fmt)
+ return;
+
+ ctx->dec_fmt[AML_FMT_DST] = *cap_fmt;
+
+ memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane));
+
+ fmt.height = cap_fmt->stepwise.min_height;
+ fmt.width = cap_fmt->stepwise.min_width;
+ fmt.colorspace = V4L2_COLORSPACE_DEFAULT;
+ fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt.quantization = V4L2_QUANTIZATION_DEFAULT;
+ fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, &fmt,
+ cap_fmt);
+
+ ctx->pix_fmt[AML_FMT_DST] = fmt;
+}
+
+void aml_vdec_reset_fmts(struct aml_vdec_ctx *ctx)
+{
+ ctx->m2m_ctx->q_lock = &ctx->v4l2_intf_lock;
+ reset_output_fmts(ctx);
+ reset_capture_fmts(ctx);
+}
+
+static int vdec_g_fmt(struct aml_vdec_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type))
+ *pix_mp = ctx->pix_fmt[AML_FMT_SRC];
+ else
+ *pix_mp = ctx->pix_fmt[AML_FMT_DST];
+
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s fmt %d num planes %d\n", __func__, pix_mp->pixelformat,
+ pix_mp->num_planes);
+
+ return 0;
+}
+
+static int vidioc_try_fmt_cap_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vdec_try_fmt_capture(ctx, f);
+}
+
+static int vidioc_try_fmt_out_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vdec_try_fmt_output(ctx, f);
+}
+
+static int vidioc_vdec_s_fmt_out_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vdec_s_fmt_output(ctx, f);
+}
+
+static int vidioc_vdec_s_fmt_cap_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vdec_s_fmt_capture(ctx, f);
+}
+
+static int vidioc_vdec_g_fmt_out_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vdec_g_fmt(ctx, f);
+}
+
+static int vidioc_vdec_g_fmt_cap_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vdec_g_fmt(ctx, f);
+}
+
+static int vidioc_vdec_enum_fmt_out_mplane(struct file *file,
+ void *priv, struct v4l2_fmtdesc *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vidioc_vdec_enum_fmt(ctx, f, 1);
+}
+
+static int vidioc_vdec_enum_fmt_cap_mplane(struct file *file,
+ void *priv, struct v4l2_fmtdesc *f)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ return vidioc_vdec_enum_fmt(ctx, f, 0);
+}
+
+const struct v4l2_ioctl_ops aml_vdec_ioctl_ops = {
+ .vidioc_querycap = vidioc_vdec_querycap,
+ .vidioc_enum_framesizes = vidioc_vdec_enum_framesizes,
+
+ .vidioc_enum_fmt_vid_cap = vidioc_vdec_enum_fmt_cap_mplane,
+ .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_cap_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = vidioc_vdec_s_fmt_cap_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = vidioc_vdec_g_fmt_cap_mplane,
+
+ .vidioc_enum_fmt_vid_out = vidioc_vdec_enum_fmt_out_mplane,
+ .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_out_mplane,
+ .vidioc_s_fmt_vid_out_mplane = vidioc_vdec_s_fmt_out_mplane,
+ .vidioc_g_fmt_vid_out_mplane = vidioc_vdec_g_fmt_out_mplane,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+
+ .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd,
+ .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+};
+
+static void aml_vdec_release_instance(struct aml_vdec_ctx *ctx)
+{
+ if (ctx->codec_ops && ctx->codec_ops->exit)
+ ctx->codec_ops->exit(ctx);
+}
+
+static int vb2ops_vdec_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers,
+ unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vq);
+ struct v4l2_pix_format_mplane *pix_fmt;
+ unsigned int i;
+
+ if (V4L2_TYPE_IS_OUTPUT(vq->type))
+ pix_fmt = &ctx->pix_fmt[AML_FMT_SRC];
+ else
+ pix_fmt = &ctx->pix_fmt[AML_FMT_DST];
+
+ if (*nplanes) {
+ if (*nplanes != pix_fmt->num_planes)
+ return -EINVAL;
+
+ for (i = 0; i < *nplanes; i++) {
+ if (sizes[i] < pix_fmt->plane_fmt[i].sizeimage) {
+ dev_err(&ctx->dev->plat_dev->dev,
+ "not supported sizeimage\n");
+ return -EINVAL;
+ }
+ alloc_devs[i] = &ctx->dev->plat_dev->dev;
+ }
+ } else {
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ *nplanes = pix_fmt->num_planes;
+ else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ *nplanes = 1;
+
+ for (i = 0; i < *nplanes; i++) {
+ alloc_devs[i] = &ctx->dev->plat_dev->dev;
+ sizes[i] = pix_fmt->plane_fmt[i].sizeimage;
+ }
+ }
+
+ if (*nplanes) {
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "type: %d, plane: %d, buf cnt: %d, size: [Y: %u, C: %u]\n",
+ vq->type, *nplanes, *nbuffers, sizes[0], sizes[1]);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int vb2ops_vdec_buf_prepare(struct vb2_buffer *vb)
+{
+ struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct v4l2_pix_format_mplane *pix_fmt;
+ unsigned int sizeimage = 0;
+ int i;
+
+ if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type))
+ pix_fmt = &ctx->pix_fmt[AML_FMT_SRC];
+ else
+ pix_fmt = &ctx->pix_fmt[AML_FMT_DST];
+
+ for (i = 0; i < pix_fmt->num_planes; i++) {
+ sizeimage = pix_fmt->plane_fmt[i].sizeimage;
+ if (vb2_plane_size(vb, i) < sizeimage)
+ return -EINVAL;
+
+ if (V4L2_TYPE_IS_CAPTURE(vb->type)) {
+ vb2_set_plane_payload(vb, i,
+ pix_fmt->plane_fmt[i].sizeimage);
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s type: %d set plane: %d, sizeimage: %d\n",
+ __func__, vb->vb2_queue->type, i,
+ pix_fmt->plane_fmt[i].sizeimage);
+ }
+ }
+
+ return 0;
+}
+
+static int vb2_ops_vdec_buf_init(struct vb2_buffer *vb)
+{
+ return 0;
+}
+
+static void vb2_ops_vdec_buf_queue(struct vb2_buffer *vb)
+{
+ struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vb2_v4l2 = to_vb2_v4l2_buffer(vb);
+
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb2_v4l2);
+}
+
+static void vb2_ops_vdec_buf_finish(struct vb2_buffer *vb)
+{
+}
+
+static int vb2ops_vdec_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q);
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+ struct vb2_v4l2_buffer *src_buf;
+
+ ctx->is_output_streamon = 1;
+ if (aml_vdec_init_dec_inst(ctx) < 0) {
+ while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)))
+ v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_QUEUED);
+ return -EINVAL;
+ }
+ } else {
+ ctx->is_cap_streamon = 1;
+ }
+
+ return 0;
+}
+
+static void vb2ops_vdec_stop_streaming(struct vb2_queue *q)
+{
+ struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *src_buf = NULL, *dst_buf = NULL;
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+ while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)))
+ v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+ ctx->is_output_streamon = 0;
+ } else {
+ while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx)))
+ v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
+ ctx->is_cap_streamon = 0;
+ }
+
+ if (!ctx->is_output_streamon && !ctx->is_cap_streamon)
+ aml_vdec_release_instance(ctx);
+}
+
+static int vb2ops_vdec_out_buf_validate(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ vbuf->field = V4L2_FIELD_NONE;
+ return 0;
+}
+
+static void vb2ops_vdec_buf_request_complete(struct vb2_buffer *vb)
+{
+ struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+ v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->ctrl_handler);
+}
+
+static const struct vb2_ops aml_vdec_vb2_ops = {
+ .queue_setup = vb2ops_vdec_queue_setup,
+ .start_streaming = vb2ops_vdec_start_streaming,
+ .stop_streaming = vb2ops_vdec_stop_streaming,
+
+ .buf_init = vb2_ops_vdec_buf_init,
+ .buf_prepare = vb2ops_vdec_buf_prepare,
+ .buf_out_validate = vb2ops_vdec_out_buf_validate,
+ .buf_queue = vb2_ops_vdec_buf_queue,
+ .buf_finish = vb2_ops_vdec_buf_finish,
+ .buf_request_complete = vb2ops_vdec_buf_request_complete,
+};
+
+int aml_vdec_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv;
+ struct aml_vdec_dev *dev = ctx->dev;
+ int ret = 0;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->drv_priv = ctx;
+ src_vq->ops = &aml_vdec_vb2_ops;
+ src_vq->lock = &ctx->v4l2_intf_lock;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->supports_requests = true;
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ ret = vb2_queue_init(src_vq);
+ if (ret) {
+ v4l2_info(&dev->v4l2_dev,
+ "Failed to initialize videobuf2 queue(output)");
+ return ret;
+ }
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ dst_vq->drv_priv = ctx;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->ops = &aml_vdec_vb2_ops;
+ dst_vq->lock = &ctx->v4l2_intf_lock;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ ret = vb2_queue_init(dst_vq);
+ if (ret) {
+ v4l2_info(&dev->v4l2_dev,
+ "Failed to initialize videobuf2 queue(capture)");
+ vb2_queue_release(src_vq);
+ }
+
+ return ret;
+}
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.h b/drivers/media/platform/amlogic/vdec/aml_vdec.h
new file mode 100644
index 000000000000..32f7fa245f7e
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef _AML_VDEC_H_
+#define _AML_VDEC_H_
+
+#include "aml_vdec_drv.h"
+
+/**
+ * struct aml_vdec_v4l2_ctrl - helper type to declare supported ctrls
+ * @codec_type: codec id this control belong to (CODEC_TYPE_H264, etc.)
+ * @cfg: control configuration
+ */
+struct aml_vdec_v4l2_ctrl {
+ unsigned int codec_type;
+ struct v4l2_ctrl_config cfg;
+};
+
+struct aml_dec_type {
+ unsigned int codec_type;
+ const char *name;
+};
+
+extern const struct v4l2_m2m_ops aml_vdec_m2m_ops;
+extern const struct v4l2_ioctl_ops aml_vdec_ioctl_ops;
+
+int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx);
+int aml_vdec_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq);
+void aml_vdec_reset_fmts(struct aml_vdec_ctx *ctx);
+#endif
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
new file mode 100644
index 000000000000..21ae2418044a
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+
+#include "aml_vdec.h"
+#include "aml_vdec_hw.h"
+#include "aml_vdec_platform.h"
+
+#define AML_VDEC_DRV_NAME "aml-vdec-drv"
+
+static int fops_vcodec_open(struct file *file)
+{
+ struct aml_vdec_dev *dec_dev = video_drvdata(file);
+ struct aml_vdec_ctx *ctx = NULL;
+ int ret = 0;
+
+ ctx = kzalloc_obj(*ctx, GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ mutex_lock(&dec_dev->dev_mutex);
+ ctx->dev = dec_dev;
+ v4l2_fh_init(&ctx->fh, video_devdata(file));
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh, file);
+ mutex_init(&ctx->v4l2_intf_lock);
+ init_waitqueue_head(&ctx->queue);
+ ctx->int_cond = 0;
+
+ ctx->m2m_ctx = v4l2_m2m_ctx_init(dec_dev->m2m_dev_dec, ctx,
+ &aml_vdec_queue_init);
+ if (IS_ERR(ctx->m2m_ctx)) {
+ ret = PTR_ERR((__force void *)ctx->m2m_ctx);
+ v4l2_err(&dec_dev->v4l2_dev, "Failed to v4l2_m2m_ctx_init() (%d)", ret);
+ goto err_m2m_ctx_init;
+ }
+
+ ctx->fh.m2m_ctx = ctx->m2m_ctx;
+ ret = aml_vdec_ctrls_setup(ctx);
+ if (ret) {
+ v4l2_err(&dec_dev->v4l2_dev, "Failed to init all ctrls (%d)", ret);
+ goto err_ctrls_setup;
+ }
+
+ aml_vdec_reset_fmts(ctx);
+ mutex_unlock(&dec_dev->dev_mutex);
+
+ return ret;
+
+err_ctrls_setup:
+ v4l2_m2m_ctx_release(ctx->m2m_ctx);
+err_m2m_ctx_init:
+ v4l2_fh_del(&ctx->fh, file);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ mutex_unlock(&dec_dev->dev_mutex);
+
+ return ret;
+}
+
+static int fops_vcodec_release(struct file *file)
+{
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ v4l2_m2m_ctx_release(ctx->m2m_ctx);
+ v4l2_fh_del(&ctx->fh, file);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations aml_vdec_fops = {
+ .owner = THIS_MODULE,
+ .open = fops_vcodec_open,
+ .release = fops_vcodec_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static const struct video_device dec_dev = {
+ .name = "aml_dev_drv",
+ .fops = &aml_vdec_fops,
+ .ioctl_ops = &aml_vdec_ioctl_ops,
+ .release = video_device_release,
+ .vfl_dir = VFL_DIR_M2M,
+ .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING,
+};
+
+static const struct media_device_ops aml_m2m_media_ops = {
+ .req_validate = vb2_request_validate,
+ .req_queue = v4l2_m2m_request_queue,
+};
+
+static int aml_vdec_drv_probe(struct platform_device *pdev)
+{
+ struct aml_vdec_dev *dev;
+ struct video_device *vfd_dec;
+ struct aml_vdec_hw *hw;
+ int ret = 0;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->plat_dev = pdev;
+ mutex_init(&dev->dev_mutex);
+
+ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "v4l2_device_register err\n");
+
+ vfd_dec = video_device_alloc();
+ if (!vfd_dec) {
+ v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto err_device_alloc;
+ }
+ *vfd_dec = dec_dev;
+ vfd_dec->v4l2_dev = &dev->v4l2_dev;
+ vfd_dec->lock = &dev->dev_mutex;
+ video_set_drvdata(vfd_dec, dev);
+ dev->vfd = vfd_dec;
+ platform_set_drvdata(pdev, dev);
+
+ hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
+ if (!hw) {
+ ret = -ENOMEM;
+ goto err_dec_mem_init;
+ }
+ dev->dec_hw = hw;
+
+ dev->pvdec_data = of_device_get_match_data(&pdev->dev);
+ ret = dev->pvdec_data->req_hw_resource(dev);
+ if (ret < 0)
+ goto err_hw_init;
+
+ dev->m2m_dev_dec = v4l2_m2m_init(&aml_vdec_m2m_ops);
+ if (IS_ERR(dev->m2m_dev_dec)) {
+ v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem dec device\n");
+ ret = PTR_ERR((__force void *)dev->m2m_dev_dec);
+ goto err_m2m_dev_init;
+ }
+
+ ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register video device");
+ goto err_vid_dev_register;
+ }
+
+ dev->mdev.dev = &pdev->dev;
+ strscpy(dev->mdev.model, AML_VDEC_DRV_NAME, sizeof(dev->mdev.model));
+ media_device_init(&dev->mdev);
+ dev->mdev.ops = &aml_m2m_media_ops;
+ dev->v4l2_dev.mdev = &dev->mdev;
+
+ ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, vfd_dec,
+ MEDIA_ENT_F_PROC_VIDEO_DECODER);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n");
+ goto error_m2m_mc_register;
+ }
+
+ ret = media_device_register(&dev->mdev);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register media device");
+ goto err_media_dev_register;
+ }
+ vdec_enable(dev->dec_hw);
+ return 0;
+
+err_media_dev_register:
+ v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
+error_m2m_mc_register:
+ media_device_cleanup(&dev->mdev);
+ video_unregister_device(dev->vfd);
+err_vid_dev_register:
+ v4l2_m2m_release(dev->m2m_dev_dec);
+err_m2m_dev_init:
+ dev->pvdec_data->destroy_hw_resource(dev);
+err_hw_init:
+ dev->dec_hw = NULL;
+err_dec_mem_init:
+ video_device_release(vfd_dec);
+err_device_alloc:
+ v4l2_device_unregister(&dev->v4l2_dev);
+ return ret;
+}
+
+static void aml_vdec_drv_remove(struct platform_device *pdev)
+{
+ struct aml_vdec_dev *dev = platform_get_drvdata(pdev);
+
+ vdec_disable(dev->dec_hw);
+
+ media_device_unregister(&dev->mdev);
+ v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
+ media_device_cleanup(&dev->mdev);
+
+ v4l2_m2m_release(dev->m2m_dev_dec);
+ video_unregister_device(dev->vfd);
+ dev->pvdec_data->destroy_hw_resource(dev);
+ v4l2_device_unregister(&dev->v4l2_dev);
+}
+
+static const struct of_device_id aml_vdec_match[] = {
+ { .compatible = "amlogic,s4-vdec", .data = &aml_vdec_s4_pdata },
+ { }
+};
+
+static struct platform_driver aml_vcodec_dec_driver = {
+ .probe = aml_vdec_drv_probe,
+ .remove = aml_vdec_drv_remove,
+ .driver = {
+ .name = AML_VDEC_DRV_NAME,
+ .of_match_table = aml_vdec_match,
+ },
+};
+
+module_platform_driver(aml_vcodec_dec_driver);
+
+MODULE_DESCRIPTION("Amlogic V4L2 decoder driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h
new file mode 100644
index 000000000000..23bf1a8ca4d6
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef _AML_VDEC_DRV_H_
+#define _AML_VDEC_DRV_H_
+
+#include <linux/platform_device.h>
+#include <linux/videodev2.h>
+#include <linux/clk.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define AML_VCODEC_MAX_PLANES 3
+#define AML_VDEC_MIN_W 64U
+#define AML_VDEC_MIN_H 64U
+#define AML_VDEC_1080P_MAX_H 1088U
+#define AML_VDEC_1080P_MAX_W 1920U
+
+struct aml_vdec_ctx;
+/**
+ * enum aml_fmt_type - Type of format type
+ */
+enum aml_fmt_type {
+ AML_FMT_DEC = 0,
+ AML_FMT_FRAME = 1,
+};
+
+/**
+ * enum aml_codec_type - Type of codec format
+ */
+enum aml_codec_type {
+ CODEC_TYPE_H264 = 0,
+ CODEC_TYPE_FRAME,
+};
+
+/**
+ * enum aml_queue_type - Type of queue : cap or output
+ */
+enum aml_queue_type {
+ AML_FMT_SRC = 0,
+ AML_FMT_DST = 1,
+};
+
+/**
+ * struct aml_video_fmt - aml video decoder fmt information
+ * @fourcc: FourCC code of the format. See V4L2_PIX_FMT_*.
+ * @align: Describe the align width/height required by hardware.
+ * @is_10_bit_support: If the curr platform support p010 output.
+ * @type: Curr queue type: capture or output.
+ * @codec_type: Codec mode related. See aml_codec_type.
+ * @num_planes: Num planes of the format.
+ * @name: Name of the format.
+ * @stepwise: Supported range of frame sizes (only for bitstream formats).
+ */
+struct aml_video_fmt {
+ u32 fourcc;
+ int align;
+ int is_10_bit_support;
+ enum aml_fmt_type type;
+ enum aml_codec_type codec_type;
+ u32 num_planes;
+ const u8 *name;
+ struct v4l2_frmsize_stepwise stepwise;
+};
+
+/**
+ * struct aml_vdec_dev - driver data
+ * @plat_dev: Platform device for the current driver.
+ * @v4l2_dev: V4L2 device to register video devices for.
+ * @m2m_dev_dec: Mem2mem device associated to this device.
+ * @vfd: Video_device associated to this device.
+ * @mdev: Media_device associated to this device.
+ * @dec_hw: Decoder hardware resources. See struct aml_vdec_hw.
+ * @pvdec_data: Decoder platform data. See struct aml_dev_platform_data.
+ * @dev_mutex: video_device lock.
+ */
+struct aml_vdec_dev {
+ struct platform_device *plat_dev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_m2m_dev *m2m_dev_dec;
+ struct video_device *vfd;
+ struct media_device mdev;
+
+ struct aml_vdec_hw *dec_hw;
+ const struct aml_dev_platform_data *pvdec_data;
+
+ struct mutex dev_mutex;
+};
+
+/**
+ * struct dec_pic_info - pic information description
+ * @cap_pix_fmt: Pixel format for capture queue.
+ * @output_pix_fmt: Pixel format for output queue.
+ * @coded_width: Width for decode.
+ * @coded_height: Height for decode.
+ * @fb_size: Frame buffer size for Y or UV.
+ * @plane_num: Num for planes for curr format.
+ */
+struct dec_pic_info {
+ u32 cap_pix_fmt;
+ u32 output_pix_fmt;
+ u32 coded_width;
+ u32 coded_height;
+ u32 fb_size[2];
+};
+
+/**
+ * struct aml_vdec_ctx - driver instance context
+ * @dev: pointer to the aml_vdec_dev of the device.
+ * @fh: struct v4l2 fh.
+ * @m2m_ctx: pointer to v4l2_m2m_ctx context.
+ * @ctrl_handler: V4L2 ctrl handler.
+ * @v4l2_intf_lock: Mutex lock for v4l2 interface.
+ * @codec_ops: Codec operation functions. See struct aml_codec_ops.
+ * @int_cond: Variable used by the waitqueue.
+ * @queue: Waitqueue to wait for the current decode context finish.
+ * @pix_fmt: To store the V4L2 pixel format.
+ * @dec_fmt: To describe the decoding format supported by hardware platform.
+ * @is_cap_streamon: indicates if the current capture stream is on.
+ * @is_output_streamon: indicates if the current output stream is on.
+ * @dos_clk_en: indicates if dos clk is enabled.
+ * @pic_info: Pic information for curr decoder context. See struct dec_pic_info.
+ * @curr_dec_type: Current decoder type. (CODEC_TYPE_H264, etc.)
+ * @codec_priv: Pointer to current decoder instance.
+ */
+struct aml_vdec_ctx {
+ struct aml_vdec_dev *dev;
+ struct v4l2_fh fh;
+ struct v4l2_m2m_ctx *m2m_ctx;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct mutex v4l2_intf_lock;
+
+ const struct aml_codec_ops *codec_ops;
+ int int_cond;
+ wait_queue_head_t queue;
+ struct v4l2_pix_format_mplane pix_fmt[2];
+ struct aml_video_fmt dec_fmt[2];
+
+ bool is_cap_streamon;
+ bool is_output_streamon;
+ bool dos_clk_en;
+ bool curr_frm_err_flag;
+
+ struct dec_pic_info pic_info;
+ u32 curr_dec_type;
+ void *codec_priv;
+};
+
+static inline struct aml_vdec_ctx *fh_to_dec_ctx(struct file *file)
+{
+ struct v4l2_fh *file_fh = file_to_v4l2_fh(file);
+
+ return container_of(file_fh, struct aml_vdec_ctx, fh);
+}
+
+static inline struct aml_vdec_ctx *ctrl_to_dec_ctx(struct v4l2_ctrl_handler *ctrl)
+{
+ return container_of(ctrl, struct aml_vdec_ctx, ctrl_handler);
+}
+
+#endif
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
new file mode 100644
index 000000000000..c5ad88de9914
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
@@ -0,0 +1,530 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/iopoll.h>
+
+#include "aml_vdec_drv.h"
+#include "aml_vdec_hw.h"
+#include "aml_vdec_platform.h"
+#include "aml_vdec_tee_fw.h"
+
+#define MHz (1000000)
+#define MC_SIZE (4096 * 16)
+
+static struct pm_pd_s vdec_domain_data[] = {
+ [VDEC] = {.name = "vdec", },
+ [HVDEC] = {.name = "hvdec", },
+};
+
+u32 read_dos_reg(struct aml_vdec_hw *hw, u32 addr)
+{
+ u32 ret_val;
+
+ regmap_read(hw->map[DOS_BUS], addr, &ret_val);
+
+ return ret_val;
+}
+
+static void dos_reg_write_bits(struct aml_vdec_hw *hw, u32 reg, u32 val, int start, int len)
+{
+ u32 mask = (((1L << (len)) - 1) << (start));
+
+ regmap_update_bits(hw->map[DOS_BUS], reg, mask, val);
+}
+
+void dos_enable(struct aml_vdec_hw *hw)
+{
+ dos_reg_write_bits(hw, DOS_GCLK_EN0, 0x3ff, 0, 10);
+
+ regmap_write(hw->map[DOS_BUS], GCLK_EN, 0x3ff);
+
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0);
+}
+
+void aml_vdec_reset_core(struct aml_vdec_hw *hw)
+{
+ unsigned int mask = 0;
+
+ mask = (1 << 21);
+
+ regmap_update_bits(hw->map[DMC_BUS], 0x0, mask, 0);
+ usleep_range(60, 70);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0,
+ (1 << 3) | (1 << 4) | (1 << 5) | (1 << 7) |
+ (1 << 8) | (1 << 9));
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0);
+ regmap_update_bits(hw->map[DOS_BUS], VDEC_ASSIST_MMC_CTRL1, 1 << 3, 0);
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_MUX_CTRL, 1 << 31, 0);
+ regmap_write(hw->map[DOS_BUS], MDEC_EXTIF_CFG1, 0);
+
+ regmap_update_bits(hw->map[DMC_BUS], 0x0, mask, mask);
+}
+
+void aml_start_vdec_hw(struct aml_vdec_hw *hw)
+{
+ u32 reg_read_val;
+
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val);
+
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, (1 << 12) | (1 << 11));
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0);
+
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val);
+
+ regmap_write(hw->map[DOS_BUS], MPSR, 0x0001);
+}
+
+void aml_stop_vdec_hw(struct aml_vdec_hw *hw)
+{
+ u32 reg_val = 0;
+ int ret;
+
+ regmap_write(hw->map[DOS_BUS], MPSR, 0);
+ regmap_write(hw->map[DOS_BUS], CPSR, 0);
+
+ ret = read_poll_timeout_atomic(read_dos_reg, reg_val,
+ !(reg_val & 0x8000),
+ 10, 100000, true,
+ hw, IMEM_DMA_CTRL);
+
+ ret = read_poll_timeout_atomic(read_dos_reg, reg_val,
+ !(reg_val & 0x8000),
+ 10, 100000, true,
+ hw, LMEM_DMA_CTRL);
+
+ ret = read_poll_timeout_atomic(read_dos_reg, reg_val,
+ !(reg_val & 0xfff),
+ 10, 300000, true,
+ hw, WRRSP_LMEM);
+ if (ret)
+ dev_err(hw->dev, "%s, ctrl %x, rsp %x, pc %x status %x,%x\n",
+ __func__, read_dos_reg(hw, LMEM_DMA_CTRL),
+ read_dos_reg(hw, WRRSP_LMEM), read_dos_reg(hw, MPC_E),
+ read_dos_reg(hw, AV_SCRATCH_J), read_dos_reg(hw, AV_SCRATCH_9));
+
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val);
+
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, (1 << 12) | (1 << 11));
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0);
+
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val);
+ regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val);
+}
+
+static int vdec_clock_gate_init(struct aml_vdec_hw *hw)
+{
+ hw->gates[CLK_DOS].id = "dos";
+ hw->gates[CLK_VDEC].id = "vdec";
+ hw->gates[CLK_HVDEC].id = "hevcf";
+
+ return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates);
+}
+
+static struct clk_bulk_data *vdec_get_clk_by_name(struct aml_vdec_hw *hw,
+ const char *name)
+{
+ int i;
+
+ for (i = 0; i < CLK_MAX; i++) {
+ if (!strcmp(name, hw->gates[i].id)) {
+ if (hw->gates[i].clk)
+ return &hw->gates[i];
+ }
+ }
+ return NULL;
+}
+
+static void pm_vdec_power_domain_release(struct aml_vdec_hw *hw)
+{
+ int i;
+ const struct power_manager_s *pm = hw->pm;
+ struct pm_pd_s *pd = pm->pd_data;
+
+ for (i = 0; i < VDEC_MAX; i++) {
+ if (!IS_ERR_OR_NULL(pd[i].link))
+ device_link_del(pd[i].link);
+
+ if (!IS_ERR_OR_NULL(pd[i].dev))
+ dev_pm_domain_detach(pd[i].dev, true);
+ }
+}
+
+static int pm_vdec_power_domain_init(struct aml_vdec_hw *hw)
+{
+ int i, err;
+ const struct power_manager_s *pm = hw->pm;
+ struct pm_pd_s *pd = pm->pd_data;
+
+ mutex_init(&hw->pm_mutex);
+
+ for (i = 0; i < VDEC_MAX; i++) {
+ pd[i].dev = dev_pm_domain_attach_by_name(hw->dev, pd[i].name);
+ if (IS_ERR_OR_NULL(pd[i].dev)) {
+ err = PTR_ERR(pd[i].dev);
+ dev_dbg(hw->dev, "Get %s failed, pm-domain: %d\n",
+ pd[i].name, err);
+ pd[i].dev = NULL;
+ continue;
+ }
+
+ pd[i].link = device_link_add(hw->dev, pd[i].dev,
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_STATELESS);
+ if (IS_ERR_OR_NULL(pd[i].link)) {
+ dev_err(hw->dev, "Adding %s device link failed!\n", pd[i].name);
+ goto err_release_pm;
+ }
+
+ dev_dbg(hw->dev, "power domain: name: %s, dev: %p, link: %p\n",
+ pd[i].name, pd[i].dev, pd[i].link);
+ }
+
+ return 0;
+err_release_pm:
+ pm_vdec_power_domain_release(hw);
+ return -ENODEV;
+}
+
+static void dos_local_config(struct aml_vdec_hw *hw, bool is_on, int id)
+{
+ if (is_on) {
+ usleep_range(20, 100);
+
+ switch (id) {
+ case VDEC:
+ regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0xfffffffc);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0);
+ break;
+ case HVDEC:
+ regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0xffffffff);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0);
+ break;
+ default:
+ dev_info(hw->dev, "%s on, not found id %d\n", __func__, id);
+ break;
+ }
+ } else {
+ switch (id) {
+ case VDEC:
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0xfffffffc);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0xffffffffUL);
+ break;
+ case HVDEC:
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0xffffffff);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0);
+ usleep_range(20, 100);
+ regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0xffffffffUL);
+ break;
+ default:
+ dev_info(hw->dev, "%s off, not found id %d\n", __func__, id);
+ break;
+ }
+ }
+
+ dev_dbg(hw->dev, "%s end, id %d, is_on %d\n", __func__, id, is_on);
+}
+
+static void pm_vdec_power_domain_power_on(struct aml_vdec_hw *hw, int id)
+{
+ const struct power_manager_s *pm = hw->pm;
+ struct device *dev = pm->pd_data[id].dev;
+ struct clk_bulk_data *gate_node = NULL;
+
+ if (id == VDEC)
+ gate_node = vdec_get_clk_by_name(hw, "vdec");
+ else if (id == HVDEC)
+ gate_node = vdec_get_clk_by_name(hw, "hevcf");
+
+ if (gate_node) {
+ clk_prepare_enable(gate_node->clk);
+ if (id == VDEC) {
+ clk_set_rate(gate_node->clk, 499999992);
+ dev_dbg(hw->dev, "after set, vdec clock is %lu Hz\n",
+ clk_get_rate(gate_node->clk));
+ }
+ dev_dbg(hw->dev, "the %-15s clock on\n", gate_node->id);
+ } else {
+ dev_info(hw->dev, "clk %d, unreachable\n", id);
+ }
+
+ if (dev) {
+ pm_runtime_get_sync(dev);
+ dev_dbg(dev, "dev: %p link %p the %-15s power on\n",
+ dev, pm->pd_data[id].link, pm->pd_data[id].name);
+ }
+
+ dos_local_config(hw, 1, id);
+}
+
+static void pm_vdec_power_domain_power_off(struct aml_vdec_hw *hw, int id)
+{
+ const struct power_manager_s *pm = hw->pm;
+ struct device *dev = pm->pd_data[id].dev;
+ struct clk_bulk_data *gate_node = NULL;
+
+ dos_local_config(hw, 0, id);
+
+ if (dev) {
+ pm_runtime_put_sync(dev);
+ dev_dbg(dev, "dev: %p link %p the %-15s power off\n",
+ dev, pm->pd_data[id].link, pm->pd_data[id].name);
+ }
+
+ if (id == VDEC)
+ gate_node = vdec_get_clk_by_name(hw, "vdec");
+ else if (id == HVDEC)
+ gate_node = vdec_get_clk_by_name(hw, "hevcf");
+
+ if (gate_node) {
+ clk_disable_unprepare(gate_node->clk);
+ dev_dbg(hw->dev, "the %-15s clock off\n", gate_node->id);
+ } else {
+ dev_info(hw->dev, "clk %d, unreachable\n", id);
+ }
+}
+
+static void vdec_poweron(struct aml_vdec_hw *hw, enum vdec_type_e core)
+{
+ if (core >= VDEC_MAX)
+ return;
+
+ mutex_lock(&hw->pm_mutex);
+ if (!hw->pm->pd_data[core].dev)
+ goto out;
+
+ hw->pm->pd_data[core].ref_count++;
+ if (hw->pm->pd_data[core].ref_count > 1)
+ goto out;
+
+ hw->pm->power_on(hw, core);
+
+out:
+ mutex_unlock(&hw->pm_mutex);
+}
+
+static void vdec_poweroff(struct aml_vdec_hw *hw, enum vdec_type_e core)
+{
+ if (core >= VDEC_MAX)
+ return;
+
+ mutex_lock(&hw->pm_mutex);
+ if (hw->pm->pd_data[core].ref_count == 0)
+ goto out;
+
+ hw->pm->pd_data[core].ref_count--;
+ if (hw->pm->pd_data[core].ref_count > 0)
+ goto out;
+
+ hw->pm->power_off(hw, core);
+
+out:
+ mutex_unlock(&hw->pm_mutex);
+}
+
+int vdec_enable(struct aml_vdec_hw *hw)
+{
+ vdec_poweron(hw, VDEC);
+
+ return 0;
+}
+
+int vdec_disable(struct aml_vdec_hw *hw)
+{
+ vdec_poweroff(hw, VDEC);
+
+ return 0;
+}
+
+static const struct power_manager_s pm[] = {
+ [AML_PM_PD] = {
+ .pd_data = vdec_domain_data,
+ .init = pm_vdec_power_domain_init,
+ .release = pm_vdec_power_domain_release,
+ .power_on = pm_vdec_power_domain_power_on,
+ .power_off = pm_vdec_power_domain_power_off,
+ },
+};
+
+static irqreturn_t vdec_irq_handler(int irq, void *priv)
+{
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+ struct aml_vdec_hw *hw = dev->dec_hw;
+ irqreturn_t ret = IRQ_HANDLED;
+
+ if (hw->hw_ops.irq_handler)
+ ret = hw->hw_ops.irq_handler(irq, priv);
+
+ return ret;
+}
+
+static irqreturn_t vdec_threaded_isr_handler(int irq, void *priv)
+{
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+ struct aml_vdec_hw *hw = dev->dec_hw;
+ irqreturn_t ret = IRQ_HANDLED;
+
+ if (hw->hw_ops.irq_threaded_func && hw->curr_ctx)
+ ret = hw->hw_ops.irq_threaded_func(irq, priv);
+
+ return ret;
+}
+
+struct aml_vdec_hw *vdec_get_hw(void *priv)
+{
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+
+ return dev->dec_hw;
+}
+
+static const struct regmap_config dos_regmap_conf = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x10000,
+};
+
+static const struct regmap_config dmc_regmap_conf = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x20,
+};
+
+int dev_request_hw_resources(void *priv)
+{
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+ struct aml_vdec_hw *hw;
+ struct platform_device *pdev;
+ struct device_node *sm_np;
+ void __iomem *reg_base[MAX_REG_BUS];
+ struct resource res;
+ int i;
+ int ret = -1;
+
+ if (!dev || !dev->dec_hw)
+ return -1;
+
+ pdev = dev->plat_dev;
+ hw = dev->dec_hw;
+ hw->dev = &pdev->dev;
+
+ hw->dec_irq = platform_get_irq(pdev, IRQ_VDEC);
+ if (hw->dec_irq < 0) {
+ dev_err(&pdev->dev, "get irq failed\n");
+ return hw->dec_irq;
+ }
+ ret = devm_request_threaded_irq(&pdev->dev, hw->dec_irq, vdec_irq_handler,
+ vdec_threaded_isr_handler, IRQF_ONESHOT,
+ "vdec", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to install irq %d (%d)",
+ hw->dec_irq, ret);
+ return -1;
+ }
+
+ for (i = 0; i < MAX_REG_BUS; i++) {
+ if (of_address_to_resource(pdev->dev.of_node, i, &res)) {
+ dev_err(&pdev->dev, "of_address_to_resource %d failed\n", i);
+ return -EINVAL;
+ }
+ reg_base[i] = devm_ioremap_resource(&pdev->dev, &res);
+
+ if (IS_ERR(reg_base[i]))
+ return PTR_ERR(reg_base[i]);
+
+ if (i == DOS_BUS) {
+ hw->map[i] = devm_regmap_init_mmio(&pdev->dev, reg_base[i],
+ &dos_regmap_conf);
+ } else if (i == DMC_BUS) {
+ hw->map[i] = devm_regmap_init_mmio(&pdev->dev, reg_base[i],
+ &dmc_regmap_conf);
+ }
+
+ if (IS_ERR(hw->map[i]))
+ return PTR_ERR(hw->map[i]);
+
+ dev_dbg(&pdev->dev, "%s, res start %llx, end %llx, iomap: %p\n",
+ __func__, (unsigned long long)res.start,
+ (unsigned long long)res.end, reg_base[i]);
+ }
+ hw->canvas = meson_canvas_get(&pdev->dev);
+ if (IS_ERR_OR_NULL(hw->canvas))
+ return PTR_ERR(hw->canvas);
+
+ sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0);
+ if (IS_ERR_OR_NULL(sm_np))
+ return PTR_ERR(sm_np);
+
+ hw->sec_fw = meson_sm_get(sm_np);
+ of_node_put(sm_np);
+ if (IS_ERR(hw->sec_fw))
+ return PTR_ERR(hw->sec_fw);
+
+ hw->pm = &pm[dev->pvdec_data->power_type];
+ if (hw->pm->init) {
+ ret = hw->pm->init(hw);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "power mgr init failed!\n");
+ return ret;
+ }
+ }
+
+ ret = vdec_clock_gate_init(hw);
+ if (ret) {
+ dev_err(&pdev->dev, "clk bulk init failed!\n");
+ return ret;
+ }
+
+ ret = aml_tee_fw_preload(hw);
+ if (ret)
+ return ret;
+
+ dev_dbg(&pdev->dev, "##Amlogic hw resource init OK##\n");
+
+ return 0;
+}
+
+void dev_destroy_hw_resources(void *priv)
+{
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+ struct aml_vdec_hw *hw;
+
+ if (!dev || !dev->dec_hw)
+ return;
+
+ hw = dev->dec_hw;
+
+ if (hw->pm->release)
+ hw->pm->release(hw);
+
+ dev_dbg(hw->dev, "##Amlogic hw resource release OK##\n");
+}
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h
new file mode 100644
index 000000000000..e98cc8487417
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef _AML_VDEC_HW_H_
+#define _AML_VDEC_HW_H_
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/soc/amlogic/meson-canvas.h>
+#include <linux/firmware/meson/meson_sm.h>
+#include "reg_defines.h"
+
+#define VDEC_FIFO_ALIGN 8
+#define VLD_PADDING_SIZE 1024
+
+/**
+ * struct aml_vdec_hw_ops - codec mode specific operations for hw
+ * @irq_handler: mandatory call when the ISR triggers
+ * @irq_threaded_func: mandatory call for the threaded ISR
+ * @canvas_alloc: Alloc canvas for curr frame
+ * @canvas_free: Release canvas.
+ * @config_canvas: Config for curr frame, such as w/h, Y/UV start addr etc.
+ */
+struct aml_vdec_hw_ops {
+ irqreturn_t (*irq_handler)(int irq, void *priv);
+ irqreturn_t (*irq_threaded_func)(int irq, void *priv);
+ int (*canvas_alloc)(u8 *canvas_index);
+ void (*canvas_free)(u8 canvas_index);
+ void (*config_canvas)(u8 canvas_index,
+ ulong addr, u32 width, u32 height,
+ u32 wrap, u32 blkmode, u32 endian);
+};
+
+/**
+ * enum vdec_type_e - Type of decoder hardware.
+ */
+enum vdec_type_e {
+ VDEC = 0,
+ HVDEC,
+ VDEC_MAX,
+};
+
+/**
+ * enum vdec_irq_num - Definition of the irq.
+ */
+enum vdec_irq_num {
+ IRQ_HVDEC = 0,
+ IRQ_VDEC,
+ IRQ_2,
+ IRQ_MAX,
+};
+
+/**
+ * enum vdec_type_e - Type of decoder clock.
+ */
+enum clk_type_e {
+ CLK_DOS = 0,
+ CLK_VDEC,
+ CLK_HVDEC,
+ CLK_MAX,
+};
+
+/**
+ * enum aml_power_type_e - Type of decoder power.
+ */
+enum aml_power_type_e {
+ AML_PM_PD = 0,
+};
+
+/**
+ * enum mm_bus_e - Type of decoder hardware bus.
+ */
+enum mm_bus_e {
+ DOS_BUS = 0,
+ DMC_BUS,
+ MAX_REG_BUS
+};
+
+/**
+ * struct pm_pd_s - power domain definition
+ * @name: Power domain name.
+ * @dev: Pointer to device structure.
+ * @mutex: Pointer to device_link structure.
+ * @ref_count: Curr power domain instance ref count.
+ */
+struct pm_pd_s {
+ u8 *name;
+ struct device *dev;
+ struct device_link *link;
+ int ref_count;
+};
+
+/**
+ * struct aml_vdec_hw - decoder hardware resources definition
+ * @pdev: Pointer to struct platform_device.
+ * @dev: Pointer to struct device.
+ * @regs: Reg base for dos/dmc hardware.
+ * @pm_mutex: Mutex for pm->pd_data.
+ * @pm: Pointer to struct power_manager_s.
+ * @hw_ops: Hardware resource operation functions. See struct aml_vdec_hw_ops.
+ * @gates: Clk instance used by curr decoder context.
+ * @dec_irq: Irq registered.
+ * @curr_ctx: Pointer to curr decoder context.
+ */
+struct aml_vdec_hw {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct regmap *map[MAX_REG_BUS];
+ struct mutex pm_mutex;
+ struct meson_canvas *canvas;
+ struct meson_sm_firmware *sec_fw;
+ const struct power_manager_s *pm;
+ struct aml_vdec_hw_ops hw_ops;
+ struct clk_bulk_data gates[CLK_MAX];
+ int dec_irq;
+ void *curr_ctx;
+};
+
+/**
+ * struct power_manager_s - Power manager & opertion function
+ * @pd_data: Pointer to struct pm_pd_s
+ * @init: Power manager init.
+ * @release: Power manager release.
+ * @power_on: Power on for decoder hw.
+ * @power_off: Power off for decoder hw.
+ */
+struct power_manager_s {
+ struct pm_pd_s *pd_data;
+ int (*init)(struct aml_vdec_hw *hw);
+ void (*release)(struct aml_vdec_hw *hw);
+ void (*power_on)(struct aml_vdec_hw *hw, int id);
+ void (*power_off)(struct aml_vdec_hw *hw, int id);
+};
+
+int dev_request_hw_resources(void *priv);
+void dev_destroy_hw_resources(void *priv);
+struct aml_vdec_hw *vdec_get_hw(void *priv);
+u32 read_dos_reg(struct aml_vdec_hw *hw, u32 reg_addr);
+int vdec_enable(struct aml_vdec_hw *hw);
+int vdec_disable(struct aml_vdec_hw *hw);
+void dos_enable(struct aml_vdec_hw *hw);
+void aml_start_vdec_hw(struct aml_vdec_hw *hw);
+void aml_stop_vdec_hw(struct aml_vdec_hw *hw);
+void aml_vdec_reset_core(struct aml_vdec_hw *hw);
+
+#endif
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c
new file mode 100644
index 000000000000..60d20efb1d74
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#include "aml_vdec_platform.h"
+#include "aml_vdec_hw.h"
+#include "h264.h"
+
+static struct aml_video_fmt aml_s4_video_formats[] = {
+ {
+ .name = "H.264",
+ .fourcc = V4L2_PIX_FMT_H264_SLICE,
+ .type = AML_FMT_DEC,
+ .align = 64,
+ .is_10_bit_support = 0,
+ .codec_type = CODEC_TYPE_H264,
+ .num_planes = 1,
+ .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2,
+ AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2},
+ },
+ {
+ .name = "NV21M",
+ .fourcc = V4L2_PIX_FMT_NV21M,
+ .type = AML_FMT_FRAME,
+ .align = 64,
+ .codec_type = CODEC_TYPE_FRAME,
+ .num_planes = 2,
+ .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2,
+ AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2},
+ },
+ {
+ .name = "NV21",
+ .fourcc = V4L2_PIX_FMT_NV21,
+ .type = AML_FMT_FRAME,
+ .align = 64,
+ .codec_type = CODEC_TYPE_FRAME,
+ .num_planes = 1,
+ .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2,
+ AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2},
+ },
+ {
+ .name = "NV12M",
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .type = AML_FMT_FRAME,
+ .align = 64,
+ .codec_type = CODEC_TYPE_FRAME,
+ .num_planes = 2,
+ .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2,
+ AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2},
+
+ },
+ {
+ .name = "NV12",
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .type = AML_FMT_FRAME,
+ .align = 64,
+ .codec_type = CODEC_TYPE_FRAME,
+ .num_planes = 1,
+ .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2,
+ AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2},
+ },
+};
+
+const struct aml_codec_ops aml_S4_dec_ops[] = {
+ [CODEC_TYPE_H264] = {
+ .init = aml_h264_init,
+ .exit = aml_h264_exit,
+ .run = aml_h264_dec_run,
+ },
+};
+
+const struct aml_dev_platform_data aml_vdec_s4_pdata = {
+ .codec_ops = aml_S4_dec_ops,
+ .dec_fmt = aml_s4_video_formats,
+ .num_fmts = ARRAY_SIZE(aml_s4_video_formats),
+ .power_type = AML_PM_PD,
+ .req_hw_resource = dev_request_hw_resources,
+ .destroy_hw_resource = dev_destroy_hw_resources,
+};
+
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h
new file mode 100644
index 000000000000..a167abfe7b51
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef AML_VDEC_PLATFORM_H_
+#define AML_VDEC_PLATFORM_H_
+
+#include <linux/videodev2.h>
+#include "aml_vdec_drv.h"
+
+/**
+ * struct aml_codec_ops - codec mode specific operations
+ * @init: Used for decoder initialization.
+ * @exit: If needed, can be used to undo the .init phase.
+ * @run: Start a single decoding job. Called from atomic context.
+ * Caller should ensure that a pair of buffers is ready and the
+ * hardware is powered on and clk is enabled. Returns zero if OK,
+ * a negative value in error cases.
+ */
+struct aml_codec_ops {
+ int (*init)(void *priv);
+ void (*exit)(void *priv);
+ int (*run)(void *priv);
+};
+
+/**
+ * struct aml_dev_platform_data - compatible data for each chip.
+ * @dec_fmt: Support dec format.
+ * @codec_ops: Codec operation function.
+ * @req_hw_resource: Operation function to request the hardware resource.
+ * @destroy_hw_resource: Operation function to release the hardware resource.
+ * @power_type: Type of power that the current chip need. See aml_power_type_e.
+ */
+struct aml_dev_platform_data {
+ const struct aml_codec_ops *codec_ops;
+ const struct aml_video_fmt *dec_fmt;
+ int num_fmts;
+ int (*req_hw_resource)(void *priv);
+ void (*destroy_hw_resource)(void *priv);
+ int power_type;
+};
+
+extern const struct aml_dev_platform_data aml_vdec_s4_pdata;
+
+#endif
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
new file mode 100644
index 000000000000..9532ca53a298
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+#include <linux/tee_drv.h>
+#include <linux/firmware.h>
+
+#include "aml_vdec_tee_fw.h"
+#include "aml_vdec_drv.h"
+
+#define VIDEO_DEC_H264_MULTI 15
+
+#define CORE_VDEC_LEGENCY 0
+
+#define FIRMWARE_PATH "video_ucode.bin"
+#define ONCE_SENT_SIZE (1024 * 128)
+#define UCODE_HEADER_SIZE (1024 * 32)
+
+#define TEEC_SUCCESS 0x0
+#define TEEC_ERROR_BUSY 0xffff000d
+#define FIRMWARE_CMD_PROCESS 0
+
+#define TEE_SMC_FUNCID_LOAD_VIDEO_FW 15
+#define TEE_SMC_LOAD_VIDEO_FW \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
+ ARM_SMCCC_OWNER_TRUSTED_OS, TEE_SMC_FUNCID_LOAD_VIDEO_FW)
+
+#define PTA_LOAD_FW UUID_INIT(0x526fc4fc, 0x7ee6, 0x4a12, \
+ 0x96, 0xe3, 0x83, 0xda, 0x95, 0x65, 0xbc, 0xe8)
+
+#define TEE_PARAM_NUM 4
+
+static struct aml_tee_fw firmware[] = {
+ [CODEC_TYPE_H264] = {
+ .fw_format = VIDEO_DEC_H264_MULTI,
+ .core = CORE_VDEC_LEGENCY,
+ .is_swap = 1,
+ },
+};
+
+static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
+{
+ return (ver->impl_id == TEE_IMPL_ID_OPTEE);
+}
+
+static void prepare_tee_grgs(size_t firmware_size, struct tee_param *param0,
+ struct tee_param *param1)
+{
+ memset(param0, 0, TEE_PARAM_NUM * sizeof(*param0));
+ memset(param1, 0, TEE_PARAM_NUM * sizeof(*param1));
+
+ param0[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
+ param0[0].u.value.a = firmware_size;
+
+ param0[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT;
+
+ param0[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+ param0[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+
+ param1[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT;
+ param1[0].u.memref.size = firmware_size;
+
+ param1[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+ param1[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+ param1[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+}
+
+static int tee_pta_invoke_cmd(struct aml_vdec_hw *hw, struct tee_context *ctx,
+ uuid_t uuid, u32 cmd, void *firmware_data,
+ struct tee_param *param_init,
+ struct tee_param *param_invoke)
+{
+ int ret = 0;
+ struct tee_ioctl_open_session_arg sess_arg = { 0 };
+ struct tee_ioctl_invoke_arg inv_arg = { 0 };
+ u32 sent_size = 0;
+ u32 fw_size = 0;
+ struct tee_shm *shm = NULL;
+ void *shm_va = NULL;
+
+ fw_size = param_invoke[0].u.memref.size;
+
+ shm = tee_shm_alloc_kernel_buf(ctx, ONCE_SENT_SIZE);
+ if (IS_ERR(shm)) {
+ dev_info(hw->dev, "Failed to allocate shared memory size %d\n",
+ ONCE_SENT_SIZE);
+ ret = PTR_ERR(shm);
+ goto out;
+ }
+
+ shm_va = tee_shm_get_va(shm, 0);
+ if (IS_ERR(shm_va)) {
+ dev_info(hw->dev, "Failed to get VA for shared memory\n");
+ ret = PTR_ERR(shm_va);
+ goto free_shm;
+ }
+
+ /* Open session */
+ memcpy(sess_arg.uuid, uuid.b, TEE_IOCTL_UUID_LEN);
+ sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
+ sess_arg.num_params = TEE_PARAM_NUM;
+ ret = tee_client_open_session(ctx, &sess_arg, param_init);
+ if (ret < 0 || sess_arg.ret != TEEC_SUCCESS) {
+ dev_info(hw->dev,
+ "%s open session failed, cmd = %u, ret = %d, res = 0x%x, origin = 0x%x\n",
+ __func__, cmd, ret, sess_arg.ret, sess_arg.ret_origin);
+ ret = sess_arg.ret;
+ goto free_shm;
+ }
+
+ inv_arg.func = cmd;
+ inv_arg.session = sess_arg.session;
+ inv_arg.num_params = TEE_PARAM_NUM;
+
+ while (sent_size < fw_size) {
+ memset(shm_va, 0, ONCE_SENT_SIZE);
+ if (fw_size - sent_size > ONCE_SENT_SIZE) {
+ memcpy(shm_va, (firmware_data + sent_size),
+ ONCE_SENT_SIZE);
+ param_invoke[0].u.memref.size = ONCE_SENT_SIZE;
+ } else {
+ memcpy(shm_va, (firmware_data + sent_size),
+ fw_size - sent_size);
+ param_invoke[0].u.memref.size = (fw_size - sent_size);
+ }
+ param_invoke[0].u.memref.shm = shm;
+ ret = tee_client_invoke_func(ctx, &inv_arg, param_invoke);
+ if (ret < 0 || (inv_arg.ret != TEEC_SUCCESS && inv_arg.ret != TEEC_ERROR_BUSY)) {
+ dev_info(hw->dev,
+ "%s invoke func failed, cmd = %u, ret= %d, res = 0x%x, origin = 0x%x\n",
+ __func__, cmd, ret, inv_arg.ret,
+ inv_arg.ret_origin);
+ ret = inv_arg.ret;
+ goto close_session;
+ }
+ sent_size += param_invoke[0].u.memref.size;
+ }
+close_session:
+ tee_client_close_session(ctx, sess_arg.session);
+free_shm:
+ tee_shm_free(shm);
+out:
+ return ret;
+}
+
+int load_firmware(struct aml_vdec_hw *hw, u32 type)
+{
+ int ret = -1;
+ struct aml_tee_fw *video_fw;
+
+ if (type >= CODEC_TYPE_FRAME) {
+ dev_info(hw->dev, "codec type %d invalid\n", type);
+ return ret;
+ }
+ video_fw = &firmware[type];
+
+ meson_sm_call(hw->sec_fw, SM_LOAD_VIDEO_FW, &ret,
+ video_fw->fw_format, video_fw->core,
+ video_fw->is_swap, 0, 0);
+ if (ret < 0)
+ dev_err(hw->dev, "loading fw type %d core %d, ret %x\n",
+ video_fw->fw_format, video_fw->core, ret);
+
+ return ret;
+}
+
+static int get_firmware(const char *path, void **data, size_t *size)
+{
+ const struct firmware *fw = NULL;
+ int ret;
+ void *buf;
+
+ ret = request_firmware(&fw, FIRMWARE_PATH, NULL);
+ if (ret || fw->size <= UCODE_HEADER_SIZE)
+ return ret;
+
+ /* get rid of the first 32K bytes plaintext */
+ buf = kzalloc((fw->size - UCODE_HEADER_SIZE), GFP_KERNEL);
+ if (!buf) {
+ release_firmware(fw);
+ return -ENOMEM;
+ }
+
+ memcpy(buf, fw->data + UCODE_HEADER_SIZE, fw->size - UCODE_HEADER_SIZE);
+ release_firmware(fw);
+
+ *data = buf;
+ *size = fw->size - UCODE_HEADER_SIZE;
+
+ return 0;
+}
+
+static int pass_firmware_to_tee(struct aml_vdec_hw *hw)
+{
+ int ret;
+ struct tee_context *ctx = NULL;
+ uuid_t uuid = PTA_LOAD_FW;
+ struct tee_param param_init[TEE_PARAM_NUM];
+ struct tee_param param_invoke[TEE_PARAM_NUM];
+ void *firmware_data;
+ size_t firmware_size;
+
+ ret = get_firmware(FIRMWARE_PATH, &firmware_data, &firmware_size);
+ if (ret) {
+ dev_info(hw->dev, "Failed get firmware %s from FS\n",
+ FIRMWARE_PATH);
+ return ret;
+ }
+
+ ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
+ if (IS_ERR(ctx)) {
+ dev_info(hw->dev, "Failed to open TEE context\n");
+ ret = PTR_ERR(ctx);
+ goto free_firmware;
+ }
+
+ prepare_tee_grgs(firmware_size, param_init, param_invoke);
+
+ ret = tee_pta_invoke_cmd(hw, ctx, uuid, FIRMWARE_CMD_PROCESS,
+ firmware_data, param_init, param_invoke);
+ if (ret)
+ dev_info(hw->dev, "TEE firmware processing failed, ret = %d\n",
+ ret);
+
+ tee_client_close_context(ctx);
+free_firmware:
+ kfree(firmware_data);
+ return ret;
+}
+
+int aml_tee_fw_preload(struct aml_vdec_hw *hw)
+{
+ int ret;
+
+ ret = pass_firmware_to_tee(hw);
+ if (ret)
+ dev_err(hw->dev, "Failed to preload firmware via TEE\n");
+
+ return ret;
+}
diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h
new file mode 100644
index 000000000000..04b47e8f8654
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef AML_VDEC_TEE_FW_H_
+#define AML_VDEC_TEE_FW_H_
+
+#include "aml_vdec_hw.h"
+
+/**
+ * struct aml_tee_fw - specify the firmware format for each dec type
+ * @fw_format: Specify firmware format for current decoder.
+ * @core: Specify which hardware core is needed.
+ * @is_swap: Specify if the swap memory is needed.
+ */
+struct aml_tee_fw {
+ u32 fw_format;
+ u32 core;
+ u32 is_swap;
+};
+
+int aml_tee_fw_preload(struct aml_vdec_hw *hw);
+int load_firmware(struct aml_vdec_hw *hw, u32 type);
+
+#endif
+
diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c
new file mode 100644
index 000000000000..755f125c974b
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/h264.c
@@ -0,0 +1,2082 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#include <media/v4l2-h264.h>
+#include <linux/vmalloc.h>
+#include <linux/overflow.h>
+#include "aml_vdec.h"
+#include "aml_vdec_hw.h"
+#include "h264.h"
+
+#define INVALID_POC 0xffffffff
+
+#define H264_SLICE_HEADER_DONE 0x1
+#define H264_SLICE_DATA_DONE 0x2
+
+#define H264_MAX_COL_BUF 32
+#define H264_MAX_CANVAS_POS 26
+
+#define DECODER_TIMEOUT_MS 500
+
+#define COL_BUFFER_MARGIN 2
+#define COL_SIZE_FOR_ONE_MB 96
+
+struct vdec_h264_stateless_ctrl_ref {
+ const struct v4l2_ctrl_h264_decode_params *decode;
+ const struct v4l2_ctrl_h264_scaling_matrix *scaling;
+ const struct v4l2_ctrl_h264_sps *sps;
+ const struct v4l2_ctrl_h264_pps *pps;
+};
+
+enum SliceType {
+ P_SLICE = 0,
+ B_SLICE = 1,
+ I_SLICE = 2,
+ SP_SLICE = 3,
+ SI_SLICE = 4,
+ MAX_SLICE_TYPES = 5
+};
+
+#define I_Slice 2
+#define P_Slice 5
+#define B_Slice 6
+#define P_Slice_0 0
+#define B_Slice_1 1
+#define I_Slice_7 7
+
+/* Used by firmware */
+union param {
+ struct {
+ unsigned short data[RPM_END - RPM_BEGIN];
+ } l;
+ struct {
+ unsigned short dump[DPB_OFFSET];
+ unsigned short dpb_base[FRAME_IN_DPB << 3];
+
+ unsigned short dpb_max_buffer_frame;
+ unsigned short actual_dpb_size;
+
+ unsigned short colocated_buf_status;
+
+ unsigned short num_forward_short_term_reference_pic;
+ unsigned short num_short_term_reference_pic;
+ unsigned short num_reference_pic;
+
+ unsigned short current_dpb_index;
+ unsigned short current_decoded_frame_num;
+ unsigned short current_reference_frame_num;
+
+ unsigned short l0_size;
+ unsigned short l1_size;
+ /**
+ * [6:5] : nal_ref_idc
+ * [4:0] : nal_unit_type
+ */
+ unsigned short NAL_info_mmco;
+ /**
+ * [1:0] : 00 - top field, 01 - bottom field,
+ * 10 - frame, 11 - mbaff frame
+ */
+ unsigned short picture_structure_mmco;
+ unsigned short frame_num;
+ unsigned short pic_order_cnt_lsb;
+
+ unsigned short num_ref_idx_l0_active_minus1;
+ unsigned short num_ref_idx_l1_active_minus1;
+
+ unsigned short PrevPicOrderCntLsb;
+ unsigned short PreviousFrameNum;
+
+ /* 32 bits variables */
+ unsigned short delta_pic_order_cnt_bottom[2];
+ unsigned short delta_pic_order_cnt_0[2];
+ unsigned short delta_pic_order_cnt_1[2];
+
+ unsigned short PrevPicOrderCntMsb[2];
+ unsigned short PrevFrameNumOffset[2];
+
+ unsigned short frame_pic_order_cnt[2];
+ unsigned short top_field_pic_order_cnt[2];
+ unsigned short bottom_field_pic_order_cnt[2];
+
+ unsigned short colocated_mv_addr_start[2];
+ unsigned short colocated_mv_addr_end[2];
+ unsigned short colocated_mv_wr_addr[2];
+ } dpb;
+ struct {
+ unsigned short dump[MMCO_OFFSET];
+
+ /* array base address for offset_for_ref_frame */
+ unsigned short offset_for_ref_frame_base[128];
+
+ /**
+ * 0 - Index in DPB
+ * 1 - Picture Flag
+ * [2] : 0 - short term reference,
+ * 1 - long term reference
+ * [1] : bottom field
+ * [0] : top field
+ * 2 - Picture Number (short term or long term) low 16 bits
+ * 3 - Picture Number (short term or long term) high 16 bits
+ */
+ unsigned short reference_base[128];
+
+ /* command and parameter, until command is 3 */
+ unsigned short l0_reorder_cmd[REORDER_CMD_MAX];
+ unsigned short l1_reorder_cmd[REORDER_CMD_MAX];
+
+ /* command and parameter, until command is 0 */
+ unsigned short mmco_cmd[44];
+
+ unsigned short l0_base[40];
+ unsigned short l1_base[40];
+ } mmco;
+ struct {
+ /* from ucode lmem, do not change this struct */
+ } p;
+};
+
+struct h264_canvas {
+ u32 canvas_pos;
+ int poc;
+};
+
+struct h264_decode_buf_spec {
+ struct v4l2_h264_dpb_entry *dpb;
+ u32 canvas_pos;
+ u32 dpb_index;
+ int poc;
+ int col_buf_index;
+ u8 y_canvas_index;
+ u8 u_canvas_index;
+ u8 v_canvas_index;
+ u8 used;
+ u8 long_term_flag;
+ dma_addr_t y_dma_addr;
+ dma_addr_t c_dma_addr;
+};
+
+#define REORDERING_COMMAND_MAX_SIZE 33
+struct slice {
+ int frame_num;
+ /*modification */
+ int slice_type;
+ int num_ref_idx_l0;
+ int num_ref_idx_l1;
+ int first_mb_in_slice;
+ int ref_pic_list_reordering_flag[2];
+ int modification_of_pic_nums_idc[2][REORDERING_COMMAND_MAX_SIZE];
+ int abs_diff_pic_num_minus1[2][REORDERING_COMMAND_MAX_SIZE];
+ int long_term_pic_idx[2][REORDERING_COMMAND_MAX_SIZE];
+ unsigned char dec_ref_pic_marking_buffer_valid;
+};
+
+struct aml_h264_ctx {
+ struct aml_vdec_ctx *v4l2_ctx;
+ u8 init_flag;
+ u8 new_pic_flag;
+ u8 mc_cpu_loaded;
+ u8 param_set;
+ u8 colocated_buf_num;
+ u8 reg_iqidct_control_init_flag;
+ u32 reg_iqidct_control;
+ u32 reg_vcop_ctrl_reg;
+ u32 reg_rv_ai_mb_count;
+ u32 vld_dec_control;
+ u32 save_avscratch_f;
+ u32 seq_info;
+ u32 decode_pic_count;
+ union param dpb_param;
+ u32 dec_status;
+ struct slice mslice;
+ struct h264_decode_buf_spec curr_spec;
+ struct h264_decode_buf_spec ref_list0[V4L2_H264_NUM_DPB_ENTRIES + 1];
+ struct h264_decode_buf_spec ref_list1[V4L2_H264_NUM_DPB_ENTRIES + 1];
+ struct h264_decode_buf_spec ref_list0_unreordered[V4L2_H264_NUM_DPB_ENTRIES + 1];
+ struct h264_decode_buf_spec ref_list1_unreordered[V4L2_H264_NUM_DPB_ENTRIES + 1];
+ u8 list_size[2];
+ u32 canvas_pos_map;
+ struct h264_canvas ref_canvas[V4L2_H264_NUM_DPB_ENTRIES + 1];
+ dma_addr_t lmem_phy_addr;
+ void *lmem_addr;
+ dma_addr_t mc_cpu_paddr;
+ void *mc_cpu_vaddr;
+ dma_addr_t cma_alloc_addr;
+ void *cma_alloc_vaddr;
+ dma_addr_t collated_cma_addr;
+ dma_addr_t collated_cma_addr_end;
+ void *collated_cma_vaddr;
+ dma_addr_t workspace_offset;
+ void *workspace_vaddr;
+ u32 col_buf_alloc_size;
+ u32 one_col_buf_size;
+ u32 colocated_buf_map;
+ int colocated_buf_poc[H264_MAX_COL_BUF];
+
+ u32 frame_width;
+ u32 frame_height;
+ u32 mb_width;
+ u32 mb_height;
+ u32 mb_total;
+ u32 max_num_ref_frames;
+
+ struct vdec_h264_stateless_ctrl_ref ctrl_ref;
+};
+
+static inline int get_flag(u32 flag, u32 mask)
+{
+ return (flag & mask) ? 1 : 0;
+}
+
+static inline void write_lmem(unsigned short *base, u32 offset, u32 value)
+{
+ base[offset] = value;
+}
+
+static inline uint32_t spec2canvas(struct h264_decode_buf_spec *buf_spec)
+{
+ return (buf_spec->v_canvas_index << 16) |
+ (buf_spec->u_canvas_index << 8) |
+ (buf_spec->y_canvas_index << 0);
+}
+
+static struct h264_decode_buf_spec *find_spec_by_dpb_index(struct aml_h264_ctx
+ *h264_ctx, int index, int list)
+{
+ int i;
+ int size;
+ struct h264_decode_buf_spec *ref_list;
+
+ size = h264_ctx->list_size[list];
+ if (list == 0)
+ ref_list = &h264_ctx->ref_list0[0];
+ else
+ ref_list = &h264_ctx->ref_list1[0];
+
+ for (i = 0; i < size; i++) {
+ if (index == ref_list[i].dpb_index)
+ return &ref_list[i];
+ }
+
+ return NULL;
+}
+
+static int h264_prepare_input(struct aml_vdec_ctx *ctx)
+{
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev);
+ struct vb2_v4l2_buffer *src;
+ struct vb2_buffer *vb;
+ dma_addr_t src_dma;
+ u32 payload_size;
+ int dummy;
+
+ src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ if (!src) {
+ dev_info(hw->dev, "no input buffer available!\n");
+ return -1;
+ }
+ vb = &src->vb2_buf;
+ payload_size = vb2_get_plane_payload(vb, 0);
+ src_dma = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0);
+ /* reset VLD fifo for all vdec */
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0,
+ (1 << 5) | (1 << 4) | (1 << 3));
+ regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0);
+ regmap_write(hw->map[DOS_BUS], POWER_CTL_VLD, 1 << 4);
+
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_START_PTR, src_dma);
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_END_PTR,
+ (src_dma + payload_size));
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CURR_PTR,
+ round_down(src_dma, VDEC_FIFO_ALIGN));
+
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 1);
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0);
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 2);
+
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_RP,
+ round_down(src_dma, VDEC_FIFO_ALIGN));
+ dummy = payload_size + VLD_PADDING_SIZE;
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_WP,
+ round_down((src_dma + dummy), VDEC_FIFO_ALIGN));
+
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 3);
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 2);
+
+ regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL,
+ (0x11 << 16) | (1 << 10) | (7 << 3));
+
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_1, 0x0);
+ regmap_write(hw->map[DOS_BUS], H264_DECODE_INFO, (1 << 13));
+ regmap_write(hw->map[DOS_BUS], H264_DECODE_SIZE, payload_size);
+ regmap_write(hw->map[DOS_BUS], VIFF_BIT_CNT, payload_size * 8);
+
+ return 0;
+}
+
+static void config_sps_params(struct aml_h264_ctx *h264_ctx,
+ unsigned short *sps_base,
+ const struct v4l2_ctrl_h264_sps *sps)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev);
+ u32 cfg_tmp = 0;
+ u32 frame_size;
+ u32 offset = 0;
+ unsigned short data_tmp[0x100];
+ int i, ii;
+
+ memset(sps_base, 0, 0x100);
+
+ h264_ctx->frame_width = (sps->pic_width_in_mbs_minus1 + 1) << 4;
+ h264_ctx->frame_height = (sps->pic_height_in_map_units_minus1 + 1) << 4;
+
+ data_tmp[offset] = PARAM_BASE_VAL;
+ offset += 2;
+
+ data_tmp[offset++] = GET_SPS_PROFILE_IDC(sps->profile_idc);
+
+ data_tmp[offset++] = GET_SPS_SEQ_PARAM_SET_ID(sps->seq_parameter_set_id) |
+ GET_SPS_LEVEL_IDC(sps->level_idc);
+
+ if (sps->profile_idc >= 100) {
+ data_tmp[offset++] = GET_SPS_CHROMA_FORMAT_IDC(sps->chroma_format_idc);
+
+ data_tmp[offset++] = ((sps->chroma_format_idc ^ 1) << 1);
+ }
+
+ data_tmp[offset++] = GET_SPS_LOG2_MAX_FRAME_NUM(sps->log2_max_frame_num_minus4);
+ data_tmp[offset++] = GET_SPS_PIC_ORDER_TYPE(sps->pic_order_cnt_type);
+
+ if (sps->pic_order_cnt_type == 0) {
+ data_tmp[offset++] =
+ GET_SPS_PIC_ORDER_CNT_LSB(sps->log2_max_pic_order_cnt_lsb_minus4);
+ } else if (sps->pic_order_cnt_type == 1) {
+ data_tmp[offset++] =
+ get_flag(sps->flags,
+ V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO);
+ data_tmp[offset++] =
+ GET_SPS_OFFSET_FOR_NONREF_PIC_LOW(sps->offset_for_non_ref_pic);
+ data_tmp[offset++] =
+ GET_SPS_OFFSET_FOR_NONREF_PIC_HIGH(sps->offset_for_non_ref_pic);
+ data_tmp[offset++] =
+ GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_LOW(sps->offset_for_top_to_bottom_field);
+ data_tmp[offset++] =
+ GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_HIGH(sps->offset_for_top_to_bottom_field);
+ data_tmp[offset++] = sps->num_ref_frames_in_pic_order_cnt_cycle;
+ }
+
+ data_tmp[offset++] = GET_SPS_NUM_REF_FRAMES(sps->max_num_ref_frames) |
+ GET_SPS_GAPS_ALLOWED_FLAG(get_flag(sps->flags,
+ V4L2_H264_SPS_FLAG_GAPS_IN_FRAME_NUM_VALUE_ALLOWED));
+
+ data_tmp[offset++] = GET_SPS_PIC_WIDTH_IN_MBS(sps->pic_width_in_mbs_minus1);
+
+ data_tmp[offset++] = GET_SPS_PIC_HEIGHT_IN_MBS(sps->pic_height_in_map_units_minus1);
+ data_tmp[offset++] =
+ GET_SPS_DIRECT_8X8_FLAGS
+ (get_flag(sps->flags,
+ V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)) |
+ GET_SPS_MB_ADAPTIVE_FRAME_FIELD_FLAGS
+ (get_flag(sps->flags,
+ V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD)) |
+ GET_SPS_FRAME_MBS_ONLY_FLAGS(get_flag(sps->flags,
+ V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY));
+
+ for (i = 0; i < 0x100; i += 4) {
+ for (ii = 0; ii < 4; ii++)
+ sps_base[i + 3 - ii] = data_tmp[i + ii];
+ }
+
+ frame_size = (sps->pic_width_in_mbs_minus1 + 1) * (sps->pic_height_in_map_units_minus1 + 1);
+ cfg_tmp = (get_flag(sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 31) |
+ (sps->max_num_ref_frames << 24) | (frame_size << 8) |
+ (sps->pic_width_in_mbs_minus1 + 1);
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_1, cfg_tmp);
+ h264_ctx->seq_info = cfg_tmp;
+
+ cfg_tmp = 0;
+ cfg_tmp = (get_flag(sps->flags, V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE) << 15) |
+ (sps->chroma_format_idc);
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_2, cfg_tmp);
+
+ cfg_tmp = 0;
+ cfg_tmp = (sps->max_num_ref_frames << 8) | (sps->level_idc);
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_B, cfg_tmp);
+
+ cfg_tmp = ((sps->level_idc & 0xff) << 7) |
+ (get_flag(sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 2);
+ regmap_write(hw->map[DOS_BUS], NAL_SEARCH_CTL,
+ read_dos_reg(hw, NAL_SEARCH_CTL) | cfg_tmp);
+
+ h264_ctx->mb_width = (sps->pic_width_in_mbs_minus1 + 4) & 0xfffffffc;
+ h264_ctx->mb_height = (sps->pic_height_in_map_units_minus1 + 4) & 0xfffffffc;
+
+ h264_ctx->mb_total = h264_ctx->mb_width * h264_ctx->mb_height;
+ h264_ctx->max_num_ref_frames = clamp(sps->max_num_ref_frames,
+ 0, V4L2_H264_NUM_DPB_ENTRIES);
+}
+
+static void config_pps_params(struct aml_h264_ctx *h264_ctx,
+ unsigned short *pps_base,
+ const struct v4l2_ctrl_h264_pps *pps)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev);
+ u32 offset = 0;
+ unsigned short data_tmp[0x100];
+ u32 max_reference_size = V4L2_H264_NUM_DPB_ENTRIES;
+ u32 max_list_size;
+ int i, ii;
+
+ memset(pps_base, 0, 0x100);
+
+ data_tmp[offset++] = PARAM_BASE_VAL;
+
+ data_tmp[offset++] =
+ GET_PPS_PIC_PARAM_SET_ID(pps->pic_parameter_set_id) |
+ GET_PPS_SEQ_PARAM_SET_ID(pps->seq_parameter_set_id) |
+ GET_PPS_ENTROPY_CODING_MODE_FLAG
+ (get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE)) |
+ GET_PPS_PIC_ORDER_PRESENT_FLAG
+ (get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT));
+
+ data_tmp[offset++] =
+ GET_PPS_WEIGHTED_BIPRED_IDC(pps->weighted_bipred_idc) |
+ GET_PPS_WEIGHTED_PRED_FLAG(get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_WEIGHTED_PRED)) |
+ GET_PPS_NUM_IDX_REF_L1_MINUS1(pps->num_ref_idx_l1_default_active_minus1) |
+ GET_PPS_NUM_IDX_REF_L0_MINUS1(pps->num_ref_idx_l0_default_active_minus1);
+
+ data_tmp[offset++] = GET_PPS_INIT_QS_MINUS26(pps->pic_init_qs_minus26) |
+ GET_PPS_INIT_QP_MINUS26(pps->pic_init_qp_minus26);
+
+ data_tmp[offset] =
+ GET_PPS_CHROMA_QP_INDEX_OFFSET(pps->chroma_qp_index_offset) |
+ GET_PPS_DEBLOCK_FILTER_CTRL_PRESENT_FLAG
+ (get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT)) |
+ GET_PPS_CONSTRAIN_INTRA_PRED_FLAG
+ (get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED)) |
+ GET_PPS_REDUNDANT_PIC_CNT_PRESENT_FLAG
+ (get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT));
+ if (get_flag(pps->flags, V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE |
+ V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT))
+ data_tmp[offset] |= (1 << 11);
+ offset++;
+
+ data_tmp[offset++] =
+ GET_PPS_SCALING_MATRIX_PRESENT_FLAG(get_flag
+ (pps->flags,
+ V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) |
+ GET_PPS_TRANSFORM_8X8_FLAG(get_flag(pps->flags,
+ V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE));
+
+ data_tmp[offset++] =
+ GET_PPS_GET_SECOND_CHROMA_QP_OFFSET(pps->second_chroma_qp_index_offset);
+
+ max_list_size = (pps->num_ref_idx_l1_default_active_minus1 + 1) +
+ (pps->num_ref_idx_l0_default_active_minus1 + 1);
+
+ h264_ctx->max_num_ref_frames = max_list_size > h264_ctx->max_num_ref_frames ?
+ max_list_size : h264_ctx->max_num_ref_frames;
+
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_0,
+ ((h264_ctx->max_num_ref_frames + 1) << 24) |
+ (max_reference_size << 16) | (max_reference_size << 8));
+
+ for (i = 0; i < 0x100; i += 4) {
+ for (ii = 0; ii < 4; ii++)
+ pps_base[i + 3 - ii] = data_tmp[i + ii];
+ }
+}
+
+static void h264_config_params(struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv;
+ unsigned short *p_sps_base, *p_pps_base;
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+ const struct v4l2_ctrl_h264_sps *sps = ctrls->sps;
+ const struct v4l2_ctrl_h264_pps *pps = ctrls->pps;
+
+ p_sps_base = (unsigned short *)(h264_ctx->workspace_vaddr +
+ MEM_SPS_BASE + sps->seq_parameter_set_id * 0x400);
+ p_pps_base = (unsigned short *)(h264_ctx->workspace_vaddr +
+ MEM_PPS_BASE + pps->pic_parameter_set_id * 0x200);
+
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s sps id %d, pps id %d\n",
+ __func__, sps->seq_parameter_set_id, pps->pic_parameter_set_id);
+
+ config_sps_params(h264_ctx, p_sps_base, sps);
+ config_pps_params(h264_ctx, p_pps_base, pps);
+}
+
+static void config_decode_canvas(struct aml_vdec_hw *hw,
+ struct h264_decode_buf_spec *buf_spec,
+ u32 mb_width, u32 mb_height)
+{
+ int canvas_alloc_result = 0;
+ int blkmode = 0x0;
+
+ canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->y_canvas_index);
+ canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->u_canvas_index);
+ buf_spec->v_canvas_index = buf_spec->u_canvas_index;
+
+ if (!canvas_alloc_result) {
+ /* config y canvas */
+ meson_canvas_config(hw->canvas,
+ buf_spec->y_canvas_index, buf_spec->y_dma_addr,
+ mb_width << 4, mb_height << 4,
+ MESON_CANVAS_WRAP_NONE, MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+ regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_CANVAS_BLK32,
+ (1 << 11) | /* canvas_blk32_wr */
+ (blkmode << 10) | /* canvas_blk32 */
+ (1 << 8) | /* canvas_index_wr */
+ (buf_spec->y_canvas_index << 0) /* canvas index */
+ );
+
+ /* config uv canvas */
+ meson_canvas_config(hw->canvas,
+ buf_spec->u_canvas_index, buf_spec->c_dma_addr,
+ mb_width << 4, mb_height << 3,
+ MESON_CANVAS_WRAP_NONE, MESON_CANVAS_BLKMODE_LINEAR,
+ MESON_CANVAS_ENDIAN_SWAP64);
+ regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_CANVAS_BLK32,
+ (1 << 11) | /* canvas_blk32_wr */
+ (blkmode << 10) | /* canvas_blk32 */
+ (1 << 8) | /* canvas_index_wr */
+ (buf_spec->u_canvas_index << 0) /* canvas index */
+ );
+
+ regmap_write(hw->map[DOS_BUS], ANC0_CANVAS_ADDR + (buf_spec->canvas_pos << 2),
+ spec2canvas(buf_spec));
+ }
+}
+
+static int allocate_colocate_buf(struct aml_h264_ctx *h264_ctx, int poc)
+{
+ int i;
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+
+ for (i = 0; i < h264_ctx->colocated_buf_num; i++) {
+ if (((h264_ctx->colocated_buf_map >> i) & 0x1) == 0) {
+ h264_ctx->colocated_buf_map |= (1 << i);
+ break;
+ }
+ }
+
+ if (i == h264_ctx->colocated_buf_num)
+ return -1;
+
+ h264_ctx->colocated_buf_poc[i] = poc;
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s colocated_buf_index %d poc %d\n",
+ __func__, i, h264_ctx->colocated_buf_poc[i]);
+
+ return i;
+}
+
+static void release_colocate_buf(struct aml_h264_ctx *h264_ctx, int index)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+
+ if (index >= 0) {
+ if (index >= h264_ctx->colocated_buf_num) {
+ dev_dbg
+ (&ctx->dev->plat_dev->dev,
+ "%s error, index %d is bigger than buf count %d\n",
+ __func__, index, h264_ctx->max_num_ref_frames);
+ } else {
+ if (h264_ctx->colocated_buf_poc[index] != INVALID_POC &&
+ ((h264_ctx->colocated_buf_map >> index) & 0x1) == 0x1) {
+ h264_ctx->colocated_buf_map &= (~(1 << index));
+ dev_dbg
+ (&ctx->dev->plat_dev->dev,
+ "%s colocated_buf_index %d released poc %d\n",
+ __func__, index,
+ h264_ctx->colocated_buf_poc[index]);
+ }
+ h264_ctx->colocated_buf_poc[index] = INVALID_POC;
+ }
+ }
+}
+
+static int get_col_buf_index_by_poc(struct aml_h264_ctx *h264_ctx, int poc)
+{
+ int idx;
+
+ for (idx = 0; idx < h264_ctx->colocated_buf_num; idx++) {
+ if (h264_ctx->colocated_buf_poc[idx] == poc)
+ break;
+ }
+
+ if (idx == h264_ctx->colocated_buf_num)
+ idx = -1;
+
+ return idx;
+}
+
+static int alloc_colocate_cma(struct aml_h264_ctx *h264_ctx,
+ struct aml_vdec_ctx *ctx)
+{
+ int alloc_size = 0;
+ int i;
+ struct aml_vdec_hw *hw;
+ gfp_t gfp = GFP_KERNEL | GFP_DMA32;
+
+ if (h264_ctx->collated_cma_vaddr)
+ return 0;
+
+ hw = vdec_get_hw(ctx->dev);
+ if (!hw)
+ return -1;
+
+ /* 96 :col buf size for each mb */
+ h264_ctx->one_col_buf_size = h264_ctx->mb_total * 96;
+
+ if (check_mul_overflow(h264_ctx->one_col_buf_size,
+ (h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN),
+ &alloc_size)) {
+ dev_err(&ctx->dev->plat_dev->dev,
+ "param for col cma is invalid!\n");
+ return -1;
+ }
+ alloc_size = PAGE_ALIGN(alloc_size);
+
+ /* The dma addr here needs be limited to 32 bit
+ * since it will be given to a 32 bit registers
+ */
+ h264_ctx->collated_cma_vaddr = dma_alloc_coherent(hw->dev, alloc_size,
+ &h264_ctx->collated_cma_addr, gfp);
+ if (!h264_ctx->collated_cma_vaddr)
+ return -ENOMEM;
+
+ dev_dbg
+ (&ctx->dev->plat_dev->dev,
+ "collated_cma_addr = %pad, one_col_buf_size = %x alloc_size = %x\n",
+ &h264_ctx->collated_cma_addr, h264_ctx->one_col_buf_size,
+ alloc_size);
+ h264_ctx->collated_cma_addr_end =
+ h264_ctx->collated_cma_addr + alloc_size;
+ memset(h264_ctx->collated_cma_vaddr, 0, alloc_size);
+ h264_ctx->col_buf_alloc_size = alloc_size;
+ h264_ctx->colocated_buf_map = 0;
+ h264_ctx->colocated_buf_num = h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN;
+
+ for (i = 0; i < H264_MAX_COL_BUF; i++)
+ h264_ctx->colocated_buf_poc[i] = INVALID_POC;
+
+ return 0;
+}
+
+static void config_p_reflist(struct aml_h264_ctx *h264_ctx,
+ struct v4l2_h264_reference *v4l2_p0_reflist,
+ u32 list_size)
+{
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+ struct v4l2_ctrl_h264_decode_params *decode =
+ (struct v4l2_ctrl_h264_decode_params *)ctrls->decode;
+ struct v4l2_h264_dpb_entry *dpb = decode->dpb;
+ u8 index;
+ int i;
+
+ for (i = 0; i < list_size; i++) {
+ index = v4l2_p0_reflist[i].index;
+ h264_ctx->ref_list0[i].used = 1;
+ h264_ctx->ref_list0[i].dpb = &dpb[index];
+ h264_ctx->ref_list0[i].poc = dpb[index].top_field_order_cnt;
+ h264_ctx->ref_list0[i].long_term_flag =
+ dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false;
+ h264_ctx->ref_list0[i].dpb_index = index;
+ }
+ h264_ctx->list_size[0] = list_size;
+}
+
+static void config_b_reflist(struct aml_h264_ctx *h264_ctx,
+ struct v4l2_h264_reference *v4l2_b0_reflist,
+ struct v4l2_h264_reference *v4l2_b1_reflist,
+ u32 list_size)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+ struct v4l2_ctrl_h264_decode_params *decode =
+ (struct v4l2_ctrl_h264_decode_params *)ctrls->decode;
+ struct v4l2_h264_dpb_entry *dpb = decode->dpb;
+ u8 index;
+ int i, j;
+
+ h264_ctx->list_size[0] = list_size;
+ for (i = 0; i < list_size; i++) {
+ index = v4l2_b0_reflist[i].index;
+ h264_ctx->ref_list0[i].used = 1;
+ h264_ctx->ref_list0[i].dpb = &dpb[index];
+ h264_ctx->ref_list0[i].poc = dpb[index].top_field_order_cnt;
+ h264_ctx->ref_list0[i].long_term_flag =
+ dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false;
+ h264_ctx->ref_list0[i].col_buf_index =
+ get_col_buf_index_by_poc(h264_ctx, dpb[index].top_field_order_cnt);
+ h264_ctx->ref_list0[i].dpb_index = index;
+ }
+
+ h264_ctx->list_size[1] = list_size;
+ for (j = 0; j < list_size; j++) {
+ index = v4l2_b1_reflist[j].index;
+ h264_ctx->ref_list1[j].used = 1;
+ h264_ctx->ref_list1[j].dpb = &dpb[index];
+ h264_ctx->ref_list1[j].poc = dpb[index].top_field_order_cnt;
+ h264_ctx->ref_list1[j].long_term_flag =
+ dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false;
+ h264_ctx->ref_list1[j].col_buf_index =
+ get_col_buf_index_by_poc(h264_ctx, dpb[index].top_field_order_cnt);
+ h264_ctx->ref_list1[j].dpb_index = index;
+ }
+
+ if ((h264_ctx->list_size[1] + h264_ctx->list_size[0]) < list_size)
+ dev_info(&ctx->dev->plat_dev->dev, "ref list incorrect list0 %d list0 %d list_size%d\n",
+ h264_ctx->list_size[0], h264_ctx->list_size[1], list_size);
+}
+
+static int poc_is_in_dpb(int poc, const struct v4l2_h264_dpb_entry *dpb)
+{
+ int i;
+ int ret = 0;
+
+ for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
+ if (poc == dpb[i].top_field_order_cnt) {
+ ret = 1;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int get_ref_list_size(struct aml_h264_ctx *h264_ctx, int cur_list)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ unsigned short override_flag = h264_ctx->dpb_param.l.data[REF_IDC_OVERRIDE_FLAG];
+ int num_ref_idx_lx_active_minus1;
+
+ if (cur_list == 0) {
+ num_ref_idx_lx_active_minus1 =
+ h264_ctx->ctrl_ref.pps->num_ref_idx_l0_default_active_minus1;
+ if (override_flag)
+ num_ref_idx_lx_active_minus1 =
+ h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1;
+ } else {
+ num_ref_idx_lx_active_minus1 =
+ h264_ctx->ctrl_ref.pps->num_ref_idx_l1_default_active_minus1;
+ }
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s get list %d size %d\n",
+ __func__, cur_list, num_ref_idx_lx_active_minus1 + 1);
+
+ return num_ref_idx_lx_active_minus1 + 1;
+}
+
+static int get_refidx_by_picnum(struct aml_h264_ctx *h264_ctx, int pic_num,
+ int curr_list)
+{
+ int i;
+ struct h264_decode_buf_spec *ref_list;
+
+ if (curr_list == 0)
+ ref_list = &h264_ctx->ref_list0[0];
+ else
+ ref_list = &h264_ctx->ref_list1[0];
+
+ for (i = 0; ref_list[i].dpb; i++) {
+ if (pic_num == ref_list[i].dpb->pic_num)
+ return i;
+ }
+
+ return -1;
+}
+
+static struct h264_decode_buf_spec *get_st_refpic_by_num(struct aml_h264_ctx *h264_ctx,
+ int pic_num, int curr_list)
+{
+ int i;
+ struct h264_decode_buf_spec *ref_list;
+
+ if (curr_list == 0)
+ ref_list = &h264_ctx->ref_list0_unreordered[0];
+ else
+ ref_list = &h264_ctx->ref_list1_unreordered[0];
+
+ for (i = 0; ref_list[i].dpb; i++) {
+ if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 0)
+ return &ref_list[i];
+ }
+
+ return NULL;
+}
+
+static struct h264_decode_buf_spec *get_lt_refpic_by_num(struct aml_h264_ctx *h264_ctx,
+ int pic_num, int curr_list)
+{
+ int i;
+ struct h264_decode_buf_spec *ref_list;
+
+ if (curr_list == 0)
+ ref_list = &h264_ctx->ref_list0_unreordered[0];
+ else
+ ref_list = &h264_ctx->ref_list1_unreordered[0];
+
+ for (i = 0; ref_list[i].dpb; i++) {
+ if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 1)
+ return &ref_list[i];
+ }
+
+ return NULL;
+}
+
+static void reorder_short_term(struct slice *curr_slice, int cur_list,
+ int pic_num_lx, int *ref_idx_lx)
+{
+ struct aml_h264_ctx *h264_ctx =
+ container_of(curr_slice, struct aml_h264_ctx, mslice);
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ int c_idx, n_idx;
+ int num_ref_idx_lx_active;
+ struct h264_decode_buf_spec *pic_lx = NULL;
+ struct h264_decode_buf_spec *ref_list_reordered;
+
+ if (cur_list == 0)
+ ref_list_reordered = &h264_ctx->ref_list0[0];
+ else
+ ref_list_reordered = &h264_ctx->ref_list1[0];
+
+ num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list);
+ if (num_ref_idx_lx_active > (V4L2_H264_NUM_DPB_ENTRIES + 1)) {
+ dev_dbg(&ctx->dev->plat_dev->dev, "incorrect value st num_ref_idx_lx_active %d\n",
+ num_ref_idx_lx_active);
+ return;
+ }
+
+ /* find short-term ref frame with pic_num is pic_num_lx */
+ pic_lx = get_st_refpic_by_num(h264_ctx, pic_num_lx, cur_list);
+ if (!pic_lx) {
+ dev_dbg(&ctx->dev->plat_dev->dev, "cannot find st pic_lx for %d\n", pic_num_lx);
+ return;
+ }
+
+ if (*ref_idx_lx == get_refidx_by_picnum(h264_ctx, pic_num_lx, cur_list)) {
+ dev_dbg(&ctx->dev->plat_dev->dev, "no need to move pic lx %d\n", *ref_idx_lx);
+ *ref_idx_lx = *ref_idx_lx + 1;
+ return;
+ }
+
+ for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--)
+ memcpy(&ref_list_reordered[c_idx], &ref_list_reordered[c_idx - 1],
+ sizeof(struct h264_decode_buf_spec));
+
+ memcpy(&ref_list_reordered[*ref_idx_lx], pic_lx, sizeof(struct h264_decode_buf_spec));
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__,
+ *ref_idx_lx, pic_lx, ref_list_reordered[*ref_idx_lx].dpb->pic_num);
+ *ref_idx_lx = *ref_idx_lx + 1;
+
+ n_idx = *ref_idx_lx;
+ for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) {
+ if (ref_list_reordered[c_idx].long_term_flag || !ref_list_reordered[c_idx].dpb ||
+ ref_list_reordered[c_idx].dpb->pic_num != pic_num_lx)
+ memcpy(&ref_list_reordered[n_idx++], &ref_list_reordered[c_idx],
+ sizeof(struct h264_decode_buf_spec));
+ }
+
+ h264_ctx->list_size[cur_list] = num_ref_idx_lx_active;
+}
+
+static void reorder_long_term(struct slice *curr_slice, int cur_list,
+ int lt_pic_num, int *ref_idx_lx)
+{
+ struct aml_h264_ctx *h264_ctx =
+ container_of(curr_slice, struct aml_h264_ctx, mslice);
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ int num_ref_idx_lx_active;
+ int c_idx, n_idx;
+ struct h264_decode_buf_spec *ref_list;
+ struct h264_decode_buf_spec *pic_lt = NULL;
+
+ if (cur_list == 0)
+ ref_list = &h264_ctx->ref_list0[0];
+ else
+ ref_list = &h264_ctx->ref_list1[0];
+
+ num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list);
+
+ /* find long-term ref frame with pic_num is lt_pic_num */
+ pic_lt = get_lt_refpic_by_num(h264_ctx, lt_pic_num, cur_list);
+ if (!pic_lt) {
+ dev_dbg(&ctx->dev->plat_dev->dev, "cannot find lt pic_lx for %d\n", lt_pic_num);
+ return;
+ }
+
+ if (*ref_idx_lx == get_refidx_by_picnum(h264_ctx, lt_pic_num, cur_list)) {
+ dev_dbg(&ctx->dev->plat_dev->dev, "no need to move pic lx %d\n", *ref_idx_lx);
+ *ref_idx_lx = *ref_idx_lx + 1;
+ return;
+ }
+
+ for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--)
+ memcpy(&ref_list[c_idx], &ref_list[c_idx - 1], sizeof(struct h264_decode_buf_spec));
+
+ memcpy(&ref_list[*ref_idx_lx], pic_lt, sizeof(struct h264_decode_buf_spec));
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__,
+ *ref_idx_lx, pic_lt, ref_list[*ref_idx_lx].dpb->pic_num);
+ *ref_idx_lx = *ref_idx_lx + 1;
+
+ n_idx = *ref_idx_lx;
+ /* Pointer dpb is NULL means this is a dummy frame store */
+ for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) {
+ if (!ref_list[c_idx].long_term_flag || !ref_list[c_idx].dpb ||
+ ref_list[c_idx].dpb->pic_num != lt_pic_num)
+ memcpy(&ref_list[n_idx++], &ref_list[c_idx],
+ sizeof(struct h264_decode_buf_spec));
+ }
+
+ h264_ctx->list_size[cur_list] = num_ref_idx_lx_active;
+}
+
+static void get_modification_cmd(unsigned short *reorder_cmd,
+ struct slice *curr_slice, int list)
+{
+ int i, j, val;
+
+ val = curr_slice->ref_pic_list_reordering_flag[list];
+ if (val) {
+ i = 0;
+ j = 0;
+ do {
+ curr_slice->modification_of_pic_nums_idc[list][i] =
+ reorder_cmd[j++];
+ if (j >= REORDER_CMD_MAX) {
+ curr_slice->modification_of_pic_nums_idc[list][i] = 0;
+ break;
+ }
+
+ val = curr_slice->modification_of_pic_nums_idc[list][i];
+ if (val == 0 || val == 1)
+ curr_slice->abs_diff_pic_num_minus1[list][i] = reorder_cmd[j++];
+ else if (val == 2)
+ curr_slice->long_term_pic_idx[list][i] = reorder_cmd[j++];
+
+ i++;
+
+ if (i >= REORDERING_COMMAND_MAX_SIZE) {
+ curr_slice->ref_pic_list_reordering_flag[list] = 0;
+ break;
+ };
+ if (j > REORDER_CMD_MAX) {
+ curr_slice->ref_pic_list_reordering_flag[list] = 0;
+ break;
+ };
+ } while (val != 3);
+ }
+}
+
+static void reorder_pics(struct aml_h264_ctx *h264_ctx,
+ struct slice *curr_slice, int cur_list)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ int *modification_of_pic_nums_idc =
+ curr_slice->modification_of_pic_nums_idc[cur_list];
+ int *abs_diff_pic_num_minus1 =
+ curr_slice->abs_diff_pic_num_minus1[cur_list];
+ int *long_term_pic_idx = curr_slice->long_term_pic_idx[cur_list];
+ int pic_num_lx_nowarp, pic_num_lx_pred, pic_num_lx;
+ int curr_pic_num = curr_slice->frame_num;
+ int max_pic_num =
+ 1 << (4 + h264_ctx->ctrl_ref.sps->log2_max_frame_num_minus4);
+ int ref_idx_lx = 0;
+ int nowarp_tmp = 0;
+ int i;
+
+ pic_num_lx_pred = curr_pic_num;
+ for (i = 0; i < REORDERING_COMMAND_MAX_SIZE && modification_of_pic_nums_idc[i] != 3; i++) {
+ if (modification_of_pic_nums_idc[i] > 3) {
+ dev_info(&ctx->dev->plat_dev->dev, "error, Invalid modification_of_pic_nums_idc command\n");
+ break;
+ }
+
+ if (modification_of_pic_nums_idc[i] < 2) {
+ if (modification_of_pic_nums_idc[i] == 0) {
+ nowarp_tmp = pic_num_lx_pred - (abs_diff_pic_num_minus1[i] + 1);
+ pic_num_lx_nowarp = nowarp_tmp + (nowarp_tmp < 0 ? max_pic_num : 0);
+ } else if (modification_of_pic_nums_idc[i] == 1) {
+ nowarp_tmp = pic_num_lx_pred + (abs_diff_pic_num_minus1[i] + 1);
+ pic_num_lx_nowarp = nowarp_tmp -
+ (nowarp_tmp > max_pic_num ? max_pic_num : 0);
+ }
+ pic_num_lx_pred = pic_num_lx_nowarp;
+ if (pic_num_lx_nowarp > curr_pic_num)
+ pic_num_lx = pic_num_lx_nowarp - max_pic_num;
+ else
+ pic_num_lx = pic_num_lx_nowarp;
+
+ reorder_short_term(curr_slice, cur_list, pic_num_lx, &ref_idx_lx);
+ } else {
+ reorder_long_term(curr_slice, cur_list, long_term_pic_idx[i], &ref_idx_lx);
+ }
+ }
+}
+
+static void copy_ref_list(struct aml_h264_ctx *h264_ctx, int curr_list)
+{
+ if (curr_list == 0)
+ memcpy(h264_ctx->ref_list0_unreordered, h264_ctx->ref_list0,
+ sizeof(h264_ctx->ref_list0));
+ else
+ memcpy(h264_ctx->ref_list1_unreordered, h264_ctx->ref_list1,
+ sizeof(h264_ctx->ref_list1));
+}
+
+static void h264_reorder_reflists(struct aml_h264_ctx *h264_ctx)
+{
+ unsigned short *reorder_cmd;
+ struct slice *curr_slice = &h264_ctx->mslice;
+
+ if (curr_slice->slice_type != I_SLICE && curr_slice->slice_type != SI_SLICE) {
+ reorder_cmd = &h264_ctx->dpb_param.mmco.l0_reorder_cmd[0];
+ /* 3:parsed by ucode, means no reorder needed */
+ if (reorder_cmd[0] != 3)
+ curr_slice->ref_pic_list_reordering_flag[0] = 1;
+ else
+ curr_slice->ref_pic_list_reordering_flag[0] = 0;
+
+ get_modification_cmd(reorder_cmd, curr_slice, 0);
+ }
+
+ if (curr_slice->slice_type == B_SLICE) {
+ reorder_cmd = &h264_ctx->dpb_param.mmco.l1_reorder_cmd[0];
+ /* 3:parsed by ucode, means no reorder needed */
+ if (reorder_cmd[0] != 3)
+ curr_slice->ref_pic_list_reordering_flag[1] = 1;
+ else
+ curr_slice->ref_pic_list_reordering_flag[1] = 0;
+
+ get_modification_cmd(reorder_cmd, curr_slice, 1);
+ }
+
+ if (curr_slice->slice_type != I_SLICE &&
+ curr_slice->slice_type != SI_SLICE &&
+ curr_slice->ref_pic_list_reordering_flag[0] != 0) {
+ copy_ref_list(h264_ctx, 0);
+ reorder_pics(h264_ctx, curr_slice, 0);
+ }
+
+ if (curr_slice->slice_type == B_SLICE &&
+ curr_slice->ref_pic_list_reordering_flag[1] != 0) {
+ copy_ref_list(h264_ctx, 1);
+ reorder_pics(h264_ctx, curr_slice, 1);
+ }
+}
+
+static void h264_config_ref_lists(struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv;
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+ struct v4l2_ctrl_h264_decode_params *decode =
+ (struct v4l2_ctrl_h264_decode_params *)ctrls->decode;
+ struct v4l2_ctrl_h264_sps *sps =
+ (struct v4l2_ctrl_h264_sps *)ctrls->sps;
+ const struct v4l2_h264_dpb_entry *dpb = decode->dpb;
+ struct v4l2_h264_reflist_builder builder;
+ struct v4l2_h264_reference v4l2_p0_reflist[V4L2_H264_REF_LIST_LEN];
+ struct v4l2_h264_reference v4l2_b0_reflist[V4L2_H264_REF_LIST_LEN];
+ struct v4l2_h264_reference v4l2_b1_reflist[V4L2_H264_REF_LIST_LEN];
+ struct slice *curr_slice = &h264_ctx->mslice;
+
+ if (decode->flags == V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC)
+ return;
+
+ v4l2_h264_init_reflist_builder(&builder, decode, sps, dpb);
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s num_valid = %d", __func__,
+ builder.num_valid);
+
+ if (curr_slice->slice_type == P_SLICE &&
+ (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_PFRAME)) {
+ v4l2_h264_build_p_ref_list(&builder, v4l2_p0_reflist);
+ config_p_reflist(h264_ctx, v4l2_p0_reflist, builder.num_valid);
+ } else if (curr_slice->slice_type == B_SLICE &&
+ (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_BFRAME)) {
+ v4l2_h264_build_b_ref_lists(&builder, v4l2_b0_reflist, v4l2_b1_reflist);
+ config_b_reflist(h264_ctx, v4l2_b0_reflist, v4l2_b1_reflist,
+ builder.num_valid);
+ }
+}
+
+static int allocate_canvas_pos(struct aml_h264_ctx *h264_ctx, int poc)
+{
+ int i;
+ int ret = -1;
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+
+ for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) {
+ if (((h264_ctx->canvas_pos_map >> i) & 0x1) == 0) {
+ h264_ctx->canvas_pos_map |= (1 << i);
+ h264_ctx->ref_canvas[i].poc = poc;
+ h264_ctx->ref_canvas[i].canvas_pos = i;
+ ret = i;
+
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s i %d pos_poc %d\n", __func__, i,
+ h264_ctx->ref_canvas[i].poc);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void release_canvas_pos(struct aml_h264_ctx *h264_ctx, int index)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+
+ if (index >= 0) {
+ if (index > V4L2_H264_NUM_DPB_ENTRIES) {
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s error, index %d is bigger than buf count %d\n",
+ __func__, index, h264_ctx->max_num_ref_frames);
+ } else {
+ if (h264_ctx->ref_canvas[index].poc != INVALID_POC &&
+ ((h264_ctx->canvas_pos_map >> index) & 0x1) ==
+ 0x1) {
+ h264_ctx->canvas_pos_map &= (~(1 << index));
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s canvas_pos index %d released poc %d, canvas_pos_map 0x%x\n",
+ __func__, index, h264_ctx->ref_canvas[index].poc,
+ h264_ctx->canvas_pos_map);
+ h264_ctx->ref_canvas[index].poc = INVALID_POC;
+ h264_ctx->ref_canvas[index].canvas_pos = -1;
+ }
+ }
+ }
+}
+
+static int get_canvas_pos_by_poc(struct aml_h264_ctx *h264_ctx, int poc)
+{
+ int i;
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ int ret_pos = -1;
+
+ for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) {
+ if (h264_ctx->ref_canvas[i].poc == poc) {
+ ret_pos = h264_ctx->ref_canvas[i].canvas_pos;
+ dev_dbg(&ctx->dev->plat_dev->dev, "%s canvas_pos %d\n",
+ __func__, ret_pos);
+ return ret_pos;
+ }
+ }
+
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "%s error, no find canvas pos %d, poc %d\n", __func__, ret_pos, poc);
+
+ return ret_pos;
+}
+
+static void clear_unused_col_buf(struct aml_h264_ctx *h264_ctx,
+ struct v4l2_ctrl_h264_decode_params *decode)
+{
+ int i, col_poc;
+
+ /* flush all col buffers when IDR */
+ if (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) {
+ /* 32 : max index of co-locate buffer */
+ for (i = 0; i < 32; i++)
+ release_colocate_buf(h264_ctx, i);
+ for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++)
+ release_canvas_pos(h264_ctx, i);
+ return;
+ }
+
+ for (i = 0; i < h264_ctx->colocated_buf_num; i++) {
+ col_poc = h264_ctx->colocated_buf_poc[i];
+ if (col_poc != INVALID_POC &&
+ (poc_is_in_dpb(col_poc, decode->dpb) != 1))
+ release_colocate_buf(h264_ctx, i);
+ }
+
+ for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) {
+ col_poc = h264_ctx->ref_canvas[i].poc;
+ if (col_poc != INVALID_POC &&
+ (poc_is_in_dpb(col_poc, decode->dpb) != 1))
+ release_canvas_pos(h264_ctx, i);
+ }
+}
+
+static void h264_config_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx;
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+ struct v4l2_ctrl_h264_decode_params *decode =
+ (struct v4l2_ctrl_h264_decode_params *)ctrls->decode;
+ struct h264_decode_buf_spec *buf_spec_l0, *buf_spec_l1;
+ struct vb2_buffer *vb;
+ struct vb2_v4l2_buffer *vb2_v4l2;
+ struct vb2_queue *vq;
+ int i;
+
+ clear_unused_col_buf(h264_ctx, decode);
+
+ vb2_v4l2 = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+ vb = &vb2_v4l2->vb2_buf;
+
+ h264_ctx->curr_spec.y_dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (ctx->pix_fmt[AML_FMT_DST].num_planes > 1)
+ h264_ctx->curr_spec.c_dma_addr =
+ vb2_dma_contig_plane_dma_addr(vb, 1);
+ else
+ h264_ctx->curr_spec.c_dma_addr =
+ h264_ctx->curr_spec.y_dma_addr + ctx->pic_info.fb_size[0];
+ h264_ctx->curr_spec.canvas_pos =
+ allocate_canvas_pos(h264_ctx, decode->top_field_order_cnt);
+ if (h264_ctx->curr_spec.canvas_pos < 0)
+ dev_err(&ctx->dev->plat_dev->dev, "curr_spec.canvas error\n");
+
+ if (decode->nal_ref_idc)
+ h264_ctx->curr_spec.col_buf_index =
+ allocate_colocate_buf(h264_ctx,
+ decode->top_field_order_cnt);
+ else
+ h264_ctx->curr_spec.col_buf_index = -1;
+ h264_ctx->curr_spec.poc = decode->top_field_order_cnt;
+
+ h264_config_ref_lists(ctx);
+
+ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+ for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
+ struct v4l2_h264_dpb_entry *dpb = &decode->dpb[i];
+
+ if (!(dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE))
+ continue;
+
+ buf_spec_l0 = find_spec_by_dpb_index(h264_ctx, i, 0);
+ if (buf_spec_l0) {
+ buf_spec_l0->canvas_pos =
+ get_canvas_pos_by_poc(h264_ctx,
+ dpb->top_field_order_cnt);
+ if (buf_spec_l0->canvas_pos < 0) {
+ dev_err(&ctx->dev->plat_dev->dev,
+ "l0 canvas_pos %d error\n",
+ buf_spec_l0->canvas_pos);
+ continue;
+ }
+ vb = vb2_find_buffer(vq, dpb->reference_ts);
+ if (!vb) {
+ dev_err(&ctx->dev->plat_dev->dev,
+ "ref pic for ts %llu lost\n", dpb->reference_ts);
+ continue;
+ }
+
+ buf_spec_l0->y_dma_addr =
+ vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (ctx->pix_fmt[AML_FMT_DST].num_planes > 1)
+ buf_spec_l0->c_dma_addr =
+ vb2_dma_contig_plane_dma_addr(vb, 1);
+ else
+ buf_spec_l0->c_dma_addr =
+ buf_spec_l0->y_dma_addr +
+ ctx->pic_info.fb_size[0];
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n",
+ buf_spec_l0->dpb->top_field_order_cnt,
+ buf_spec_l0->canvas_pos,
+ &buf_spec_l0->y_dma_addr,
+ &buf_spec_l0->c_dma_addr);
+ }
+
+ buf_spec_l1 = find_spec_by_dpb_index(h264_ctx, i, 1);
+ if (!buf_spec_l0 && buf_spec_l1) {
+ buf_spec_l1->canvas_pos =
+ get_canvas_pos_by_poc(h264_ctx,
+ dpb->top_field_order_cnt);
+ if (buf_spec_l1->canvas_pos < 0) {
+ dev_err(&ctx->dev->plat_dev->dev,
+ "l1 canvas_pos %d error\n",
+ buf_spec_l1->canvas_pos);
+ continue;
+ }
+ vb = vb2_find_buffer(vq, dpb->reference_ts);
+ if (!vb) {
+ dev_err(&ctx->dev->plat_dev->dev,
+ "ref pic for ts %llu lost\n", dpb->reference_ts);
+ continue;
+ }
+
+ buf_spec_l1->y_dma_addr =
+ vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (ctx->pix_fmt[AML_FMT_DST].num_planes > 1)
+ buf_spec_l1->c_dma_addr =
+ vb2_dma_contig_plane_dma_addr(vb, 1);
+ else
+ buf_spec_l1->c_dma_addr =
+ buf_spec_l1->y_dma_addr +
+ ctx->pic_info.fb_size[0];
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n",
+ buf_spec_l1->dpb->top_field_order_cnt,
+ buf_spec_l1->canvas_pos,
+ &buf_spec_l1->y_dma_addr,
+ &buf_spec_l1->c_dma_addr);
+ } else if (buf_spec_l0 && buf_spec_l1) {
+ memcpy(buf_spec_l1, buf_spec_l0,
+ sizeof(struct h264_decode_buf_spec));
+ dev_dbg(&ctx->dev->plat_dev->dev,
+ "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n",
+ buf_spec_l1->dpb->top_field_order_cnt,
+ buf_spec_l1->canvas_pos,
+ &buf_spec_l1->y_dma_addr,
+ &buf_spec_l1->c_dma_addr);
+ }
+ }
+}
+
+static int get_poc_by_canvas_pos(struct aml_h264_ctx *h264_ctx, int canvas_pos)
+{
+ int i;
+
+ for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) {
+ if (h264_ctx->ref_canvas[i].canvas_pos == canvas_pos)
+ return h264_ctx->ref_canvas[i].poc;
+ }
+ return -1;
+}
+
+static struct v4l2_h264_dpb_entry *get_dpb_by_poc(struct v4l2_ctrl_h264_decode_params *decode,
+ int poc)
+{
+ int i;
+
+ for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) {
+ if (decode->dpb[i].top_field_order_cnt == poc)
+ return &decode->dpb[i];
+ }
+ return NULL;
+}
+
+static int h264_config_decode_buf(struct aml_vdec_hw *hw,
+ struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx;
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+ struct v4l2_ctrl_h264_decode_params *decode =
+ (struct v4l2_ctrl_h264_decode_params *)ctrls->decode;
+ unsigned int canvas_adr;
+ unsigned int ref_cfg;
+ unsigned int ref_cfg_once = 0;
+ struct slice *curr_slice = &h264_ctx->mslice;
+ unsigned int type_cfg = 0x3; /* 0x3: frame type */
+ unsigned int colocate_adr_offset = 0;
+ unsigned int colocate_wr_adr;
+ unsigned int info0;
+ unsigned int info1;
+ unsigned int info2;
+ int i, j;
+ int h264_buffer_info_data_write_count = 0;
+ u8 canvas_pos;
+ u8 use_mode_8x8_flag;
+ u32 reg_val;
+
+ regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC_IDX_RESET, 0);
+ regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->top_field_order_cnt);
+ regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->top_field_order_cnt);
+ regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->bottom_field_order_cnt);
+ regmap_write(hw->map[DOS_BUS], CURR_CANVAS_CTRL, h264_ctx->curr_spec.canvas_pos << 24);
+ regmap_read(hw->map[DOS_BUS], CURR_CANVAS_CTRL, &canvas_adr);
+ canvas_adr &= 0xffffff;
+ dev_dbg(hw->dev, "canvas_pos = %d canvas_adr 0x%x\n",
+ h264_ctx->curr_spec.canvas_pos, canvas_adr);
+
+ regmap_write(hw->map[DOS_BUS], REC_CANVAS_ADDR, canvas_adr);
+ regmap_write(hw->map[DOS_BUS], DBKR_CANVAS_ADDR, canvas_adr);
+ regmap_write(hw->map[DOS_BUS], DBKW_CANVAS_ADDR, canvas_adr);
+
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 16);
+
+ for (j = 0; j < (V4L2_H264_NUM_DPB_ENTRIES + 1); j++) {
+ int poc;
+ struct v4l2_h264_dpb_entry *dpb = NULL;
+
+ info0 = 0;
+ info1 = 0;
+ info2 = 0;
+
+ poc = get_poc_by_canvas_pos(h264_ctx, j);
+ if (poc == decode->top_field_order_cnt) {
+ info0 = 0xf480 | 0xf;
+ info1 = decode->top_field_order_cnt;
+ info2 = decode->bottom_field_order_cnt;
+ if (decode->bottom_field_order_cnt <
+ decode->top_field_order_cnt)
+ info0 |= 0x100;
+ } else {
+ dpb = get_dpb_by_poc(decode, poc);
+ if (dpb && (dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) {
+ info0 = 0xf480;
+ if (dpb->bottom_field_order_cnt <
+ dpb->top_field_order_cnt)
+ info0 |= 0x100;
+ info1 = dpb->top_field_order_cnt;
+ info2 = dpb->bottom_field_order_cnt;
+ if (dpb->flags &
+ V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)
+ info0 |= ((1 << 5) | (1 << 4));
+ }
+ }
+
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info0);
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info1);
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info2);
+ }
+
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 0);
+ /* when frame width <= 256, Disable DDR_BYTE64_CACHE */
+ if (ctx->pic_info.coded_width <= 256) {
+ regmap_update_bits(hw->map[DOS_BUS], IQIDCT_CONTROL, (1 << 16), (1 << 16));
+ regmap_write(hw->map[DOS_BUS], DCAC_DDR_BYTE64_CTL,
+ (read_dos_reg(hw, DCAC_DDR_BYTE64_CTL) & (~0xf)) | 0xa);
+ } else {
+ regmap_update_bits(hw->map[DOS_BUS], IQIDCT_CONTROL, (1 << 16), 0);
+ regmap_write(hw->map[DOS_BUS], DCAC_DDR_BYTE64_CTL,
+ (read_dos_reg(hw, DCAC_DDR_BYTE64_CTL) & (~0xf)));
+ }
+
+ ref_cfg = 0;
+ j = 0;
+
+ for (i = 0; i < h264_ctx->list_size[0]; i++) {
+ canvas_pos = h264_ctx->ref_list0[i].canvas_pos;
+ /* bit 0:3 canvas_pos bit 5:6 frame struct cfg */
+ ref_cfg_once = (canvas_pos & 0x1f) | (type_cfg << 5);
+ ref_cfg <<= 8;
+ ref_cfg |= ref_cfg_once;
+ j++;
+
+ if (j == 4) {
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA,
+ ref_cfg);
+ dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n",
+ ref_cfg);
+ h264_buffer_info_data_write_count++;
+ j = 0;
+ }
+ }
+
+ if (j != 0) {
+ while (j != 4) {
+ ref_cfg <<= 8;
+ ref_cfg |= ref_cfg_once;
+ j++;
+ }
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg);
+ dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg);
+ h264_buffer_info_data_write_count++;
+ }
+ ref_cfg = (ref_cfg_once << 24) | (ref_cfg_once << 16) |
+ (ref_cfg_once << 8) | ref_cfg_once;
+ for (j = h264_buffer_info_data_write_count; j < 8; j++) {
+ dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg);
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg);
+ }
+
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 8);
+ j = 0;
+ ref_cfg = 0;
+
+ for (i = 0; i < h264_ctx->list_size[1]; i++) {
+ canvas_pos = h264_ctx->ref_list1[i].canvas_pos;
+ ref_cfg_once = (canvas_pos & 0x1f) | (type_cfg << 5);
+ ref_cfg <<= 8;
+ ref_cfg |= ref_cfg_once;
+ j++;
+
+ if (j == 4) {
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg);
+ dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg);
+ j = 0;
+ }
+ }
+
+ if (j != 0) {
+ while (j != 4) {
+ ref_cfg <<= 8;
+ ref_cfg |= ref_cfg_once;
+ j++;
+ }
+ dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg);
+ regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg);
+ }
+
+ if (get_flag(ctrls->sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) &&
+ get_flag(ctrls->sps->flags, V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE))
+ use_mode_8x8_flag = 1;
+ else
+ use_mode_8x8_flag = 0;
+
+ read_poll_timeout(read_dos_reg, reg_val,
+ !(reg_val & 0x800),
+ 10, 0, true, hw, H264_CO_MB_RW_CTL);
+
+ /* col buf for curr frame */
+ colocate_adr_offset = COL_SIZE_FOR_ONE_MB;
+ if (use_mode_8x8_flag)
+ colocate_adr_offset >>= 2;
+ colocate_adr_offset *= curr_slice->first_mb_in_slice;
+
+ if (h264_ctx->curr_spec.col_buf_index >= 0 &&
+ h264_ctx->curr_spec.col_buf_index < h264_ctx->colocated_buf_num) {
+ /* collated_cma_addr is limited to 32bit by flag GFP_DMA32 when allocated */
+ colocate_wr_adr = (unsigned int)h264_ctx->collated_cma_addr +
+ ((h264_ctx->one_col_buf_size *
+ h264_ctx->curr_spec.col_buf_index) >> (use_mode_8x8_flag ? 2 : 0));
+ if (colocate_adr_offset > h264_ctx->one_col_buf_size ||
+ colocate_wr_adr + h264_ctx->one_col_buf_size >
+ (unsigned int)h264_ctx->collated_cma_addr_end) {
+ dev_err(hw->dev,
+ "Error, colocate buf is not enough, index is %d\n",
+ h264_ctx->curr_spec.col_buf_index);
+ return -1;
+ }
+ regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR,
+ (colocate_wr_adr + colocate_adr_offset));
+ dev_dbg(hw->dev, "col buffer addr = 0x%x col_buf_index %d\n",
+ (colocate_wr_adr + colocate_adr_offset),
+ h264_ctx->curr_spec.col_buf_index);
+ } else {
+ regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, 0xffffffff);
+ dev_dbg(hw->dev, "col buffer addr = 0xffffffff\n");
+ }
+
+ if (h264_ctx->list_size[1] > 0) {
+ struct h264_decode_buf_spec *colocate_pic =
+ &h264_ctx->ref_list1[0];
+ struct h264_decode_buf_spec *curr_pic = &h264_ctx->curr_spec;
+ int l10_structure = 2; /* for pic struct == FRAME, default to 2 */
+ int cur_colocate_ref_type;
+ unsigned int colocate_rd_adr;
+ unsigned int colocate_rd_adr_offset = 0;
+ unsigned int val;
+
+ cur_colocate_ref_type =
+ (abs(curr_pic->poc - colocate_pic->dpb->top_field_order_cnt) <
+ abs(curr_pic->poc - colocate_pic->dpb->bottom_field_order_cnt)) ? 0 : 1;
+ colocate_rd_adr_offset = COL_SIZE_FOR_ONE_MB;
+ if (use_mode_8x8_flag)
+ colocate_rd_adr_offset >>= 2;
+
+ colocate_rd_adr_offset *= curr_slice->first_mb_in_slice;
+ if (colocate_pic->col_buf_index >= 0 &&
+ colocate_pic->col_buf_index < h264_ctx->colocated_buf_num) {
+ /* collated_cma_addr is limited to 32bit by flag GFP_DMA32 when allocated */
+ colocate_rd_adr = (unsigned int)h264_ctx->collated_cma_addr +
+ ((h264_ctx->one_col_buf_size *
+ colocate_pic->col_buf_index) >> (use_mode_8x8_flag
+ ? 2 : 0));
+ if (colocate_rd_adr + h264_ctx->one_col_buf_size >
+ (unsigned int)h264_ctx->collated_cma_addr_end) {
+ dev_err(hw->dev,
+ "Error, colocate rd buf is not enough, index is %d\n",
+ colocate_pic->col_buf_index);
+ return -1;
+ }
+ val = ((colocate_rd_adr_offset + colocate_rd_adr) >> 3) |
+ (cur_colocate_ref_type << 29) |
+ (l10_structure << 30);
+ regmap_write(hw->map[DOS_BUS], H264_CO_MB_RD_ADDR, val);
+ } else {
+ dev_err
+ (hw->dev,
+ "Error, reference pic has no colocated buf poc %d\n",
+ curr_pic->poc);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void get_canvas_index(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx;
+ int i;
+ struct h264_decode_buf_spec *buf;
+
+ config_decode_canvas(hw, &h264_ctx->curr_spec,
+ h264_ctx->mb_width, h264_ctx->mb_height);
+ if (h264_ctx->list_size[0] > 0) {
+ for (i = 0; i < h264_ctx->list_size[0]; i++) {
+ buf = &h264_ctx->ref_list0[i];
+ config_decode_canvas(hw, buf, h264_ctx->mb_width,
+ h264_ctx->mb_height);
+ }
+ }
+
+ if (h264_ctx->list_size[1] > 0) {
+ for (i = 0; i < h264_ctx->list_size[1]; i++) {
+ buf = &h264_ctx->ref_list1[i];
+ config_decode_canvas(hw, buf, h264_ctx->mb_width,
+ h264_ctx->mb_height);
+ }
+ }
+}
+
+static void release_canvas_index(struct aml_vdec_hw *hw,
+ struct h264_decode_buf_spec *buf)
+{
+ dev_dbg(hw->dev, "free y_canvas %d uv_canvas %d\n",
+ buf->y_canvas_index, buf->u_canvas_index);
+ meson_canvas_free(hw->canvas, buf->y_canvas_index);
+ meson_canvas_free(hw->canvas, buf->u_canvas_index);
+}
+
+static void h264_release_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx;
+ int i;
+ struct h264_decode_buf_spec *buf;
+
+ release_canvas_index(hw, &h264_ctx->curr_spec);
+ if (h264_ctx->list_size[0] > 0) {
+ for (i = 0; i < h264_ctx->list_size[0]; i++) {
+ buf = &h264_ctx->ref_list0[i];
+ if (buf->used) {
+ buf->dpb = NULL;
+ release_canvas_index(hw, buf);
+ buf->used = 0;
+ }
+ }
+ h264_ctx->list_size[0] = 0;
+ }
+
+ if (h264_ctx->list_size[1] > 0) {
+ for (i = 0; i < h264_ctx->list_size[1]; i++) {
+ buf = &h264_ctx->ref_list1[i];
+ if (buf->used) {
+ buf->dpb = NULL;
+ release_canvas_index(hw, buf);
+ buf->used = 0;
+ }
+ }
+ h264_ctx->list_size[1] = 0;
+ }
+}
+
+static void save_reg_status(struct aml_h264_ctx *h264_ctx)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev);
+
+ regmap_read(hw->map[DOS_BUS], IQIDCT_CONTROL, &h264_ctx->reg_iqidct_control);
+ h264_ctx->reg_iqidct_control_init_flag = 1;
+ regmap_read(hw->map[DOS_BUS], VCOP_CTRL_REG, &h264_ctx->reg_vcop_ctrl_reg);
+ regmap_read(hw->map[DOS_BUS], RV_AI_MB_COUNT, &h264_ctx->reg_rv_ai_mb_count);
+ regmap_read(hw->map[DOS_BUS], VLD_DECODE_CONTROL, &h264_ctx->vld_dec_control);
+}
+
+static void h264_get_slice_params(struct aml_h264_ctx *h264_ctx)
+{
+ struct slice *curr_slice = &h264_ctx->mslice;
+
+ memset(curr_slice, 0, sizeof(struct slice));
+ /* parsed by ucode */
+ switch (h264_ctx->dpb_param.l.data[SLICE_TYPE]) {
+ case I_Slice:
+ curr_slice->slice_type = I_SLICE;
+ break;
+ case P_Slice:
+ curr_slice->slice_type = P_SLICE;
+ break;
+ case B_Slice:
+ curr_slice->slice_type = B_SLICE;
+ break;
+ default:
+ curr_slice->slice_type = MAX_SLICE_TYPES;
+ break;
+ }
+
+ curr_slice->first_mb_in_slice =
+ h264_ctx->dpb_param.l.data[FIRST_MB_IN_SLICE];
+ curr_slice->num_ref_idx_l0 =
+ h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1 + 1;
+ curr_slice->num_ref_idx_l1 =
+ h264_ctx->dpb_param.dpb.num_ref_idx_l1_active_minus1 + 1;
+ curr_slice->frame_num = h264_ctx->ctrl_ref.decode->frame_num;
+}
+
+static irqreturn_t h264_isr(int irq, void *priv)
+{
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+
+ regmap_write(dev->dec_hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_CLR_REG, 1);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t h264_threaded_isr_func(int irq, void *priv)
+{
+ u32 dec_status;
+ struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)dev->dec_hw->curr_ctx;
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx;
+ struct aml_vdec_hw *hw = vdec_get_hw(dev);
+ unsigned short *p = (unsigned short *)h264_ctx->lmem_addr;
+ int i, ii;
+
+ regmap_read(hw->map[DOS_BUS], DPB_STATUS_REG, &dec_status);
+ h264_ctx->dec_status = dec_status;
+ dev_dbg
+ (&dev->plat_dev->dev,
+ "%s, dec_status 0x%x VIFF_BIT_CNT 0x%x MBY_MBX 0x%x VLD_SHIFT_STATUS 0x%x\n",
+ __func__, dec_status, read_dos_reg(hw, VIFF_BIT_CNT),
+ read_dos_reg(hw, MBY_MBX), read_dos_reg(hw, VLD_SHIFT_STATUS));
+
+ regmap_read(hw->map[DOS_BUS], AV_SCRATCH_F, &h264_ctx->save_avscratch_f);
+
+ switch (dec_status) {
+ case H264_SLICE_HEADER_DONE:
+ for (i = 0; i < 0x400; i += 4)
+ for (ii = 0; ii < 4; ii++)
+ h264_ctx->dpb_param.l.data[i + ii] = p[i + 3 - ii];
+ save_reg_status(h264_ctx);
+ h264_get_slice_params(h264_ctx);
+ if (h264_ctx->mslice.first_mb_in_slice != 0)
+ h264_release_decode_spec(hw, ctx);
+
+ h264_config_decode_spec(hw, ctx);
+ h264_reorder_reflists(h264_ctx);
+ get_canvas_index(hw, ctx);
+
+ if (h264_config_decode_buf(hw, ctx) < 0) {
+ h264_release_decode_spec(hw, ctx);
+ ctx->int_cond = 1;
+ ctx->curr_frm_err_flag = 1;
+ wake_up_interruptible(&ctx->queue);
+ goto irq_handled;
+ }
+ if (h264_ctx->new_pic_flag == 1) {
+ regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_NEWPIC);
+ dev_dbg(&dev->plat_dev->dev, "action decode new pic\n");
+ h264_ctx->new_pic_flag = 0;
+ } else {
+ regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_SLICE);
+ dev_dbg(&dev->plat_dev->dev, "action decode new slice\n");
+ }
+ break;
+ case H264_SLICE_DATA_DONE:
+ h264_release_decode_spec(hw, ctx);
+ h264_ctx->decode_pic_count++;
+ ctx->int_cond = 1;
+ wake_up_interruptible(&ctx->queue);
+ break;
+ default:
+ h264_release_decode_spec(hw, ctx);
+ ctx->int_cond = 1;
+ ctx->curr_frm_err_flag = 1;
+ wake_up_interruptible(&ctx->queue);
+ break;
+ }
+irq_handled:
+ return IRQ_HANDLED;
+}
+
+static int h264_restore_hw_ctx(struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv;
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev);
+
+ regmap_write(hw->map[DOS_BUS], POWER_CTL_VLD,
+ (read_dos_reg(hw, POWER_CTL_VLD) | (0 << 10) | (1 << 9) | (1 << 6)));
+
+ regmap_write(hw->map[DOS_BUS], PSCALE_CTRL, 0);
+
+ /* clear mailbox interrupt */
+ regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_CLR_REG, 1);
+
+ /* enable mailbox interrupt */
+ regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_MASK, 1);
+
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 17), (1 << 17));
+ if (ctx->dec_fmt[AML_FMT_DST].fourcc == V4L2_PIX_FMT_NV21 ||
+ ctx->dec_fmt[AML_FMT_DST].fourcc == V4L2_PIX_FMT_NV21M)
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL,
+ (1 << 16), (1 << 16));
+ else
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 16), 0);
+
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL,
+ (0xbf << 24), (0xbf << 24));
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (0xbf << 24), 0);
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0);
+
+ regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_MUX_CTRL, (1 << 31), 0);
+ regmap_write(hw->map[DOS_BUS], MDEC_EXTIF_CFG1, 0);
+ regmap_write(hw->map[DOS_BUS], MDEC_PIC_DC_THRESH, 0x404038aa);
+
+ regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, 0);
+
+ regmap_write(hw->map[DOS_BUS], LMEM_DUMP_ADR, h264_ctx->lmem_phy_addr);
+ regmap_write(hw->map[DOS_BUS], FRAME_COUNTER_REG, h264_ctx->decode_pic_count);
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_8, h264_ctx->workspace_offset);
+
+ regmap_write(hw->map[DOS_BUS], AV_SCRATCH_F,
+ ((h264_ctx->save_avscratch_f & 0xffffffc3) | (1 << 4)));
+ regmap_update_bits(hw->map[DOS_BUS], AV_SCRATCH_F, (1 << 6), 0);
+
+ regmap_write(hw->map[DOS_BUS], MDEC_PIC_DC_THRESH, 0x404038aa);
+
+ if (h264_ctx->reg_iqidct_control_init_flag == 0)
+ regmap_write(hw->map[DOS_BUS], IQIDCT_CONTROL, 0x200);
+
+ if (h264_ctx->reg_iqidct_control)
+ regmap_write(hw->map[DOS_BUS], IQIDCT_CONTROL, h264_ctx->reg_iqidct_control);
+
+ if (h264_ctx->reg_vcop_ctrl_reg)
+ regmap_write(hw->map[DOS_BUS], VCOP_CTRL_REG, h264_ctx->reg_vcop_ctrl_reg);
+
+ if (h264_ctx->vld_dec_control)
+ regmap_write(hw->map[DOS_BUS], VLD_DECODE_CONTROL, h264_ctx->vld_dec_control);
+
+ dev_dbg
+ (hw->dev,
+ "IQIDCT_CONTROL = 0x%x, VCOP_CTRL_REG 0x%x VLD_DECODE_CONTROL 0x%x\n",
+ read_dos_reg(hw, IQIDCT_CONTROL), read_dos_reg(hw, VCOP_CTRL_REG),
+ read_dos_reg(hw, VLD_DECODE_CONTROL));
+
+ return 0;
+}
+
+static void *aml_h264_get_ctrl(struct v4l2_ctrl_handler *hdl, u32 id)
+{
+ struct v4l2_ctrl *ctrl;
+
+ ctrl = v4l2_ctrl_find(hdl, id);
+ return ctrl ? ctrl->p_cur.p : NULL;
+}
+
+static int aml_h264_get_stateless_ctrl_ref(struct aml_h264_ctx *h264_ctx)
+{
+ struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx;
+ struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref;
+
+ ctrls->sps =
+ (struct v4l2_ctrl_h264_sps *)aml_h264_get_ctrl(&ctx->ctrl_handler,
+ V4L2_CID_STATELESS_H264_SPS);
+ if (WARN_ON(!ctrls->sps))
+ return -EINVAL;
+
+ ctrls->pps =
+ (struct v4l2_ctrl_h264_pps *)aml_h264_get_ctrl(&ctx->ctrl_handler,
+ V4L2_CID_STATELESS_H264_PPS);
+ if (WARN_ON(!ctrls->pps))
+ return -EINVAL;
+
+ ctrls->decode =
+ (struct v4l2_ctrl_h264_decode_params *)aml_h264_get_ctrl(&ctx->ctrl_handler,
+ V4L2_CID_STATELESS_H264_DECODE_PARAMS);
+ if (WARN_ON(!ctrls->decode))
+ return -EINVAL;
+
+ ctrls->scaling =
+ (struct v4l2_ctrl_h264_scaling_matrix *)aml_h264_get_ctrl(&ctx->ctrl_handler,
+ V4L2_CID_STATELESS_H264_SCALING_MATRIX);
+ if (WARN_ON(!ctrls->scaling))
+ return -EINVAL;
+
+ return 0;
+}
+
+int aml_h264_init(void *priv)
+{
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv;
+ struct aml_vdec_hw *dec_hw;
+ struct aml_h264_ctx *h264_ctx;
+ int ret = 0;
+
+ h264_ctx = kzalloc_obj(*h264_ctx, GFP_KERNEL);
+ if (!h264_ctx)
+ return -ENOMEM;
+
+ h264_ctx->v4l2_ctx = ctx;
+ dec_hw = vdec_get_hw(ctx->dev);
+ if (!dec_hw) {
+ ret = -1;
+ goto err_alloc_lmem;
+ }
+
+ h264_ctx->mc_cpu_loaded = false;
+ dec_hw->hw_ops.irq_handler = h264_isr;
+ dec_hw->hw_ops.irq_threaded_func = h264_threaded_isr_func;
+
+ h264_ctx->lmem_addr = dma_alloc_coherent(dec_hw->dev, LMEM_DUMP_SIZE,
+ &h264_ctx->lmem_phy_addr,
+ GFP_KERNEL);
+ if (!h264_ctx->lmem_addr) {
+ ret = -ENOMEM;
+ goto err_alloc_lmem;
+ }
+
+ h264_ctx->cma_alloc_vaddr =
+ dma_alloc_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET,
+ &h264_ctx->cma_alloc_addr, GFP_KERNEL);
+ if (!h264_ctx->cma_alloc_vaddr) {
+ ret = -ENOMEM;
+ goto err_alloc_workspace;
+ }
+
+ h264_ctx->workspace_offset = h264_ctx->cma_alloc_addr + DCAC_READ_MARGIN;
+ h264_ctx->workspace_vaddr = h264_ctx->cma_alloc_vaddr + DCAC_READ_MARGIN;
+
+ ctx->codec_priv = h264_ctx;
+ h264_ctx->col_buf_alloc_size = 0;
+ h264_ctx->init_flag = 0;
+ h264_ctx->new_pic_flag = 0;
+ h264_ctx->param_set = 0;
+ h264_ctx->reg_iqidct_control_init_flag = 0;
+ h264_ctx->decode_pic_count = 0;
+
+ return 0;
+
+err_alloc_workspace:
+ dma_free_coherent(dec_hw->dev, LMEM_DUMP_SIZE,
+ h264_ctx->lmem_addr, h264_ctx->lmem_phy_addr);
+err_alloc_lmem:
+ kfree(h264_ctx);
+
+ return ret;
+}
+
+void aml_h264_exit(void *priv)
+{
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv;
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv;
+ struct aml_vdec_hw *dec_hw;
+
+ if (!h264_ctx) {
+ dev_info(&ctx->dev->plat_dev->dev,
+ "h264 decoder is already destroyed or not created!\n");
+ return;
+ }
+ dec_hw = vdec_get_hw(ctx->dev);
+ h264_ctx->param_set = 0;
+
+ synchronize_irq(dec_hw->dec_irq);
+
+ if (ctx->dos_clk_en)
+ aml_stop_vdec_hw(dec_hw);
+
+ if (h264_ctx->collated_cma_vaddr) {
+ dma_free_coherent(dec_hw->dev, h264_ctx->col_buf_alloc_size,
+ h264_ctx->collated_cma_vaddr,
+ h264_ctx->collated_cma_addr);
+ h264_ctx->col_buf_alloc_size = 0;
+ }
+
+ if (h264_ctx->mc_cpu_vaddr) {
+ dma_free_coherent(dec_hw->dev, MC_TOTAL_SIZE,
+ h264_ctx->mc_cpu_vaddr,
+ h264_ctx->mc_cpu_paddr);
+ h264_ctx->mc_cpu_loaded = false;
+ }
+
+ if (h264_ctx->lmem_addr)
+ dma_free_coherent(dec_hw->dev, LMEM_DUMP_SIZE,
+ h264_ctx->lmem_addr, h264_ctx->lmem_phy_addr);
+
+ if (h264_ctx->cma_alloc_vaddr)
+ dma_free_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET,
+ h264_ctx->cma_alloc_vaddr,
+ h264_ctx->cma_alloc_addr);
+
+ kfree(ctx->codec_priv);
+ ctx->codec_priv = NULL;
+}
+
+static void config_decode_mode(struct aml_vdec_ctx *ctx)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv;
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev);
+
+ regmap_write(hw->map[DOS_BUS], H264_DECODE_MODE, 0x1); /*decode mode framebase */
+ regmap_write(hw->map[DOS_BUS], HEAD_PADDING_REG, 0);
+ regmap_write(hw->map[DOS_BUS], H264_DECODE_SEQINFO, h264_ctx->seq_info);
+ regmap_write(hw->map[DOS_BUS], INIT_FLAG_REG, 1);
+}
+
+int aml_h264_dec_run(void *priv)
+{
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv;
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv;
+ struct aml_vdec_hw *dec_hw = vdec_get_hw(ctx->dev);
+ int ret = -1;
+ int i;
+
+ dec_hw->curr_ctx = h264_ctx;
+
+ ret = aml_h264_get_stateless_ctrl_ref(h264_ctx);
+ if (ret < 0) {
+ dev_err(&ctx->dev->plat_dev->dev, "not ctrl ref for h264 decoder\n");
+ return ret;
+ }
+
+ h264_ctx->new_pic_flag = 1;
+ h264_config_params(ctx);
+
+ if (h264_prepare_input(ctx) < 0)
+ return ret;
+
+ if (alloc_colocate_cma(h264_ctx, ctx) < 0)
+ return ret;
+
+ h264_restore_hw_ctx(ctx);
+
+ config_decode_mode(ctx);
+ /* enable stream input hardware */
+ regmap_update_bits(dec_hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0x6, 0x6);
+ /* enable hardware timer */
+ regmap_write(dec_hw->map[DOS_BUS], NAL_SEARCH_CTL,
+ read_dos_reg(dec_hw, NAL_SEARCH_CTL) | (1 << 16));
+ regmap_write(dec_hw->map[DOS_BUS], MDEC_EXTIF_CFG2,
+ read_dos_reg(dec_hw, MDEC_EXTIF_CFG2) | 0x20);
+ regmap_write(dec_hw->map[DOS_BUS], NAL_SEARCH_CTL,
+ read_dos_reg(dec_hw, NAL_SEARCH_CTL) & (~0x2));
+ regmap_update_bits(dec_hw->map[DOS_BUS], VDEC_ASSIST_MMC_CTRL1,
+ (1 << 3), 0);
+
+ aml_start_vdec_hw(dec_hw);
+ h264_ctx->init_flag = 1;
+
+ regmap_write(dec_hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_SEARCH_HEAD);
+
+ ret = wait_event_interruptible_timeout(ctx->queue, ctx->int_cond,
+ msecs_to_jiffies(DECODER_TIMEOUT_MS));
+ ctx->int_cond = 0;
+ if (!ret) {
+ ret = -1;
+ dev_err(&ctx->dev->plat_dev->dev, "dec timeout=%u\n", DECODER_TIMEOUT_MS);
+ for (i = 0; i < 16; i++) { /* 16 : show ucode PC 16 times when timeout */
+ dev_dbg(&ctx->dev->plat_dev->dev, "decoder timeout, pc 0x%x\n",
+ read_dos_reg(dec_hw, MPC_E));
+ usleep_range(10, 20);
+ }
+ h264_release_decode_spec(dec_hw, ctx);
+ } else if (-ERESTARTSYS == ret) {
+ ret = -1;
+ h264_release_decode_spec(dec_hw, ctx);
+ dev_err(&ctx->dev->plat_dev->dev, "dec inter fail\n");
+ }
+
+ aml_stop_vdec_hw(dec_hw);
+ h264_ctx->init_flag = 0;
+ dec_hw->curr_ctx = NULL;
+
+ return ret;
+}
diff --git a/drivers/media/platform/amlogic/vdec/h264.h b/drivers/media/platform/amlogic/vdec/h264.h
new file mode 100644
index 000000000000..830ab3241a1e
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/h264.h
@@ -0,0 +1,299 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+#ifndef _H264_H_
+#define _H264_H_
+
+#define RPM_BEGIN 0x0
+#define FRAME_IN_DPB 24
+#define RPM_END 0x400
+#define DPB_OFFSET 0x100
+#define MMCO_OFFSET 0x200
+#define SPS_OFFSET 0x100
+#define PPS_OFFSET 0x300
+#define PARAM_BASE_VAL 0x414d
+#define MEM_MMCO_BASE 0x01c3000
+#define MEM_SPS_BASE 0x01c3c00
+#define MEM_PPS_BASE 0x01cbc00
+#define MC_TOTAL_SIZE ((20 + 16) * SZ_1K)
+#define MC_SWAP_SIZE (4 * SZ_1K)
+#define LMEM_DUMP_SIZE 4096
+#define V_BUF_ADDR_OFFSET (0x200000 + 0x8000 + 0x20000 + 0x1000)
+#define DCAC_READ_MARGIN (64 * 1024)
+#define MC_OFFSET_HEADER 0x0000
+#define MC_OFFSET_DATA 0x1000
+#define MC_OFFSET_MMCO 0x2000
+#define MC_OFFSET_LIST 0x3000
+#define MC_OFFSET_SLICE 0x4000
+#define MC_OFFSET_MAIN 0x5000
+
+/* Rename the dos regs */
+#define H264_DECODE_INFO M4_CONTROL_REG
+#define INIT_FLAG_REG AV_SCRATCH_2
+#define HEAD_PADDING_REG AV_SCRATCH_3
+#define UCODE_WATCHDOG_REG AV_SCRATCH_7
+#define LMEM_DUMP_ADR AV_SCRATCH_L
+#define DEBUG_REG1 AV_SCRATCH_M
+#define DEBUG_REG2 AV_SCRATCH_N
+#define FRAME_COUNTER_REG AV_SCRATCH_I
+#define RPM_CMD_REG AV_SCRATCH_A
+#define H264_DECODE_SIZE AV_SCRATCH_E
+#define H264_DECODE_MODE AV_SCRATCH_4
+#define H264_DECODE_SEQINFO AV_SCRATCH_5
+/**
+ * NAL_SEARCH_CTL: bit 0, enable itu_t35
+ * NAL_SEARCH_CTL: bit 1, enable mmu
+ * NAL_SEARCH_CTL: bit 2, detect frame_mbs_only_flag whether switch resolution
+ * NAL_SEARCH_CTL: bit 3, recover the correct sps pps
+ * NAL_SEARCH_CTL: bit 7-14,level_idc
+ * NAL_SEARCH_CTL: bit 15,bitstream_restriction_flag
+ */
+#define NAL_SEARCH_CTL AV_SCRATCH_9
+#define DPB_STATUS_REG AV_SCRATCH_J
+#define ERROR_STATUS_REG AV_SCRATCH_9
+
+#define H264_BUFFER_INFO_INDEX PMV3_X /* 0xc24 */
+#define H264_BUFFER_INFO_DATA PMV2_X /* 0xc22 */
+#define H264_CURRENT_POC_IDX_RESET LAST_SLICE_MV_ADDR /* 0xc30 */
+#define H264_CURRENT_POC LAST_MVY /* 0xc32 shared with conceal MV */
+#define H264_CO_MB_WR_ADDR VLD_C38
+#define H264_CO_MB_RD_ADDR VLD_C39
+#define H264_CO_MB_RW_CTL VLD_C3D
+#define MBY_MBX MB_MOTION_MODE
+
+#define H264_ACTION_SEARCH_HEAD 0xf0
+#define H264_ACTION_DECODE_SLICE 0xf1
+#define H264_ACTION_CONFIG_DONE 0xf2
+#define H264_ACTION_DECODE_NEWPIC 0xf3
+#define H264_ACTION_DECODE_START 0xff
+
+/* RPM memory definition */
+#define FIXED_FRAME_RATE_FLAG 0X21
+#define OFFSET_DELIMITER_LO 0x2f
+#define OFFSET_DELIMITER_HI 0x30
+#define SLICE_IPONLY_BREAK 0X5C
+#define PREV_MAX_REFERENCE_FRAME_NUM 0X5D
+#define EOS 0X5E
+#define FRAME_PACKING_TYPE 0X5F
+#define OLD_POC_PAR_1 0X60
+#define OLD_POC_PAR_2 0X61
+#define PREV_MBX 0X62
+#define PREV_MBY 0X63
+#define ERROR_SKIP_MB_NUM 0X64
+#define ERROR_MB_STATUS 0X65
+#define L0_PIC0_STATUS 0X66
+#define TIMEOUT_COUNTER 0X67
+#define BUFFER_SIZE 0X68
+#define BUFFER_SIZE_HI 0X69
+#define CROPPING_LEFT_RIGHT 0X6A
+#define CROPPING_TOP_BOTTOM 0X6B
+/**
+ * sps_flags2:
+ * bit 3, bitstream_restriction_flag
+ * bit 2, pic_struct_present_flag
+ * bit 1, vcl_hrd_parameters_present_flag
+ * bit 0, nal_hrd_parameters_present_flag
+ */
+#define SPS_FLAGS2 0x6C
+#define NUM_REORDER_FRAMES 0x6D
+#define MAX_BUFFER_FRAME 0X6E
+
+#define NON_CONFORMING_STREAM 0X70
+#define RECOVERY_POINT 0X71
+#define POST_CANVAS 0X72
+#define POST_CANVAS_H 0X73
+#define SKIP_PIC_COUNT 0X74
+#define TARGET_NUM_SCALING_LIST 0X75
+#define FF_POST_ONE_FRAME 0X76
+#define PREVIOUS_BIT_CNT 0X77
+#define MB_NOT_SHIFT_COUNT 0X78
+#define PIC_STATUS 0X79
+#define FRAME_COUNTER 0X7A
+#define NEW_SLICE_TYPE 0X7B
+#define NEW_PICTURE_STRUCTURE 0X7C
+#define NEW_FRAME_NUM 0X7D
+#define NEW_IDR_PIC_ID 0X7E
+#define IDR_PIC_ID 0X7F
+
+/* h264 LOCAL */
+#define NAL_UNIT_TYPE 0X80
+#define NAL_REF_IDC 0X81
+#define SLICE_TYPE 0X82
+#define LOG2_MAX_FRAME_NUM 0X83
+#define FRAME_MBS_ONLY_FLAG 0X84
+#define PIC_ORDER_CNT_TYPE 0X85
+#define LOG2_MAX_PIC_ORDER_CNT_LSB 0X86
+#define PIC_ORDER_PRESENT_FLAG 0X87
+#define REDUNDANT_PIC_CNT_PRESENT_FLAG 0X88
+#define PIC_INIT_QP_MINUS26 0X89
+#define DEBLOCKING_FILTER_CONTROL_PRESENT_FLAG 0X8A
+#define NUM_SLICE_GROUPS_MINUS1 0X8B
+#define MODE_8X8_FLAGS 0X8C
+#define ENTROPY_CODING_MODE_FLAG 0X8D
+#define SLICE_QUANT 0X8E
+#define TOTAL_MB_HEIGHT 0X8F
+#define PICTURE_STRUCTURE 0X90
+#define TOP_INTRA_TYPE 0X91
+#define RV_AI_STATUS 0X92
+#define AI_READ_START 0X93
+#define AI_WRITE_START 0X94
+#define AI_CUR_BUFFER 0X95
+#define AI_DMA_BUFFER 0X96
+#define AI_READ_OFFSET 0X97
+#define AI_WRITE_OFFSET 0X98
+#define AI_WRITE_OFFSET_SAVE 0X99
+#define RV_AI_BUFF_START 0X9A
+#define I_PIC_MB_COUNT 0X9B
+#define AI_WR_DCAC_DMA_CTRL 0X9C
+#define SLICE_MB_COUNT 0X9D
+#define PICTYPE 0X9E
+#define SLICE_GROUP_MAP_TYPE 0X9F
+#define MB_TYPE 0XA0
+#define MB_AFF_ADDED_DMA 0XA1
+#define PREVIOUS_MB_TYPE 0XA2
+#define WEIGHTED_PRED_FLAG 0XA3
+#define WEIGHTED_BIPRED_IDC 0XA4
+/* bit 3:2 - PICTURE_STRUCTURE
+ * bit 1 - MB_ADAPTIVE_FRAME_FIELD_FLAG
+ * bit 0 - FRAME_MBS_ONLY_FLAG
+ */
+#define MBFF_INFO 0XA5
+#define TOP_INTRA_TYPE_TOP 0XA6
+#define RV_AI_BUFF_INC 0xA7
+#define DEFAULT_MB_INFO_LO 0xA8
+/* 0 -- no need to read
+ * 1 -- need to wait Left
+ * 2 -- need to read Intra
+ * 3 -- need to read back MV
+ */
+#define NEED_READ_TOP_INFO 0xA9
+/* 0 -- idle
+ * 1 -- wait Left
+ * 2 -- reading top Intra
+ * 3 -- reading back MV
+ */
+#define READ_TOP_INFO_STATE 0xAA
+#define DCAC_MBX 0xAB
+#define TOP_MB_INFO_OFFSET 0xAC
+#define TOP_MB_INFO_RD_IDX 0xAD
+#define TOP_MB_INFO_WR_IDX 0xAE
+
+#define VLD_NO_WAIT 0
+#define VLD_WAIT_BUFFER 1
+#define VLD_WAIT_HOST 2
+#define VLD_WAIT_GAP 3
+
+#define VLD_WAITING 0xAF
+
+#define MB_X_NUM 0xB0
+#define MB_HEIGHT 0xB2
+#define MBX 0xB3
+#define TOTAL_MBY 0xB4
+#define INTR_MSK_SAVE 0xB5
+#define NEED_DISABLE_PPE 0xB6
+#define IS_NEW_PICTURE 0XB7
+#define PREV_NAL_REF_IDC 0XB8
+#define PREV_NAL_UNIT_TYPE 0XB9
+#define FRAME_MB_COUNT 0XBA
+#define REF_IDC_OVERRIDE_FLAG 0XBB
+#define SLICE_GROUP_CHANGE_RATE 0XBC
+#define SLICE_GROUP_CHANGE_CYCLE_LEN 0XBD
+#define DELAY_LENGTH 0XBE
+#define PICTURE_STRUCT 0XBF
+#define DCAC_PREVIOUS_MB_TYPE 0xC1
+
+#define TIME_STAMP 0XC2
+#define H_TIME_STAMP 0XC3
+#define VPTS_MAP_ADDR 0XC4
+#define H_VPTS_MAP_ADDR 0XC5
+#define PIC_INSERT_FLAG 0XC7
+#define TIME_STAMP_START 0XC8
+#define TIME_STAMP_END 0XDF
+#define OFFSET_FOR_NON_REF_PIC 0XE0
+#define OFFSET_FOR_TOP_TO_BOTTOM_FIELD 0XE2
+#define MAX_REFERENCE_FRAME_NUM 0XE4
+#define FRAME_NUM_GAP_ALLOWED 0XE5
+#define NUM_REF_FRAMES_IN_PIC_ORDER_CNT_CYCLE 0XE6
+#define PROFILE_IDC_MMCO 0XE7
+#define LEVEL_IDC_MMCO 0XE8
+#define FRAME_SIZE_IN_MB 0XE9
+#define DELTA_PIC_ORDER_ALWAYS_ZERO_FLAG 0XEA
+#define PPS_NUM_REF_IDX_L0_ACTIVE_MINUS1 0XEB
+#define PPS_NUM_REF_IDX_L1_ACTIVE_MINUS1 0XEC
+#define CURRENT_SPS_ID 0XED
+#define CURRENT_PPS_ID 0XEE
+/* bit 0 - sequence parameter set may change
+ * bit 1 - picture parameter set may change
+ * bit 2 - new dpb just inited
+ * bit 3 - IDR picture not decoded yet
+ * bit 5:4 - 0: mb level code loaded 1: picture
+ * level code loaded 2: slice level code loaded
+ */
+#define DECODE_STATUS 0XEF
+#define FIRST_MB_IN_SLICE 0XF0
+#define PREV_MB_WIDTH 0XF1
+#define PREV_FRAME_SIZE_IN_MB 0XF2
+/* bit 0 - aspect_ratio_info_present_flag
+ * bit 1 - timing_info_present_flag
+ * bit 2 - nal_hrd_parameters_present_flag
+ * bit 3 - vcl_hrd_parameters_present_flag
+ * bit 4 - pic_struct_present_flag
+ * bit 5 - bitstream_restriction_flag
+ */
+#define VUI_STATUS 0XF4
+#define ASPECT_RATIO_IDC 0XF5
+#define ASPECT_RATIO_SAR_WIDTH 0XF6
+#define ASPECT_RATIO_SAR_HEIGHT 0XF7
+#define NUM_UNITS_IN_TICK 0XF8
+#define TIME_SCALE 0XFA
+#define CURRENT_PIC_INFO 0XFC
+#define DPB_BUFFER_INFO 0XFD
+#define REFERENCE_POOL_INFO 0XFE
+#define REFERENCE_LIST_INFO 0XFF
+
+#define REORDER_CMD_MAX 66
+
+/* config parameters to DDR lmem */
+#define GET_SPS_PROFILE_IDC(x) (((x) & 0xff) << 8)
+#define GET_SPS_LEVEL_IDC(x) ((x) & 0xff)
+#define GET_SPS_SEQ_PARAM_SET_ID(x) (((x) & 0x1f) << 8)
+#define GET_SPS_CHROMA_FORMAT_IDC(x) ((x) << 8)
+#define GET_SPS_NUM_REF_FRAMES(x) ((x) & 0xff)
+#define GET_SPS_GAPS_ALLOWED_FLAG(x) ((x) << 8)
+#define GET_SPS_LOG2_MAX_FRAME_NUM(x) ((x) + 4)
+#define GET_SPS_PIC_ORDER_CNT_LSB(x) ((x) + 4)
+#define GET_SPS_PIC_ORDER_TYPE(x) (x)
+#define GET_SPS_OFFSET_FOR_NONREF_PIC_HIGH(x) (((x) & 0xffff0000) >> 16)
+#define GET_SPS_OFFSET_FOR_NONREF_PIC_LOW(x) ((x) & 0xffff)
+#define GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_HIGH(x) (((x) & 0xffff0000) >> 16)
+#define GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_LOW(x) ((x) & 0xffff)
+#define GET_SPS_PIC_WIDTH_IN_MBS(x) ((x) + 1)
+#define GET_SPS_PIC_HEIGHT_IN_MBS(x) ((x) + 1)
+#define GET_SPS_DIRECT_8X8_FLAGS(x) (((x) & 0x1) << 2)
+#define GET_SPS_MB_ADAPTIVE_FRAME_FIELD_FLAGS(x) (((x) & 0x1) << 1)
+#define GET_SPS_FRAME_MBS_ONLY_FLAGS(x) ((x) & 0x1)
+
+#define GET_PPS_PIC_PARAM_SET_ID(x) ((x) & 0xff)
+#define GET_PPS_SEQ_PARAM_SET_ID(x) (((x) & 0x1f) << 8)
+#define GET_PPS_ENTROPY_CODING_MODE_FLAG(x) (((x) & 0x1) << 13)
+#define GET_PPS_PIC_ORDER_PRESENT_FLAG(x) (((x) & 0x1) << 14)
+#define GET_PPS_NUM_IDX_REF_L0_MINUS1(x) ((x) & 0x1f)
+#define GET_PPS_NUM_IDX_REF_L1_MINUS1(x) (((x) & 0x1f) << 5)
+#define GET_PPS_WEIGHTED_PRED_FLAG(x) (((x) & 0x1) << 10)
+#define GET_PPS_WEIGHTED_BIPRED_IDC(x) (((x) & 0x3) << 11)
+#define GET_PPS_INIT_QS_MINUS26(x) (((x) & 0xff) << 8)
+#define GET_PPS_INIT_QP_MINUS26(x) ((x) & 0xff)
+#define GET_PPS_CHROMA_QP_INDEX_OFFSET(x) ((x) & 0xff)
+#define GET_PPS_DEBLOCK_FILTER_CTRL_PRESENT_FLAG(x) (((x) & 0x1) << 8)
+#define GET_PPS_CONSTRAIN_INTRA_PRED_FLAG(x) (((x) & 0x1) << 9)
+#define GET_PPS_REDUNDANT_PIC_CNT_PRESENT_FLAG(x) (((x) & 0x1) << 10)
+#define GET_PPS_SCALING_MATRIX_PRESENT_FLAG(x) (((x) & 0x1) << 1)
+#define GET_PPS_TRANSFORM_8X8_FLAG(x) ((x) & 0x1)
+#define GET_PPS_GET_SECOND_CHROMA_QP_OFFSET(x) (x)
+
+int aml_h264_init(void *priv);
+void aml_h264_exit(void *priv);
+int aml_h264_dec_run(void *priv);
+
+#endif
diff --git a/drivers/media/platform/amlogic/vdec/reg_defines.h b/drivers/media/platform/amlogic/vdec/reg_defines.h
new file mode 100644
index 000000000000..ea50018a078d
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/reg_defines.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */
+/*
+ * Copyright (C) 2025 Amlogic, Inc. All rights reserved
+ */
+
+#ifndef _REG_DEFINES_H_
+#define _REG_DEFINES_H_
+
+#define REG_ALIGN(x) ((x) << 2)
+
+#define VDEC_ASSIST_MMC_CTRL0 REG_ALIGN(0x0001)
+#define VDEC_ASSIST_MMC_CTRL1 REG_ALIGN(0x0002)
+
+#define VDEC_ASSIST_CANVAS_BLK32 REG_ALIGN(0x0005)
+
+#define VDEC_ASSIST_MBOX1_CLR_REG REG_ALIGN(0x0075)
+#define VDEC_ASSIST_MBOX1_MASK REG_ALIGN(0x0076)
+
+#define MPSR REG_ALIGN(0x0301)
+#define MPC_P REG_ALIGN(0x0306)
+#define MPC_D REG_ALIGN(0x0307)
+#define MPC_E REG_ALIGN(0x0308)
+#define MPC_W REG_ALIGN(0x0309)
+#define CPSR REG_ALIGN(0x0321)
+#define IMEM_DMA_CTRL REG_ALIGN(0x0340)
+#define IMEM_DMA_ADR REG_ALIGN(0x0341)
+#define IMEM_DMA_COUNT REG_ALIGN(0x0342)
+#define WRRSP_IMEM REG_ALIGN(0x0343)
+#define LMEM_DMA_CTRL REG_ALIGN(0x0350)
+#define WRRSP_LMEM REG_ALIGN(0x0353)
+
+#define PSCALE_CTRL REG_ALIGN(0x0911)
+#define GCLK_EN REG_ALIGN(0x0983)
+#define MDEC_PIC_DC_CTRL REG_ALIGN(0x098e)
+#define MDEC_PIC_DC_MUX_CTRL REG_ALIGN(0x098d)
+#define ANC0_CANVAS_ADDR REG_ALIGN(0x0990)
+#define ANC1_CANVAS_ADDR REG_ALIGN(0x0991)
+#define ANC2_CANVAS_ADDR REG_ALIGN(0x0992)
+#define ANC3_CANVAS_ADDR REG_ALIGN(0x0993)
+#define ANC4_CANVAS_ADDR REG_ALIGN(0x0994)
+#define ANC5_CANVAS_ADDR REG_ALIGN(0x0995)
+#define ANC6_CANVAS_ADDR REG_ALIGN(0x0996)
+#define ANC7_CANVAS_ADDR REG_ALIGN(0x0997)
+#define ANC8_CANVAS_ADDR REG_ALIGN(0x0998)
+#define ANC9_CANVAS_ADDR REG_ALIGN(0x0999)
+#define ANC10_CANVAS_ADDR REG_ALIGN(0x099a)
+#define ANC11_CANVAS_ADDR REG_ALIGN(0x099b)
+#define ANC12_CANVAS_ADDR REG_ALIGN(0x099c)
+#define ANC13_CANVAS_ADDR REG_ALIGN(0x099d)
+#define ANC14_CANVAS_ADDR REG_ALIGN(0x099e)
+#define ANC15_CANVAS_ADDR REG_ALIGN(0x099f)
+#define ANC16_CANVAS_ADDR REG_ALIGN(0x09a0)
+#define ANC17_CANVAS_ADDR REG_ALIGN(0x09a1)
+#define ANC18_CANVAS_ADDR REG_ALIGN(0x09a2)
+#define ANC19_CANVAS_ADDR REG_ALIGN(0x09a3)
+#define ANC20_CANVAS_ADDR REG_ALIGN(0x09a4)
+#define ANC21_CANVAS_ADDR REG_ALIGN(0x09a5)
+#define ANC22_CANVAS_ADDR REG_ALIGN(0x09a6)
+#define ANC23_CANVAS_ADDR REG_ALIGN(0x09a7)
+#define ANC24_CANVAS_ADDR REG_ALIGN(0x09a8)
+#define ANC25_CANVAS_ADDR REG_ALIGN(0x09a9)
+#define ANC26_CANVAS_ADDR REG_ALIGN(0x09aa)
+#define ANC27_CANVAS_ADDR REG_ALIGN(0x09ab)
+#define ANC28_CANVAS_ADDR REG_ALIGN(0x09ac)
+#define ANC29_CANVAS_ADDR REG_ALIGN(0x09ad)
+#define ANC30_CANVAS_ADDR REG_ALIGN(0x09ae)
+#define ANC31_CANVAS_ADDR REG_ALIGN(0x09af)
+#define DBKR_CANVAS_ADDR REG_ALIGN(0x09b0)
+#define DBKW_CANVAS_ADDR REG_ALIGN(0x09b1)
+#define REC_CANVAS_ADDR REG_ALIGN(0x09b2)
+#define CURR_CANVAS_CTRL REG_ALIGN(0x09b3)
+#define MDEC_PIC_DC_THRESH REG_ALIGN(0x09b8)
+#define AV_SCRATCH_0 REG_ALIGN(0x09c0)
+#define AV_SCRATCH_1 REG_ALIGN(0x09c1)
+#define AV_SCRATCH_2 REG_ALIGN(0x09c2)
+#define AV_SCRATCH_3 REG_ALIGN(0x09c3)
+#define AV_SCRATCH_4 REG_ALIGN(0x09c4)
+#define AV_SCRATCH_5 REG_ALIGN(0x09c5)
+#define AV_SCRATCH_6 REG_ALIGN(0x09c6)
+#define AV_SCRATCH_7 REG_ALIGN(0x09c7)
+#define AV_SCRATCH_8 REG_ALIGN(0x09c8)
+#define AV_SCRATCH_9 REG_ALIGN(0x09c9)
+#define AV_SCRATCH_A REG_ALIGN(0x09ca)
+#define AV_SCRATCH_B REG_ALIGN(0x09cb)
+#define AV_SCRATCH_C REG_ALIGN(0x09cc)
+#define AV_SCRATCH_D REG_ALIGN(0x09cd)
+#define AV_SCRATCH_E REG_ALIGN(0x09ce)
+#define AV_SCRATCH_F REG_ALIGN(0x09cf)
+#define AV_SCRATCH_G REG_ALIGN(0x09d0)
+#define AV_SCRATCH_H REG_ALIGN(0x09d1)
+#define AV_SCRATCH_I REG_ALIGN(0x09d2)
+#define AV_SCRATCH_J REG_ALIGN(0x09d3)
+#define AV_SCRATCH_K REG_ALIGN(0x09d4)
+#define AV_SCRATCH_L REG_ALIGN(0x09d5)
+#define AV_SCRATCH_M REG_ALIGN(0x09d6)
+#define AV_SCRATCH_N REG_ALIGN(0x09d7)
+#define WRRSP_VLD REG_ALIGN(0x09da)
+#define MDEC_DOUBLEW_CFG0 REG_ALIGN(0x09db)
+#define MDEC_DOUBLEW_CFG1 REG_ALIGN(0x09dc)
+#define MDEC_DOUBLEW_CFG2 REG_ALIGN(0x09dd)
+#define MDEC_DOUBLEW_CFG3 REG_ALIGN(0x09de)
+#define MDEC_DOUBLEW_CFG4 REG_ALIGN(0x09df)
+#define MDEC_DOUBLEW_CFG5 REG_ALIGN(0x09e0)
+#define MDEC_DOUBLEW_CFG6 REG_ALIGN(0x09e1)
+#define MDEC_DOUBLEW_CFG7 REG_ALIGN(0x09e2)
+#define MDEC_DOUBLEW_STATUS REG_ALIGN(0x09e3)
+#define MDEC_EXTIF_CFG0 REG_ALIGN(0x09e4)
+
+#define MDEC_EXTIF_CFG1 REG_ALIGN(0x09e5)
+#define MDEC_EXTIF_CFG2 REG_ALIGN(0x09e6)
+
+#define POWER_CTL_VLD REG_ALIGN(0x0c08)
+#define VLD_DECODE_CONTROL REG_ALIGN(0x0c18)
+
+#define PMV1_X REG_ALIGN(0x0c20)
+#define PMV1_Y REG_ALIGN(0x0c21)
+#define PMV2_X REG_ALIGN(0x0c22)
+#define PMV2_Y REG_ALIGN(0x0c23)
+#define PMV3_X REG_ALIGN(0x0c24)
+#define PMV3_Y REG_ALIGN(0x0c25)
+#define PMV4_X REG_ALIGN(0x0c26)
+#define PMV4_Y REG_ALIGN(0x0c27)
+#define M4_TABLE_SELECT REG_ALIGN(0x0c28)
+#define M4_CONTROL_REG REG_ALIGN(0x0c29)
+#define BLOCK_NUM REG_ALIGN(0x0c2a)
+#define PATTERN_CODE REG_ALIGN(0x0c2b)
+#define MB_INFO REG_ALIGN(0x0c2c)
+#define VLD_DC_PRED REG_ALIGN(0x0c2d)
+#define VLD_ERROR_MASK REG_ALIGN(0x0c2e)
+#define VLD_DC_PRED_C REG_ALIGN(0x0c2f)
+#define LAST_SLICE_MV_ADDR REG_ALIGN(0x0c30)
+#define LAST_MVX REG_ALIGN(0x0c31)
+#define LAST_MVY REG_ALIGN(0x0c32)
+
+#define MB_MOTION_MODE REG_ALIGN(0x0c07)
+#define VIFF_BIT_CNT REG_ALIGN(0x0c1a)
+#define M4_CONTROL_REG REG_ALIGN(0x0c29)
+#define VLD_C38 REG_ALIGN(0x0c38)
+#define VLD_C39 REG_ALIGN(0x0c39)
+#define VLD_SHIFT_STATUS REG_ALIGN(0x0c3b)
+#define VLD_C3D REG_ALIGN(0x0c3d)
+#define VLD_MEM_VIFIFO_START_PTR REG_ALIGN(0x0c40)
+#define VLD_MEM_VIFIFO_CURR_PTR REG_ALIGN(0x0c41)
+#define VLD_MEM_VIFIFO_END_PTR REG_ALIGN(0x0c42)
+#define VLD_MEM_VIFIFO_BYTES_AVAIL REG_ALIGN(0x0c43)
+#define VLD_MEM_VIFIFO_CONTROL REG_ALIGN(0x0c44)
+#define VLD_MEM_VIFIFO_WP REG_ALIGN(0x0c45)
+#define VLD_MEM_VIFIFO_RP REG_ALIGN(0x0c46)
+#define VLD_MEM_VIFIFO_LEVEL REG_ALIGN(0x0c47)
+#define VLD_MEM_VIFIFO_BUF_CNTL REG_ALIGN(0x0c48)
+
+#define VCOP_CTRL_REG REG_ALIGN(0x0e00)
+#define RV_AI_MB_COUNT REG_ALIGN(0x0e0c)
+#define IQIDCT_CONTROL REG_ALIGN(0x0e0e)
+#define DCAC_DDR_BYTE64_CTL REG_ALIGN(0x0e1d)
+
+#define VDEC2_IMEM_DMA_CTRL REG_ALIGN(0x2340)
+#define VDEC2_IMEM_DMA_ADR REG_ALIGN(0x2341)
+#define VDEC2_IMEM_DMA_COUNT REG_ALIGN(0x2342)
+
+#define DOS_SW_RESET0 REG_ALIGN(0x3f00)
+#define DOS_GCLK_EN0 REG_ALIGN(0x3f01)
+#define DOS_GCLK_EN1 REG_ALIGN(0x3f09)
+#define DOS_GCLK_EN3 REG_ALIGN(0x3f35)
+
+#define DOS_MEM_PD_VDEC REG_ALIGN(0x3f30)
+#define DOS_MEM_PD_VDEC2 REG_ALIGN(0x3f31)
+#define DOS_MEM_PD_HCODEC REG_ALIGN(0x3f32)
+/*add from M8M2*/
+#define DOS_MEM_PD_HEVC REG_ALIGN(0x3f33)
+
+#define DOS_SW_RESET3 REG_ALIGN(0x3f34)
+#define DOS_GCLK_EN3 REG_ALIGN(0x3f35)
+#define DOS_HEVC_INT_EN REG_ALIGN(0x3f36)
+
+#endif
+
--
2.42.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
` (3 preceding siblings ...)
2026-08-12 2:41 ` [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
@ 2026-08-12 2:41 ` Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
5 siblings, 0 replies; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
From: Zhentao Guo <zhentao.guo@amlogic.com>
Add vdec node to enable Amlogic V4L2 stateless video decoder
support. Since the video fw needs to be loaded via TEE helpers,
enable the optee node in this patch.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 35 +++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
index 2a6fbd530836..2262bc198de3 100644
--- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
@@ -80,6 +80,11 @@ pwrc: power-controller {
#power-domain-cells = <1>;
};
};
+
+ optee {
+ compatible = "linaro,optee-tz";
+ method = "smc";
+ };
};
saradc: adc@fe026000 {
@@ -907,5 +912,35 @@ emmc: mmc@fe08c000 {
assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_C>;
assigned-clock-rates = <24000000>;
};
+
+ canvas: video-lut@fe036048 {
+ compatible = "amlogic,canvas";
+ reg = <0x0 0xfe036048 0x0 0x14>;
+ };
+
+ video-codec@fe320000 {
+ compatible = "amlogic,s4-vdec";
+ reg = <0x0 0xfe320000 0x0 0x10000>,
+ <0x0 0xfe036000 0x0 0x20>;
+ reg-names = "dos",
+ "dmc";
+ interrupts = <GIC_SPI 91 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 92 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "hvdec",
+ "vdec";
+ clocks = <&clkc_periphs CLKID_DOS>,
+ <&clkc_periphs CLKID_VDEC_SEL>,
+ <&clkc_periphs CLKID_HEVCF_SEL>;
+ clock-names = "dos",
+ "vdec",
+ "hevcf";
+ power-domains = <&pwrc PWRC_S4_DOS_VDEC_ID>,
+ <&pwrc PWRC_S4_DOS_HEVC_ID>;
+ power-domain-names = "vdec",
+ "hvdec";
+ resets = <&reset RESET_DOS>;
+ amlogic,canvas = <&canvas>;
+ secure-monitor = <&sm>;
+ };
};
};
--
2.42.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
` (4 preceding siblings ...)
2026-08-12 2:41 ` [PATCH v7 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
@ 2026-08-12 2:41 ` Zhentao Guo via B4 Relay
5 siblings, 0 replies; 10+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-08-12 2:41 UTC (permalink / raw)
To: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-kernel, linux-amlogic, linux-kernel, linux-media,
devicetree, Zhentao Guo
From: Zhentao Guo <zhentao.guo@amlogic.com>
Enable the Amlogic V4L2 stateless video decoder driver as a module
in the arm64 defconfig to support stateless video decoder on Amlogic
SoCs. The driver is used by Amlogic S4 platform, such as S805X2
board.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 6ec8d3a69c98..7be4e5a284ba 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -919,6 +919,7 @@ CONFIG_V4L_PLATFORM_DRIVERS=y
CONFIG_SDR_PLATFORM_DRIVERS=y
CONFIG_V4L_MEM2MEM_DRIVERS=y
CONFIG_VIDEO_AMPHION_VPU=m
+CONFIG_VIDEO_AMLOGIC_VDEC=m
CONFIG_VIDEO_CADENCE_CSI2RX=m
CONFIG_VIDEO_WAVE_VPU=m
CONFIG_VIDEO_E5010_JPEG_ENC=m
--
2.42.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor
2026-08-12 2:41 ` [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
@ 2026-08-12 2:50 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-12 2:50 UTC (permalink / raw)
To: Zhentao Guo
Cc: robh, linux-amlogic, conor+dt, devicetree, neil.armstrong, media-ci
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The `SM_LOAD_VIDEO_FW` command is added to the enum but lacks the required SMC ID mapping in the secure monitor command table, making it entirely unusable.
Pre-existing issues:
- [High] Missing synchronization in `meson_sm_call_read` and `meson_sm_call_write` leads to shared memory corruption and race conditions.
- [High] Object lifetime mismanagement in `meson_sm_get` returns a dangling pointer.
--
--- Patch [1]: [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor ---
commit ee0d9a884be177be69f150b77b1f3503c328edfa
Author: Zhentao Guo <zhentao.guo@amlogic.com>
firmware: meson: sm: video firmware loading via secure monitor
Add SM_LOAD_VIDEO_FW to the secure monitor command enum to allow decoder
drivers to load firmware through the meson_sm interface.
> diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h
> index 3ebc2bd9a9760..3b90b49d05529 100644
> --- a/include/linux/firmware/meson/meson_sm.h
> +++ b/include/linux/firmware/meson/meson_sm.h
> @@ -15,6 +15,7 @@ enum {
> SM_THERMAL_CALIB_READ,
> SM_A1_PWRC_SET,
> SM_A1_PWRC_GET,
> + SM_LOAD_VIDEO_FW,
[Severity: High]
Does this new command also need an SMC ID mapping in the gxbb_chip command
table in drivers/firmware/meson/meson_sm.c?
If drivers attempt to call meson_sm_call() with SM_LOAD_VIDEO_FW, it looks
like meson_sm_get_cmd() might return 0 because the command is not mapped,
causing meson_sm_call() to return -EINVAL:
drivers/firmware/meson/meson_sm.c:meson_sm_call() {
...
cmd = meson_sm_get_cmd(fw->chip, cmd_index);
if (!cmd)
return -EINVAL;
...
}
[Severity: High]
This is a pre-existing issue, but will drivers loading video firmware
concurrently through this interface encounter data corruption?
Looking at drivers/firmware/meson/meson_sm.c, meson_sm_call_write() copies
data to the globally shared memory region and invokes the SMC without any
locking:
drivers/firmware/meson/meson_sm.c:meson_sm_call_write() {
...
memcpy(fw->sm_shmem_in_base, buffer, size);
if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, ...) < 0)
return -EINVAL;
...
}
A similar lack of synchronization exists in meson_sm_call_read(). Could
multiple kernel threads executing these functions clobber each other's data
mid-operation?
[Severity: High]
This is a pre-existing issue, but is there an object lifetime mismanagement
issue for drivers obtaining the meson_sm context to make these firmware
calls?
In drivers/firmware/meson/meson_sm.c, meson_sm_get() retrieves the fw
structure but drops the device reference before returning it:
drivers/firmware/meson/meson_sm.c:meson_sm_get() {
...
fw = platform_get_drvdata(pdev);
put_device(&pdev->dev);
return fw;
}
If the meson-sm device is unbound via sysfs, devres cleanup frees fw,
leaving consumer drivers with a dangling pointer that will be dereferenced
during subsequent operations. Does this require a fix?
> };
>
> struct meson_sm_firmware;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260812-b4-s4-vdec-upstream-v7-0-3fae085861bd@amlogic.com?part=1
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call
2026-08-12 2:41 ` [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
@ 2026-08-12 2:51 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-12 2:51 UTC (permalink / raw)
To: Zhentao Guo
Cc: linux-amlogic, conor+dt, robh, devicetree, media-ci, neil.armstrong
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing synchronization in `meson_sm_call_read` and `meson_sm_call_write` allows concurrent SMC calls to corrupt shared memory buffers.
--
--- Patch [2]: [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call ---
commit df496e889046fc128eabc52108d2f27967c682ad
Author: Zhentao Guo <zhentao.guo@amlogic.com>
firmware: meson: sm: Add video firmware loading SMC call
This commit adds the SM_LOAD_VIDEO_FW command to the command table for
loading video firmware.
> diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
> index ab9751a59b552..887caa5c625db 100644
> --- a/drivers/firmware/meson/meson_sm.c
> +++ b/drivers/firmware/meson/meson_sm.c
> @@ -48,6 +48,7 @@ static const struct meson_sm_chip gxbb_chip = {
> CMD(SM_THERMAL_CALIB_READ, 0x82000047),
> CMD(SM_A1_PWRC_SET, 0x82000093),
> CMD(SM_A1_PWRC_GET, 0x82000095),
> + CMD(SM_LOAD_VIDEO_FW, 0xb200000f),
[Severity: High]
This is a pre-existing issue, but does adding new firmware loading commands
increase the risk of concurrent shared memory corruption?
Looking at how meson_sm_call_read() and meson_sm_call_write() use the shared
buffers:
drivers/firmware/meson/meson_sm.c:meson_sm_call_read() {
...
if (meson_sm_call(fw, cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0)
return -EINVAL;
...
if (buffer)
memcpy(buffer, fw->sm_shmem_out_base, size);
...
}
and
drivers/firmware/meson/meson_sm.c:meson_sm_call_write() {
...
memcpy(fw->sm_shmem_in_base, buffer, size);
if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0)
return -EINVAL;
...
}
Since these buffers (sm_shmem_out_base and sm_shmem_in_base) are shared
across the system, if two threads call into this API concurrently (for
example, unprivileged userspace reading the serial sysfs attribute while
another process reads efuse via the nvmem framework or loads video
firmware), can they overwrite each other's data in the shared memory region
before the memcpy completes?
Is there a locking mechanism protecting these SMC calls and shared buffers
that I am missing, or do we need to add a mutex to serialize access to
meson_sm_call_read() and meson_sm_call_write()?
> { /* sentinel */ },
> },
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260812-b4-s4-vdec-upstream-v7-0-3fae085861bd@amlogic.com?part=2
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver
2026-08-12 2:41 ` [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
@ 2026-08-12 3:04 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-08-12 3:04 UTC (permalink / raw)
To: Zhentao Guo
Cc: devicetree, robh, neil.armstrong, conor+dt, linux-amlogic, media-ci
Thank you for your contribution! Sashiko AI review found 22 potential issue(s) to consider:
- [Critical] Unvalidated SPS dimensions cause a 32-bit integer overflow, resulting in an undersized CMA buffer and massive out-of-bounds DMA writes.
- [Critical] Array bounds writes during reference list reordering allows massive heap corruption.
- [Critical] Trusting V4L2 PPS control fields allows a heap overflow via `clear_unused_col_buf`.
- [Critical] Dynamic resolution changes bypass DMA buffer reallocation, causing the hardware to overflow the colocated CMA buffer.
- [High] Use-after-free of V4L2 M2M context and driver resources due to incorrect teardown order and devres usage in `aml_vdec_drv_remove`.
- [High] TOCTOU race in `vdec_threaded_isr_handler` leads to NULL pointer dereference of `curr_ctx` and `v4l2_m2m_next_dst_buf`.
- [High] Error path in `get_firmware` incorrectly returns 0 on undersized firmware, passing uninitialized stack memory to the TEE API.
- [High] Hardware VIFIFO Write Pointer is programmed with a physical offset beyond the allocated V4L2 buffer bounds, causing out-of-bounds DMA reads.
- [High] Improper error handling of `of_parse_phandle` allows `sec_fw` to remain NULL, causing a kernel panic.
- [High] Sleeping in `device_run` stalls the shared V4L2 M2M worker thread.
- [High] Double free of the video device object `vfd_dec` on error paths in `aml_vdec_drv_probe`.
- [High] Missing reset of `ctx->int_cond` causes subsequent hardware decode waits to complete prematurely.
- [High] `fops_vcodec_release` frees the control handler before releasing the M2M context, causing active jobs to access freed controls.
- [High] Failing to validate an unallocated Canvas POC allows an out-of-bounds MMIO register write, letting an attacker tamper with adjacent device registers.
- [High] Data race between hardware wait timeout/signal handling and the threaded IRQ handler.
- [High] The hard IRQ handler accesses regmap over MMIO without `.use_raw_spinlock = true`, triggering a 'sleeping in atomic context' BUG on PREEMPT_RT kernels.
- [Medium] Overwriting `meson_canvas_alloc` return values bypasses error checking, leading to canvas corruption and double-frees.
- [Medium] The driver ignores `num_ref_idx_l1_active_minus1` overrides in B-slices, violating the H.264 specification.
- [Medium] Hardware power domains and clocks are unconditionally enabled permanently during probe.
- [Medium] Power domains and device links are leaked if hardware resource initialization fails.
- [Medium] The `v4l2_ctrl_handler` memory is leaked if `v4l2_ctrl_handler_setup` fails.
- [Low] Kconfig unconditionally selects `MESON_CANVAS`, risking unmet direct dependency warnings under `COMPILE_TEST`.
--
--- Patch [4]: [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver ---
commit b11f2b7369a2bd8b856bd77ca3082b77fefa66fe
Author: Zhentao Guo <zhentao.guo@amlogic.com>
Subject: decoder: Add V4L2 stateless H.264 decoder driver
This commit adds the initial support for the V4L2 stateless video decoder
driver on the Amlogic S4 platform. It currently supports 8-bit progressive
H.264 bitstream decoding.
> diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR MIT)
> +
> +config VIDEO_AMLOGIC_VDEC
> + tristate "Amlogic Video Decoder Driver"
> + depends on ARCH_MESON || COMPILE_TEST
> + depends on VIDEO_DEV
> + depends on V4L_MEM2MEM_DRIVERS
> + depends on TEE
> + depends on MESON_SM
> + select VIDEOBUF2_DMA_CONTIG
> + select V4L2_H264
> + select V4L2_MEM2MEM_DEV
> + select MESON_CANVAS
[Severity: Low]
Does selecting MESON_CANVAS unconditionally here risk unmet direct
dependency warnings under COMPILE_TEST? Since VIDEO_AMLOGIC_VDEC supports
COMPILE_TEST, it could be built on architectures where MESON_CANVAS
dependencies are not met.
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
> @@ -0,0 +1,231 @@
[ ... ]
> +static int fops_vcodec_open(struct file *file)
> +{
[ ... ]
> + ret = aml_vdec_ctrls_setup(ctx);
> + if (ret) {
> + v4l2_err(&dec_dev->v4l2_dev, "Failed to init all ctrls (%d)", ret);
> + goto err_ctrls_setup;
> + }
> +
> + aml_vdec_reset_fmts(ctx);
> + mutex_unlock(&dec_dev->dev_mutex);
> +
> + return ret;
> +
> +err_ctrls_setup:
> + v4l2_m2m_ctx_release(ctx->m2m_ctx);
> +err_m2m_ctx_init:
> + v4l2_fh_del(&ctx->fh, file);
> + v4l2_fh_exit(&ctx->fh);
> + kfree(ctx);
> + mutex_unlock(&dec_dev->dev_mutex);
> +
> + return ret;
> +}
[Severity: Medium]
Is there a missing call to v4l2_ctrl_handler_free in the err_ctrls_setup
path in fops_vcodec_open? If v4l2_ctrl_handler_setup fails during
aml_vdec_ctrls_setup, it returns an error but does not free the handler.
This error path also skips cleaning it up, which looks like it would
permanently leak the handler's internal allocations.
[ ... ]
> +static int fops_vcodec_release(struct file *file)
> +{
> + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
> +
> + v4l2_ctrl_handler_free(&ctx->ctrl_handler);
> + v4l2_m2m_ctx_release(ctx->m2m_ctx);
> + v4l2_fh_del(&ctx->fh, file);
> + v4l2_fh_exit(&ctx->fh);
> + kfree(ctx);
> +
> + return 0;
> +}
[Severity: High]
Could freeing the control handler before releasing the M2M context cause
a use-after-free in fops_vcodec_release?
The M2M context release performs a blocking wait to cancel active jobs.
If the worker thread is still executing or sleeping in aml_h264_dec_run,
it will eventually finish and call v4l2_ctrl_request_complete. This
would access the poisoned control lists since v4l2_ctrl_handler_free
has already destroyed them.
[ ... ]
> +static int aml_vdec_drv_probe(struct platform_device *pdev)
> +{
> + struct aml_vdec_dev *dev;
> + struct video_device *vfd_dec;
> + struct aml_vdec_hw *hw;
> + int ret = 0;
> +
> + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
[Severity: High]
Should the aml_vdec_dev structure's lifetime be tied to the V4L2 object
instead of the device binding in aml_vdec_drv_probe?
By using devres, the structure is freed when the device unbinds. However,
userspace could still hold an open file descriptor to the video device,
allowing subsequent ioctls or close operations to access the freed
m2m_dev_dec and dev_mutex via the dangling ctx->dev pointer.
[ ... ]
> + ret = media_device_register(&dev->mdev);
> + if (ret) {
> + v4l2_err(&dev->v4l2_dev, "Failed to register media device");
> + goto err_media_dev_register;
> + }
> + vdec_enable(dev->dec_hw);
> + return 0;
[Severity: Medium]
Does aml_vdec_drv_probe keep the hardware power domain and clocks
permanently enabled while the driver is loaded?
Calling vdec_enable here synchronously powers on the domain and enables
the clocks indefinitely. It might be better to integrate this with
Runtime PM so the hardware can power down when not actively decoding.
[ ... ]
> +err_media_dev_register:
> + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
> +error_m2m_mc_register:
> + media_device_cleanup(&dev->mdev);
> + video_unregister_device(dev->vfd);
> +err_vid_dev_register:
> + v4l2_m2m_release(dev->m2m_dev_dec);
> +err_m2m_dev_init:
> + dev->pvdec_data->destroy_hw_resource(dev);
> +err_hw_init:
> + dev->dec_hw = NULL;
> +err_dec_mem_init:
> + video_device_release(vfd_dec);
> +err_device_alloc:
> + v4l2_device_unregister(&dev->v4l2_dev);
> + return ret;
> +}
[Severity: High]
Can the err_dec_mem_init path in aml_vdec_drv_probe cause a double free
of the video device object?
If an error occurs after successful video device registration, the code
falls through error_m2m_mc_register and calls video_unregister_device,
which drops the refcount and invokes the release callback to kfree vfd_dec.
Continuing to fall through to err_dec_mem_init then calls
video_device_release(vfd_dec), which would kfree the exact same pointer.
[ ... ]
> +static void aml_vdec_drv_remove(struct platform_device *pdev)
> +{
> + struct aml_vdec_dev *dev = platform_get_drvdata(pdev);
> +
> + vdec_disable(dev->dec_hw);
> +
> + media_device_unregister(&dev->mdev);
> + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
> + media_device_cleanup(&dev->mdev);
> +
> + v4l2_m2m_release(dev->m2m_dev_dec);
> + video_unregister_device(dev->vfd);
> + dev->pvdec_data->destroy_hw_resource(dev);
> + v4l2_device_unregister(&dev->v4l2_dev);
> +}
[Severity: High]
Does releasing the M2M context before unregistering the video device
in aml_vdec_drv_remove create a race condition?
If a userspace application is concurrently calling ioctls while the driver
is being removed, they might access the freed M2M context before the
video device is fully torn down by video_unregister_device.
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
[ ... ]
> +static irqreturn_t vdec_threaded_isr_handler(int irq, void *priv)
> +{
> + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
> + struct aml_vdec_hw *hw = dev->dec_hw;
> + irqreturn_t ret = IRQ_HANDLED;
> +
> + if (hw->hw_ops.irq_threaded_func && hw->curr_ctx)
> + ret = hw->hw_ops.irq_threaded_func(irq, priv);
> +
> + return ret;
> +}
[Severity: High]
Is it safe to check hw->curr_ctx in vdec_threaded_isr_handler without
holding any locks?
Concurrently, aml_h264_dec_run can set hw->curr_ctx to NULL upon a timeout
or signal. If the context is cleared immediately after this check passes,
the threaded ISR would dereference a NULL pointer.
[ ... ]
> +static const struct regmap_config dos_regmap_conf = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> + .max_register = 0x10000,
> +};
[Severity: High]
Should the dos_regmap_conf configuration include .use_raw_spinlock = true?
Without it, the regmap uses a standard spinlock, which becomes a
sleepable mutex on PREEMPT_RT kernels. Since h264_isr calls regmap_write
from the primary hard IRQ handler context, taking a sleepable lock here
will cause a sleeping in atomic context BUG on RT.
[ ... ]
> +int dev_request_hw_resources(void *priv)
> +{
[ ... ]
> + if (hw->pm->init) {
> + ret = hw->pm->init(hw);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "power mgr init failed!\n");
> + return ret;
> + }
> + }
> +
> + ret = vdec_clock_gate_init(hw);
> + if (ret) {
> + dev_err(&pdev->dev, "clk bulk init failed!\n");
> + return ret;
> + }
[Severity: Medium]
If vdec_clock_gate_init or aml_tee_fw_preload fails in
dev_request_hw_resources, we return early. Should we call
hw->pm->release(hw) in the error path to tear down the PM domain we just
successfully attached, to avoid permanently leaking it?
[ ... ]
> + sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0);
> + if (IS_ERR_OR_NULL(sm_np))
> + return PTR_ERR(sm_np);
[Severity: High]
Does IS_ERR_OR_NULL mask the error when of_parse_phandle fails in
dev_request_hw_resources?
of_parse_phandle returns NULL on failure. PTR_ERR(NULL) evaluates to 0,
so dev_request_hw_resources would silently succeed while leaving
hw->sec_fw uninitialized. Later, load_firmware would pass this NULL
pointer to meson_sm_call, causing a kernel panic.
> diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c
[ ... ]
> +static int get_firmware(const char *path, void **data, size_t *size)
> +{
> + const struct firmware *fw = NULL;
> + int ret;
> + void *buf;
> +
> + ret = request_firmware(&fw, FIRMWARE_PATH, NULL);
> + if (ret || fw->size <= UCODE_HEADER_SIZE)
> + return ret;
[Severity: High]
If the firmware size is less than or equal to UCODE_HEADER_SIZE in
get_firmware, this returns 0 (since ret is 0 here) but doesn't initialize
the data or size output pointers.
This causes the caller, pass_firmware_to_tee, to believe it succeeded
and invoke the TEE API with uninitialized stack memory, eventually
calling kfree on an uninitialized pointer.
> diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/h264.c
[ ... ]
> +static int h264_prepare_input(struct aml_vdec_ctx *ctx)
> +{
[ ... ]
> + dummy = payload_size + VLD_PADDING_SIZE;
> + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_WP,
> + round_down((src_dma + dummy), VDEC_FIFO_ALIGN));
[Severity: High]
Could the dummy calculation in h264_prepare_input cause out-of-bounds
DMA reads?
We program the hardware write pointer to
src_dma + payload_size + VLD_PADDING_SIZE. If userspace sets sizeimage
equal to payload_size, this physical offset points beyond the memory
allocated for the CMA buffer.
[ ... ]
> +static void config_sps_params(struct aml_h264_ctx *h264_ctx,
> + unsigned short *sps_base,
> + const struct v4l2_ctrl_h264_sps *sps)
> +{
[ ... ]
> + h264_ctx->mb_width = (sps->pic_width_in_mbs_minus1 + 4) & 0xfffffffc;
> + h264_ctx->mb_height = (sps->pic_height_in_map_units_minus1 + 4) & 0xfffffffc;
> +
> + h264_ctx->mb_total = h264_ctx->mb_width * h264_ctx->mb_height;
[Severity: Critical]
Are the mb_width and mb_height dimensions validated against maximum
limits in config_sps_params?
Userspace can provide arbitrary values for the width and height.
Extremely large values cause mb_total * 96 in alloc_colocate_cma to
exceed 32 bits, truncating to a small allocation size. The hardware
would then write colocated motion vectors well past the allocated buffer
bounds.
[ ... ]
> +static void config_decode_canvas(struct aml_vdec_hw *hw,
> + struct h264_decode_buf_spec *buf_spec,
> + u32 mb_width, u32 mb_height)
> +{
> + int canvas_alloc_result = 0;
> + int blkmode = 0x0;
> +
> + canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->y_canvas_index);
> + canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->u_canvas_index);
[Severity: Medium]
Does config_decode_canvas clobber the first canvas_alloc_result without
checking it?
If the Y canvas allocation fails but the U canvas allocation succeeds,
the error is masked. The uninitialized Y canvas index will be programmed
into hardware and eventually passed to meson_canvas_free, potentially
causing a double-free.
[ ... ]
> +static int alloc_colocate_cma(struct aml_h264_ctx *h264_ctx,
> + struct aml_vdec_ctx *ctx)
> +{
> + int alloc_size = 0;
> + int i;
> + struct aml_vdec_hw *hw;
> + gfp_t gfp = GFP_KERNEL | GFP_DMA32;
> +
> + if (h264_ctx->collated_cma_vaddr)
> + return 0;
[Severity: Critical]
If userspace provides a new SPS mid-stream that increases the resolution,
does alloc_colocate_cma bypass reallocating the CMA buffer?
If we return early here, one_col_buf_size is not updated for the new
dimensions. This causes the hardware to silently overflow the existing
smaller buffer when writing motion vectors.
[ ... ]
> +static void release_colocate_buf(struct aml_h264_ctx *h264_ctx, int index)
> +{
[ ... ]
> + } else {
> + if (h264_ctx->colocated_buf_poc[index] != INVALID_POC &&
> + ((h264_ctx->colocated_buf_map >> index) & 0x1) == 0x1) {
> + h264_ctx->colocated_buf_map &= (~(1 << index));
> + dev_dbg
> + (&ctx->dev->plat_dev->dev,
> + "%s colocated_buf_index %d released poc %d\n",
> + __func__, index,
> + h264_ctx->colocated_buf_poc[index]);
> + }
> + h264_ctx->colocated_buf_poc[index] = INVALID_POC;
> + }
> + }
> +}
[ ... ]
> +static void clear_unused_col_buf(struct aml_h264_ctx *h264_ctx,
> + struct v4l2_ctrl_h264_decode_params *decode)
> +{
[ ... ]
> + for (i = 0; i < h264_ctx->colocated_buf_num; i++) {
> + col_poc = h264_ctx->colocated_buf_poc[i];
> + if (col_poc != INVALID_POC &&
> + (poc_is_in_dpb(col_poc, decode->dpb) != 1))
> + release_colocate_buf(h264_ctx, i);
> + }
[Severity: Critical]
Is colocated_buf_num fully controlled by the PPS default active minus1
in clear_unused_col_buf?
If userspace inflates this value beyond H264_MAX_COL_BUF (32), this loop
will iterate out of bounds, and release_colocate_buf will write
INVALID_POC (0xFFFFFFFF) beyond the end of the colocated_buf_poc array,
causing memory corruption in the aml_h264_ctx struct.
[ ... ]
> +static void h264_config_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx)
> +{
[ ... ]
> + buf_spec_l0 = find_spec_by_dpb_index(h264_ctx, i, 0);
> + if (buf_spec_l0) {
> + buf_spec_l0->canvas_pos =
> + get_canvas_pos_by_poc(h264_ctx,
> + dpb->top_field_order_cnt);
> + if (buf_spec_l0->canvas_pos < 0) {
> + dev_err(&ctx->dev->plat_dev->dev,
[Severity: High]
Is canvas_pos an unsigned integer in h264_config_decode_spec?
If get_canvas_pos_by_poc returns -1 for an unallocated canvas, the < 0
check will fail because of integer promotion. The 0xFFFFFFFF value will
then be used in config_decode_canvas to calculate an out-of-bounds MMIO
offset (ANC0_CANVAS_ADDR - 4) and overwrite adjacent hardware registers.
[ ... ]
> +static void reorder_short_term(struct slice *curr_slice, int cur_list,
> + int pic_num_lx, int *ref_idx_lx)
> +{
[ ... ]
> + num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list);
> + if (num_ref_idx_lx_active > (V4L2_H264_NUM_DPB_ENTRIES + 1)) {
> + dev_dbg(&ctx->dev->plat_dev->dev, "incorrect value st num_ref_idx_lx_active %d\n",
> + num_ref_idx_lx_active);
> + return;
> + }
[Severity: Critical]
Does the off-by-one check in reorder_short_term allow an array bounds write?
V4L2_H264_NUM_DPB_ENTRIES is 16, so the check allows num_ref_idx_lx_active
to be 17. The loop below will then access and write to index 17 of the
ref_list array, which only holds 17 items (0 to 16). Additionally,
reorder_long_term appears to completely lack this bounds check, allowing
unbounded writes past the array based on PPS values.
[ ... ]
> +static int get_ref_list_size(struct aml_h264_ctx *h264_ctx, int cur_list)
> +{
[ ... ]
> + if (cur_list == 0) {
> + num_ref_idx_lx_active_minus1 =
> + h264_ctx->ctrl_ref.pps->num_ref_idx_l0_default_active_minus1;
> + if (override_flag)
> + num_ref_idx_lx_active_minus1 =
> + h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1;
> + } else {
> + num_ref_idx_lx_active_minus1 =
> + h264_ctx->ctrl_ref.pps->num_ref_idx_l1_default_active_minus1;
> + }
[Severity: Medium]
Does get_ref_list_size ignore the override_flag for the L1 reference list?
According to the H.264 specification, num_ref_idx_active_override_flag
applies to both L0 and L1 lists in B-slices. Ignoring it here could cause
decoding issues for valid streams that override both sizes.
[ ... ]
> +static irqreturn_t h264_threaded_isr_func(int irq, void *priv)
> +{
> + u32 dec_status;
> + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv;
> + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)dev->dec_hw->curr_ctx;
> + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx;
[Severity: High]
If hw->curr_ctx is cleared concurrently as noticed earlier in
vdec_threaded_isr_handler, doesn't h264_threaded_isr_func dereference
the NULL context when casting and reading h264_ctx->v4l2_ctx?
Additionally, if the context is valid but the queue is empty during a
spurious interrupt, v4l2_m2m_next_dst_buf further down will return NULL
and crash.
[ ... ]
> +int aml_h264_dec_run(void *priv)
> +{
[ ... ]
> + ret = wait_event_interruptible_timeout(ctx->queue, ctx->int_cond,
> + msecs_to_jiffies(DECODER_TIMEOUT_MS));
> + ctx->int_cond = 0;
[Severity: High]
Is it safe to perform a blocking wait inside the non-blocking device_run
callback in aml_h264_dec_run?
The V4L2 M2M architecture expects device_run to initiate hardware
processing and return immediately. Sleeping here forces the shared M2M
worker thread to block, stalling all concurrent decoding instances.
Also, should ctx->int_cond be reset to 0 before initiating the job,
rather than after the wait? If a delayed hardware interrupt fires after
this reset, the next decode run will observe ctx->int_cond == 1 and
return instantly.
[ ... ]
> + if (!ret) {
> + ret = -1;
> + dev_err(&ctx->dev->plat_dev->dev, "dec timeout=%u\n", DECODER_TIMEOUT_MS);
> + for (i = 0; i < 16; i++) { /* 16 : show ucode PC 16 times when timeout */
> + dev_dbg(&ctx->dev->plat_dev->dev, "decoder timeout, pc 0x%x\n",
> + read_dos_reg(dec_hw, MPC_E));
> + usleep_range(10, 20);
> + }
> + h264_release_decode_spec(dec_hw, ctx);
> + } else if (-ERESTARTSYS == ret) {
> + ret = -1;
> + h264_release_decode_spec(dec_hw, ctx);
> + }
[Severity: High]
Does the timeout or signal handling in aml_h264_dec_run race with the
threaded IRQ handler?
If the wait is interrupted or times out, this directly executes
h264_release_decode_spec. If the delayed hardware interrupt fires
concurrently, h264_threaded_isr_func will also unconditionally call
h264_release_decode_spec. Without a lock, this risks double-frees of
the canvas IDs and structural corruption.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260812-b4-s4-vdec-upstream-v7-0-3fae085861bd@amlogic.com?part=4
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-08-12 3:04 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-12 2:41 [PATCH v7 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 1/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
2026-08-12 2:50 ` sashiko-bot
2026-08-12 2:41 ` [PATCH v7 2/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
2026-08-12 2:51 ` sashiko-bot
2026-08-12 2:41 ` [PATCH v7 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
2026-08-12 3:04 ` sashiko-bot
2026-08-12 2:41 ` [PATCH v7 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
2026-08-12 2:41 ` [PATCH v7 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®