* [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4
@ 2026-05-26 8:40 Zhentao Guo via B4 Relay
2026-05-26 8:40 ` [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
` (5 more replies)
0 siblings, 6 replies; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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 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: Add video firmware loading SMC call
firmware: meson: sm: video firmware loading via secure monitor
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 | 103 +
MAINTAINERS | 7 +
arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 34 +
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 | 736 +++++++
drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 +
drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 239 +++
drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 172 ++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 538 +++++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 159 ++
.../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 | 2128 ++++++++++++++++++++
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, 5053 insertions(+)
---
base-commit: d387b06f7c15b4639244ad66b4b0900c6a02b430
change-id: 20251027-b4-s4-vdec-upstream-0603c1a4c84a
Best regards,
--
Zhentao Guo <zhentao.guo@amlogic.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
@ 2026-05-26 8:40 ` Zhentao Guo via B4 Relay
2026-05-26 9:01 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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 3ab67aaa9e5d..5da6c65d684a 100644
--- a/drivers/firmware/meson/meson_sm.c
+++ b/drivers/firmware/meson/meson_sm.c
@@ -47,6 +47,7 @@ static const struct meson_sm_chip gxbb_chip = {
CMD(SM_GET_CHIP_ID, 0x82000044),
CMD(SM_A1_PWRC_SET, 0x82000093),
CMD(SM_A1_PWRC_GET, 0x82000095),
+ CMD(SM_LOAD_VIDEO_FW, 0xb200000f),
{ /* sentinel */ },
},
};
--
2.42.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-05-26 8:40 ` [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
@ 2026-05-26 8:40 ` Zhentao Guo via B4 Relay
2026-05-26 9:18 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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 8eaf8922ab02..f40867a000f1 100644
--- a/include/linux/firmware/meson/meson_sm.h
+++ b/include/linux/firmware/meson/meson_sm.h
@@ -14,6 +14,7 @@ enum {
SM_GET_CHIP_ID,
SM_A1_PWRC_SET,
SM_A1_PWRC_GET,
+ SM_LOAD_VIDEO_FW,
};
struct meson_sm_firmware;
--
2.42.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-05-26 8:40 ` [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
2026-05-26 8:40 ` [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
@ 2026-05-26 8:40 ` Zhentao Guo via B4 Relay
2026-05-26 9:29 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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 | 103 +++++++++++++++++++++
1 file changed, 103 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..a0f33f6c35a1
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml
@@ -0,0 +1,103 @@
+# 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: 3
+
+ clocks:
+ maxItems: 3
+
+ clock-names:
+ items:
+ - const: dos
+ - const: vdec
+ - const: hevcf
+
+ power-domains:
+ maxItems: 2
+
+ power-domain-names:
+ items:
+ - const: vdec
+ - const: hevc
+
+ 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
+ - clocks
+ - clock-names
+ - power-domains
+ - power-domain-names
+ - 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>;
+ amlogic,canvas = <&canvas>;
+ reg-names = "dos",
+ "dmc";
+ interrupts = <GIC_SPI 91 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 92 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 93 IRQ_TYPE_EDGE_RISING>;
+ 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",
+ "hevc";
+ resets = <&reset RESET_DOS>;
+ secure-monitor = <&sm>;
+ };
--
2.42.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
` (2 preceding siblings ...)
2026-05-26 8:40 ` [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
@ 2026-05-26 8:40 ` Zhentao Guo via B4 Relay
2026-05-26 10:04 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
2026-05-26 8:40 ` [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
5 siblings, 1 reply; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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 | 736 +++++++
drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 +
drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 239 +++
drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 172 ++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 538 +++++
drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 159 ++
.../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 | 2128 ++++++++++++++++++++
drivers/media/platform/amlogic/vdec/h264.h | 299 +++
drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++
19 files changed, 4913 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index fe28ba288999..8d35821ff4c3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1406,6 +1406,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..d392967c7743
--- /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
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_H264
+ select V4L2_MEM2MEM_DEV
+ select MESON_CANVAS
+ select MESON_SM
+ 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..d4dcd0180d2d
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c
@@ -0,0 +1,736 @@
+// 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 = 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;
+
+ 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)
+ goto err_cancel_job;
+
+ 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;
+ ctx->pic_info.plane_num = 1;
+ }
+}
+
+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)
+{
+ struct aml_video_fmt *dec_fmt;
+ int i, align;
+
+ if (V4L2_TYPE_IS_OUTPUT(type))
+ dec_fmt = &ctx->dec_fmt[AML_FMT_SRC];
+ else
+ dec_fmt = &ctx->dec_fmt[AML_FMT_DST];
+
+ pix_mp->field = V4L2_FIELD_NONE;
+
+ 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;
+ }
+
+ align = ctx->dev->pvdec_data->dec_fmt->align;
+ pix_mp->height = ALIGN(pix_mp->height, align);
+ pix_mp->width = ALIGN(pix_mp->width, align);
+
+ 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;
+ const struct aml_video_fmt *out_fmt;
+ struct vb2_queue *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;
+
+ out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat);
+ if (out_fmt)
+ ctx->dec_fmt[AML_FMT_SRC] = *out_fmt;
+ else
+ 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);
+ set_pic_info(ctx, pix_mp, f->type);
+
+ ctx->pix_fmt[AML_FMT_SRC] = *pix_mp;
+ ctx->pix_fmt[AML_FMT_DST] = *pix_mp;
+
+ 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
+ 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);
+
+ ctx->pix_fmt[AML_FMT_DST] = *pix_mp;
+
+ 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);
+
+ 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);
+
+ 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)
+{
+ return vdec_try_fmt_mp(fh_to_dec_ctx(file), f->type, &f->fmt.pix_mp);
+}
+
+static int vidioc_try_fmt_out_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ return vdec_try_fmt_mp(fh_to_dec_ctx(file), f->type, &f->fmt.pix_mp);
+}
+
+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)) {
+ ctx->is_output_streamon = 1;
+ if (aml_vdec_init_dec_inst(ctx) < 0)
+ 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;
+
+ aml_vdec_release_instance(ctx);
+
+ 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;
+ }
+}
+
+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..d63cbd4f9e26
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
@@ -0,0 +1,239 @@
+// 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);
+ dec_dev->dec_ctx = ctx;
+ ctx->dev = dec_dev;
+ v4l2_fh_init(&ctx->fh, video_devdata(file));
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh, file);
+ dec_dev->filp = 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_dev *dec_dev = video_drvdata(file);
+ struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file);
+
+ mutex_lock(&dec_dev->dev_mutex);
+ 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);
+ mutex_unlock(&dec_dev->dev_mutex);
+
+ 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_hw_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);
+err_vid_dev_register:
+ v4l2_m2m_release(dev->m2m_dev_dec);
+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);
+
+ if (media_devnode_is_registered(dev->mdev.devnode)) {
+ media_device_unregister(&dev->mdev);
+ media_device_cleanup(&dev->mdev);
+ }
+
+ if (dev->m2m_dev_dec)
+ v4l2_m2m_release(dev->m2m_dev_dec);
+ if (dev->vfd)
+ video_unregister_device(dev->vfd);
+ if (dev->dec_hw) {
+ dev->pvdec_data->destroy_hw_resource(dev);
+ dev->dec_hw = NULL;
+ }
+ 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..fa86233ea5b3
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h
@@ -0,0 +1,172 @@
+/* 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_ctx: Decoder context. See struct aml_vdec_ctx.
+ * @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.
+ * @filp: v4l2 file handle pointer.
+ */
+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_ctx *dec_ctx;
+ struct aml_vdec_hw *dec_hw;
+ const struct aml_dev_platform_data *pvdec_data;
+
+ struct mutex dev_mutex;
+ struct file *filp;
+};
+
+/**
+ * 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];
+ u32 plane_num;
+};
+
+/**
+ * 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;
+
+ 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..79ba2a68bde4
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
@@ -0,0 +1,538 @@
+// 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_1] = {.name = "vdec", },
+ [VDEC_HEVC] = {.name = "hevc", },
+};
+
+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, 800000, 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_HEVCF].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 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);
+ 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);
+ return -ENODEV;
+ }
+
+ dev_dbg(hw->dev, "power domain: name: %s, dev: %p, link: %p\n",
+ pd[i].name, pd[i].dev, pd[i].link);
+ }
+
+ return 0;
+}
+
+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 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_1:
+ 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 VDEC_HEVC:
+ 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_1:
+ 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 VDEC_HEVC:
+ 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_1)
+ gate_node = vdec_get_clk_by_name(hw, "vdec");
+ else if (id == VDEC_HEVC)
+ gate_node = vdec_get_clk_by_name(hw, "hevcf");
+
+ if (gate_node) {
+ clk_prepare_enable(gate_node->clk);
+ if (id == VDEC_1) {
+ 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_1)
+ gate_node = vdec_get_clk_by_name(hw, "vdec");
+ else if (id == VDEC_HEVC)
+ 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 bool pm_vdec_power_domain_power_state(struct aml_vdec_hw *hw, int id)
+{
+ if (hw->pm->pd_data[id].dev)
+ return pm_runtime_active(hw->pm->pd_data[id].dev);
+ else
+ return false;
+}
+
+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;
+
+ if (hw->pm->power_state(hw, core))
+ 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_1);
+
+ return 0;
+}
+
+int vdec_disable(struct aml_vdec_hw *hw)
+{
+ vdec_poweroff(hw, VDEC_1);
+
+ 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,
+ .power_state = pm_vdec_power_domain_power_state,
+ },
+};
+
+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)
+ 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, VDEC_IRQ_1);
+ 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-1", 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(hw->canvas))
+ return PTR_ERR(hw->canvas);
+
+ sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0);
+ if (IS_ERR(sm_np))
+ return PTR_ERR(hw->canvas);
+
+ 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..443f48226239
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h
@@ -0,0 +1,159 @@
+/* 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
+ * @load_firmware_ex: Load firmware for current dec specific.
+ * @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 {
+ int (*load_firmware_ex)(void *priv, const u8 *data, u32 len);
+ 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_1 = 0,
+ VDEC_HEVC,
+ VDEC_MAX,
+};
+
+/**
+ * enum vdec_irq_num - Definition of the irq.
+ */
+enum vdec_irq_num {
+ VDEC_IRQ_0 = 0,
+ VDEC_IRQ_1,
+ VDEC_IRQ_2,
+ VDEC_IRQ_MAX,
+};
+
+/**
+ * enum vdec_type_e - Type of decoder clock.
+ */
+enum clk_type_e {
+ CLK_DOS = 0,
+ CLK_VDEC,
+ CLK_HEVCF,
+ 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.
+ * @power_state: Query the power state.
+ */
+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);
+ bool (*power_state)(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..ad4156249f55
--- /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)
+ 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..bd3aef44409f
--- /dev/null
+++ b/drivers/media/platform/amlogic/vdec/h264.c
@@ -0,0 +1,2128 @@
+// 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 "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 = sps->max_num_ref_frames;
+}
+
+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;
+
+ 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;
+ alloc_size = PAGE_ALIGN(h264_ctx->one_col_buf_size *
+ (h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN));
+ h264_ctx->collated_cma_vaddr = dma_alloc_coherent(hw->dev, alloc_size,
+ &h264_ctx->collated_cma_addr, GFP_KERNEL);
+ 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);
+
+ /* 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_list0,
+ 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->pic_info.plane_num > 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))
+ break;
+
+ 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->pic_info.plane_num > 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->pic_info.plane_num > 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) {
+ colocate_wr_adr = 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 >
+ 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) {
+ colocate_rd_adr = 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 >
+ 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)
+{
+ if (buf->y_canvas_index >= 0) {
+ dev_dbg(hw->dev, "free y_canvas %d\n", buf->y_canvas_index);
+ meson_canvas_free(hw->canvas, buf->y_canvas_index);
+ buf->y_canvas_index = -1;
+ }
+
+ if (buf->u_canvas_index >= 0) {
+ dev_dbg(hw->dev, "free uv_canvas_index %d\n",
+ buf->u_canvas_index);
+ meson_canvas_free(hw->canvas, buf->u_canvas_index);
+ buf->u_canvas_index = -1;
+ buf->v_canvas_index = -1;
+ }
+}
+
+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 *)dev->dec_ctx;
+ struct aml_vdec_hw *hw = vdec_get_hw(ctx->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;
+ 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;
+ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx,
+ VB2_BUF_STATE_DONE);
+ wake_up_interruptible(&ctx->queue);
+ break;
+ default:
+ h264_release_decode_spec(hw, ctx);
+ ctx->int_cond = 1;
+ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx,
+ VB2_BUF_STATE_ERROR);
+ 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;
+}
+
+static void copy_mc_cpu_fw(void *mc_cpu_addr, const u8 *data)
+{
+ /*header */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_HEADER,
+ data + 0x4000, MC_SWAP_SIZE);
+ /*data */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_DATA,
+ data + 0x2000, MC_SWAP_SIZE);
+ /*mmco */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MMCO,
+ data + 0x6000, MC_SWAP_SIZE);
+ /*list */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_LIST,
+ data + 0x3000, MC_SWAP_SIZE);
+ /*slice */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_SLICE,
+ data + 0x5000, MC_SWAP_SIZE);
+ /*main */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN, data, 0x2000);
+ /*data */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN + 0x2000,
+ data + 0x2000, 0x1000);
+ /*slice */
+ memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN + 0x3000,
+ data + 0x5000, 0x1000);
+}
+
+static int aml_h264_load_fw_ext(void *priv, const u8 *data, u32 len)
+{
+ struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)priv;
+ struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx;
+ struct aml_vdec_hw *dec_hw;
+
+ if (h264_ctx->mc_cpu_loaded)
+ return 0;
+
+ dec_hw = vdec_get_hw(ctx->dev);
+ if (!dec_hw)
+ return -1;
+
+ if (len > MC_TOTAL_SIZE) {
+ dev_info(dec_hw->dev, "size of mc_cpu_fw id invalid\n");
+ return -1;
+ }
+
+ h264_ctx->mc_cpu_vaddr = dma_alloc_coherent(dec_hw->dev, MC_TOTAL_SIZE,
+ &h264_ctx->mc_cpu_paddr,
+ GFP_KERNEL);
+ if (!h264_ctx->mc_cpu_vaddr)
+ return -ENOMEM;
+
+ copy_mc_cpu_fw(h264_ctx->mc_cpu_vaddr, data);
+
+ h264_ctx->mc_cpu_loaded = true;
+
+ dev_dbg(dec_hw->dev, "h264 mccpu fw loaded\n");
+
+ 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)
+ return -1;
+
+ 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;
+ dec_hw->hw_ops.load_firmware_ex = aml_h264_load_fw_ext;
+
+ 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;
+ dec_hw->curr_ctx = 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;
+
+ 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);
+ dec_hw->curr_ctx = NULL;
+ 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;
+
+ 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_info(&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;
+
+ 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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
` (3 preceding siblings ...)
2026-05-26 8:40 ` [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
@ 2026-05-26 8:40 ` Zhentao Guo via B4 Relay
2026-05-26 10:38 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
5 siblings, 1 reply; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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.
Signed-off-by: Zhentao Guo <zhentao.guo@amlogic.com>
---
arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 34 +++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
index 2a6fbd530836..5ad826e4b554 100644
--- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
@@ -96,6 +96,11 @@ saradc: adc@fe026000 {
status = "disabled";
};
+ optee {
+ compatible = "linaro,optee-tz";
+ method = "smc";
+ };
+
soc {
compatible = "simple-bus";
#address-cells = <2>;
@@ -907,5 +912,34 @@ 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>;
+ amlogic,canvas = <&canvas>;
+ reg-names = "dos",
+ "dmc";
+ interrupts = <GIC_SPI 91 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 92 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 93 IRQ_TYPE_EDGE_RISING>;
+ 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",
+ "hevc";
+ resets = <&reset RESET_DOS>;
+ secure-monitor = <&sm>;
+ };
};
};
--
2.42.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
` (4 preceding siblings ...)
2026-05-26 8:40 ` [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
@ 2026-05-26 8:40 ` Zhentao Guo via B4 Relay
2026-05-26 10:52 ` sashiko-bot
5 siblings, 1 reply; 13+ messages in thread
From: Zhentao Guo via B4 Relay @ 2026-05-26 8:40 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. This driver is needed for stateless video
decoding support on Amlogic SoCs.
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 4567f4b34f29..14caac24d200 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -913,6 +913,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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call
2026-05-26 8:40 ` [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
@ 2026-05-26 9:01 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-26 9:01 UTC (permalink / raw)
To: Zhentao Guo via B4 Relay
Cc: linux-amlogic, neil.armstrong, robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The identifier `SM_LOAD_VIDEO_FW` is used in the `gxbb_chip.cmd` array but is never defined, resulting in a build failure ('undeclared identifier'). The fix is found later in the series in commit 7da77dd38d2ea56fe7b57e246847b5cd118fd85e ('firmware: meson: sm: video firmware loading via secure monitor').
--
commit 087141e411e53f403ab8bf5aa5ca58a00cc95fd4
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 gxbb_chip command table
to support loading video firmware.
> diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
> index 3ab67aaa9e5da..5da6c65d684a0 100644
> --- a/drivers/firmware/meson/meson_sm.c
> +++ b/drivers/firmware/meson/meson_sm.c
> @@ -47,6 +47,7 @@ static const struct meson_sm_chip gxbb_chip = {
> CMD(SM_GET_CHIP_ID, 0x82000044),
> CMD(SM_A1_PWRC_SET, 0x82000093),
> CMD(SM_A1_PWRC_GET, 0x82000095),
> + CMD(SM_LOAD_VIDEO_FW, 0xb200000f),
[Severity: Low]
Does this code compile on its own? It appears that SM_LOAD_VIDEO_FW is used
here but is not declared yet.
While this is resolved later in the series by commit
7da77dd38d2ea56fe7b57e246847b5cd118fd85e (firmware: meson: sm: video
firmware loading via secure monitor), each patch in a series needs to
compile successfully on its own to maintain bisectability.
> { /* sentinel */ },
> },
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor
2026-05-26 8:40 ` [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
@ 2026-05-26 9:18 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-26 9:18 UTC (permalink / raw)
To: Zhentao Guo via B4 Relay
Cc: devicetree, neil.armstrong, conor+dt, robh, linux-amlogic
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The patch series is incorrectly ordered, separating the enum declaration of `SM_LOAD_VIDEO_FW` from its usage, which breaks `git bisect` due to a build failure.
--
commit 7da77dd38d2ea56fe7b57e246847b5cd118fd85e
Author: Zhentao Guo <zhentao.guo@amlogic.com>
firmware: meson: sm: video firmware loading via secure monitor
This commit adds the SM_LOAD_VIDEO_FW command to the secure monitor
command enum. This enables 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 8eaf8922ab020..f40867a000f10 100644
> --- a/include/linux/firmware/meson/meson_sm.h
> +++ b/include/linux/firmware/meson/meson_sm.h
> @@ -14,6 +14,7 @@ enum {
> SM_GET_CHIP_ID,
> SM_A1_PWRC_SET,
> SM_A1_PWRC_GET,
> + SM_LOAD_VIDEO_FW,
[Severity: Low]
Could this patch series ordering break git bisect?
The previous commit 087141e411e53 ("firmware: meson: sm: Add video firmware
loading SMC call") uses the SM_LOAD_VIDEO_FW command, but the enum value is
not declared until this subsequent commit. Checking out the previous commit
would result in an undeclared identifier compilation error. Should this
declaration be moved before its usage to maintain bisectability?
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com?part=2
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder
2026-05-26 8:40 ` [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
@ 2026-05-26 9:29 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-26 9:29 UTC (permalink / raw)
To: Zhentao Guo via B4 Relay
Cc: linux-amlogic, devicetree, robh, neil.armstrong, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The `resets` property is functionally necessary and present in the example, but it is omitted from the `required` list.
- [Low] The schema allows multiple interrupts but lacks `interrupt-names`, `items` descriptions, and a `minItems` constraint.
--
commit 4ec0f5c7e982a0f22963af0593b18e2e09373c7e
Author: Zhentao Guo <zhentao.guo@amlogic.com>
media: dt-bindings: Add Amlogic V4L2 video decoder
This commit describes the initial support for the V4L2 stateless video decoder
driver used with the Amlogic S4 (S805X2) platform.
> 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 0000000000000..a0f33f6c35a17
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml
[ ... ]
> +properties:
> + compatible:
> + const: amlogic,s4-vdec
> +
> + reg:
> + maxItems: 2
> +
> + reg-names:
> + items:
> + - const: dos
> + - const: dmc
> +
> + interrupts:
> + maxItems: 3
[Severity: Low]
Should this property include an items list with descriptions or a
corresponding interrupt-names property? Without these, there is no
documentation on what each of the three interrupts represents or the
required order.
Is a minItems constraint also needed here so that device trees with fewer
than 3 interrupts do not silently pass schema validation?
> +
> + clocks:
> + maxItems: 3
[ ... ]
> + secure-monitor:
> + description: phandle to the secure-monitor node
> + $ref: /schemas/types.yaml#/definitions/phandle
> +
> +required:
> + - compatible
> + - reg
> + - reg-names
> + - interrupts
> + - clocks
> + - clock-names
> + - power-domains
> + - power-domain-names
> + - amlogic,canvas
> + - secure-monitor
[Severity: Medium]
Should resets be included in this required list? It is defined in the
properties block and explicitly used in the device tree example below.
Omitting it here might allow incompletely configured device tree nodes missing
the reset line to silently pass schema validation, which could lead to driver
initialization failures or undefined hardware states at runtime.
> +
> +additionalProperties: false
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver
2026-05-26 8:40 ` [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
@ 2026-05-26 10:04 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-26 10:04 UTC (permalink / raw)
To: Zhentao Guo via B4 Relay
Cc: conor+dt, neil.armstrong, devicetree, robh, linux-amlogic
Thank you for your contribution! Sashiko AI review found 22 potential issue(s) to consider:
- [Critical] Global context pointers are unconditionally overwritten, breaking isolation for multiple instances and leading to UAF.
- [High] Accessing V4L2 M2M buffers and request objects after they have been completed by the ISR.
- [High] Missing `v4l2_m2m_buf_done_and_job_finish()` on error path causes a permanent stall of the V4L2 M2M context.
- [High] Probe error paths fail to clean up PM domains and device links.
- [High] Setting the OUTPUT queue format mistakenly overwrites the CAPTURE queue's format with a compressed stream format.
- [High] Multi-planar raw capture formats result in out-of-bounds DMA access due to improperly hardcoded plane counts.
- [High] Redundant hardware canvas allocations and error path failures leak and exhaust the global hardware canvas pool.
- [High] Tautological comparisons on unsigned variables cause bounds-check bypasses and spurious resource frees.
- [High] Missing validation of V4L2 SPS/PPS controls leads to multiple heap buffer overflows, integer overflows, and OOB DMA.
- [High] Teardown in `aml_vdec_drv_remove` frees the driver context while file descriptors can remain open.
- [High] Failing `start_streaming` does not return queued buffers to the VB2 framework.
- [High] Double free and premature free of `ctx->codec_priv` via `vb2ops_vdec_stop_streaming`.
- [High] Bypassing PM runtime acquisition leads to unbalanced clock/PM references.
- [High] Lockless buffer access and unchecked NULL dereference in IRQ handler.
- [High] The DOS clock is never enabled before accessing DOS_BUS registers.
- [Medium] Incorrect error checking of `of_parse_phandle` leads to NULL pointer dereference and wrong error return.
- [Medium] Unmet dependencies when using `select` with architecture-specific symbols in a `COMPILE_TEST` driver.
- [Medium] Missing check for pm_runtime_get_sync return value allows access to unpowered hardware.
- [Medium] The V4L2 control handler memory is leaked if `v4l2_ctrl_handler_setup` fails.
- [Medium] Video device node is exposed to userspace before hardware initialization is complete.
- [Medium] Missing minimum length validation for loaded firmware data.
- [Medium] Missing `dma_rmb()` barrier before reading DMA memory updated by hardware.
--
commit 05d0db2be715fa3e4cc8fc756c957d431e6f5b89
Author: Zhentao Guo <zhentao.guo@amlogic.com>
decoder: Add V4L2 stateless H.264 decoder driver
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.
> diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig
> new file mode 100644
> index 0000000000000..d392967c77436
> --- /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
> + select VIDEOBUF2_DMA_CONTIG
> + select V4L2_H264
> + select V4L2_MEM2MEM_DEV
> + select MESON_CANVAS
> + select MESON_SM
[Severity: Medium]
Will selecting architecture-specific symbols like MESON_CANVAS and MESON_SM
cause unmet dependency warnings on unsupported architectures when COMPILE_TEST
is enabled?
[ ... ]
> 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 0000000000000..d4dcd0180d2d0
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c
[ ... ]
> +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);
[Severity: Medium]
If v4l2_ctrl_handler_setup() returns an error, does this code leak the memory
allocated for the control handler? The caller doesn't seem to free it on
this failure path.
> +}
> +
> +static void m2mops_vdec_device_run(void *m2m_priv)
> +{
> + struct aml_vdec_ctx *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;
> +
> + 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);
[Severity: High]
Since ctx->codec_ops->run() blocks on wait_event_interruptible_timeout, and
the ISR completes the job by calling v4l2_m2m_buf_done_and_job_finish(), could
src_buf, dst_buf, and src_req already be returned to userspace and potentially
freed by the time this thread wakes up to access them?
> + if (ret < 0)
> + goto err_cancel_job;
> +
> + return;
> +
> +err_cancel_job:
> + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx,
> + VB2_BUF_STATE_ERROR);
> +}
[ ... ]
> +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;
> + ctx->pic_info.plane_num = 1;
[Severity: High]
If a multi-planar format like NV12M is negotiated, shouldn't plane_num be set
to match the actual planes used? By rigidly setting plane_num to 1 here, could
the driver end up computing out-of-bounds DMA addresses for the C plane later?
> + }
> +}
[ ... ]
> +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;
> + const struct aml_video_fmt *out_fmt;
> + struct vb2_queue *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;
> +
> + out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat);
> + if (out_fmt)
> + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt;
> + else
> + 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);
> + set_pic_info(ctx, pix_mp, f->type);
> +
> + ctx->pix_fmt[AML_FMT_SRC] = *pix_mp;
> + ctx->pix_fmt[AML_FMT_DST] = *pix_mp;
[Severity: High]
Does overwriting ctx->pix_fmt[AML_FMT_DST] here corrupt the capture queue's
pixel format by erroneously assigning it the output queue's compressed
bitstream format?
> +
> + return ret;
> +}
[ ... ]
> +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)) {
> + ctx->is_output_streamon = 1;
> + if (aml_vdec_init_dec_inst(ctx) < 0)
> + return -EINVAL;
[Severity: High]
If aml_vdec_init_dec_inst() fails and returns an error, doesn't the driver
need to return all queued buffers to userspace with state VB2_BUF_STATE_QUEUED
to avoid permanently leaking them?
> + } 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;
> +
> + aml_vdec_release_instance(ctx);
[Severity: High]
Since aml_vdec_release_instance() calls aml_h264_exit() which frees
ctx->codec_priv, what happens if userspace stops only the capture queue while
the output queue is actively streaming? Will this cause a use-after-free on
ctx->codec_priv for ongoing decode jobs or incoming IRQs?
> +
> + 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;
> + }
> +}
[ ... ]
> 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 0000000000000..d63cbd4f9e26d
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c
[ ... ]
> +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);
> + dec_dev->dec_ctx = ctx;
[Severity: Critical]
Is it safe to assign the newly allocated context to the global
dec_dev->dec_ctx? If multiple file handles are opened concurrently, won't this
overwrite the pointer and cause the ISR to route events only to the most
recently opened instance?
> + ctx->dev = dec_dev;
> + v4l2_fh_init(&ctx->fh, video_devdata(file));
> + file->private_data = &ctx->fh;
> + v4l2_fh_add(&ctx->fh, file);
> + dec_dev->filp = file;
> + mutex_init(&ctx->v4l2_intf_lock);
> + init_waitqueue_head(&ctx->queue);
> + ctx->int_cond = 0;
[ ... ]
> +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_hw_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);
[Severity: Medium]
By calling video_register_device() before vdec_enable(), isn't the device node
exposed to userspace before the hardware is powered up and initialized? Could
this allow userspace to issue ioctls on unpowered hardware?
> + return 0;
> +
> +err_media_dev_register:
> + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec);
> +error_m2m_mc_register:
> + media_device_cleanup(&dev->mdev);
> +err_vid_dev_register:
> + v4l2_m2m_release(dev->m2m_dev_dec);
> +err_hw_init:
> + dev->dec_hw = NULL;
[Severity: High]
Does this error path forget to call dev->pvdec_data->destroy_hw_resource(dev)?
Failing to clean up hardware resources here might permanently leak attached PM
domains and device links.
> +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);
> +
> + if (media_devnode_is_registered(dev->mdev.devnode)) {
> + media_device_unregister(&dev->mdev);
> + media_device_cleanup(&dev->mdev);
> + }
> +
> + if (dev->m2m_dev_dec)
> + v4l2_m2m_release(dev->m2m_dev_dec);
> + if (dev->vfd)
> + video_unregister_device(dev->vfd);
> + if (dev->dec_hw) {
> + dev->pvdec_data->destroy_hw_resource(dev);
> + dev->dec_hw = NULL;
> + }
> + v4l2_device_unregister(&dev->v4l2_dev);
> +}
[Severity: High]
If a process keeps the video file descriptor open when the device is unbound,
devm_kzalloc will free the dev structure after this remove function completes.
When the file descriptor is eventually closed, fops_vcodec_release will
execute and attempt to access dev->dev_mutex. Will this lead to a
use-after-free on the dev structure?
> 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 0000000000000..79ba2a68bde41
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c
[ ... ]
> +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_HEVCF].id = "hevcf";
> +
> + return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates);
> +}
[ ... ]
> +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_1)
> + gate_node = vdec_get_clk_by_name(hw, "vdec");
> + else if (id == VDEC_HEVC)
> + gate_node = vdec_get_clk_by_name(hw, "hevcf");
> +
> + if (gate_node) {
> + clk_prepare_enable(gate_node->clk);
[Severity: High]
While the "vdec" and "hevcf" clocks are enabled here, isn't the "dos" clock
omitted entirely? If the "dos" clock remains gated, could accessing DOS_BUS
registers result in a synchronous external abort?
> + if (id == VDEC_1) {
> + 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);
[Severity: Medium]
Should the return value of pm_runtime_get_sync() be checked before calling
dos_local_config()? If the power domain fails to power up, accessing the
hardware registers could lead to a bus hang.
> +}
[ ... ]
> +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;
> +
> + if (hw->pm->power_state(hw, core))
> + goto out;
[Severity: High]
If the power domain is already active, this code jumps past power_on() but
still increments ref_count. Won't the subsequent teardown decrement ref_count
and call power_off() unconditionally, causing unbalanced clk_disable_unprepare()
and pm_runtime_put_sync() calls?
> +
> + hw->pm->power_on(hw, core);
> +
> +out:
> + mutex_unlock(&hw->pm_mutex);
> +}
[ ... ]
> +int dev_request_hw_resources(void *priv)
> +{
[ ... ]
> + hw->canvas = meson_canvas_get(&pdev->dev);
> + if (IS_ERR(hw->canvas))
> + return PTR_ERR(hw->canvas);
> +
> + sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0);
> + if (IS_ERR(sm_np))
> + return PTR_ERR(hw->canvas);
[Severity: Medium]
Since of_parse_phandle() returns NULL on failure rather than an ERR_PTR,
doesn't the IS_ERR check fail to catch a missing node? Passing NULL to
meson_sm_get() later might cause issues. Also, why is PTR_ERR(hw->canvas)
returned here instead of an appropriate error code?
> +
> + 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);
[ ... ]
> diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c
> new file mode 100644
> index 0000000000000..bd3aef44409f3
> --- /dev/null
> +++ b/drivers/media/platform/amlogic/vdec/h264.c
[ ... ]
> +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;
[Severity: High]
Does this code validate userspace-provided SPS controls? For example, if
max_num_ref_frames is artificially large, could it overflow colocated arrays?
Likewise, could an overly large pic_width_in_mbs_minus1 cause integer overflows
when calculating allocation sizes?
> +
> + data_tmp[offset] = PARAM_BASE_VAL;
> + offset += 2;
[ ... ]
> +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 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;
[Severity: High]
If the destination queue is empty when this ISR runs, won't v4l2_m2m_next_dst_buf
return NULL? This would result in an immediate NULL pointer dereference.
> +
> + h264_ctx->curr_spec.y_dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> + if (ctx->pic_info.plane_num > 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)
[Severity: High]
Since canvas_pos is a u32 (inside struct h264_decode_buf_spec), isn't this
comparison (< 0) always false? This causes the code to proceed with an invalid
canvas position if allocate_canvas_pos() fails.
> + 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);
[ ... ]
> +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);
> + }
> + }
[Severity: High]
If a reference frame is shared between list0 and list1, will config_decode_canvas()
be called twice for the same buffer, causing a redundant canvas allocation
and leaking the hardware canvas pool?
> +}
> +
> +static void release_canvas_index(struct aml_vdec_hw *hw,
> + struct h264_decode_buf_spec *buf)
> +{
> + if (buf->y_canvas_index >= 0) {
[Severity: High]
Because y_canvas_index is declared as u8, won't this check always be true?
If it's previously set to -1, it wraps to 255, causing the driver to spuriously
free canvas 255.
> + dev_dbg(hw->dev, "free y_canvas %d\n", buf->y_canvas_index);
> + meson_canvas_free(hw->canvas, buf->y_canvas_index);
> + buf->y_canvas_index = -1;
> + }
> +
> + if (buf->u_canvas_index >= 0) {
> + dev_dbg(hw->dev, "free uv_canvas_index %d\n",
> + buf->u_canvas_index);
> + meson_canvas_free(hw->canvas, buf->u_canvas_index);
> + buf->u_canvas_index = -1;
> + buf->v_canvas_index = -1;
> + }
> +}
[ ... ]
> +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 *)dev->dec_ctx;
> + struct aml_vdec_hw *hw = vdec_get_hw(ctx->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];
[Severity: Medium]
Since lmem_addr holds DMA memory populated by the hardware, should a dma_rmb()
barrier be issued before reading from `p` to ensure the CPU doesn't observe
stale cached data?
> + 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;
> + wake_up_interruptible(&ctx->queue);
> + goto irq_handled;
[Severity: High]
If the execution takes this error path, ctx->int_cond = 1 satisfies the wait
condition in m2mops_vdec_device_run(), which then returns directly without
canceling the job. Does this omit a necessary v4l2_m2m_buf_done_and_job_finish()
call and permanently stall the mem2mem context?
> + }
> + 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;
[ ... ]
> +static int aml_h264_load_fw_ext(void *priv, const u8 *data, u32 len)
> +{
> + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)priv;
> + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx;
> + struct aml_vdec_hw *dec_hw;
> +
> + if (h264_ctx->mc_cpu_loaded)
> + return 0;
> +
> + dec_hw = vdec_get_hw(ctx->dev);
> + if (!dec_hw)
> + return -1;
> +
> + if (len > MC_TOTAL_SIZE) {
> + dev_info(dec_hw->dev, "size of mc_cpu_fw id invalid\n");
> + return -1;
> + }
[Severity: Medium]
Should the firmware length also be checked against a minimum bounds before
calling copy_mc_cpu_fw()? Passing truncated firmware could cause out-of-bounds
reads when accessing fixed offsets (e.g., data + 0x6000) inside copy_mc_cpu_fw().
> +
> + h264_ctx->mc_cpu_vaddr = dma_alloc_coherent(dec_hw->dev, MC_TOTAL_SIZE,
> + &h264_ctx->mc_cpu_paddr,
> + GFP_KERNEL);
> + if (!h264_ctx->mc_cpu_vaddr)
> + return -ENOMEM;
> +
> + copy_mc_cpu_fw(h264_ctx->mc_cpu_vaddr, data);
> +
> + h264_ctx->mc_cpu_loaded = true;
> +
> + dev_dbg(dec_hw->dev, "h264 mccpu fw loaded\n");
> +
> + 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)
> + return -1;
> +
> + 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;
> + dec_hw->hw_ops.load_firmware_ex = aml_h264_load_fw_ext;
> +
> + 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;
> + dec_hw->curr_ctx = h264_ctx;
[Severity: Critical]
Similar to the issue in fops_vcodec_open(), could assigning the context to
dec_hw->curr_ctx here break isolation across multiple concurrent instances?
When multiple streams are active, the ISR will blindly dereference curr_ctx,
potentially targeting the wrong or freed context.
> + 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;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
2026-05-26 8:40 ` [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
@ 2026-05-26 10:38 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-26 10:38 UTC (permalink / raw)
To: Zhentao Guo via B4 Relay
Cc: linux-amlogic, conor+dt, devicetree, robh, neil.armstrong
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The patch silently introduces an `optee` node which is not mentioned in the commit message, and places it incorrectly at the root of the device tree.
- [Low] The newly added `canvas` and `video-codec` nodes break the expected unit-address sorting within the `soc` simple-bus node.
--
commit 9f1bc8bd76d58f7598508700fcc37888ae1dbf0f
Author: Zhentao Guo <zhentao.guo@amlogic.com>
arm64: dts: amlogic: Add video decoder driver support for S4 SOCs
This commit adds the vdec node to enable Amlogic V4L2 stateless video
decoder support for S4 SOCs. It also introduces an optee node and a canvas
node required for the decoder.
> diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
> --- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
> +++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi
> @@ -96,6 +96,11 @@ saradc: adc@fe026000 {
> status = "disabled";
> };
>
> + optee {
> + compatible = "linaro,optee-tz";
> + method = "smc";
> + };
[Severity: Medium]
Should this optee node addition be explicitly mentioned in the commit
message? The commit description currently only mentions adding the vdec
node.
Also, should this node be placed under a firmware node instead of at the root
level of the device tree?
> soc {
> compatible = "simple-bus";
> #address-cells = <2>;
[ ... ]
> @@ -907,5 +912,34 @@ 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 {
[Severity: Low]
Does appending these nodes break the expected unit-address sorting within the
soc simple-bus node? The canvas node at fe036048 and the video-codec node at
fe320000 appear to be placed at the end of the block, after mmc@fe08c000,
rather than being ordered sequentially.
> + compatible = "amlogic,s4-vdec";
> + reg = <0x0 0xfe320000 0x0 0x10000>,
> + <0x0 0xfe036000 0x0 0x20>;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC
2026-05-26 8:40 ` [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
@ 2026-05-26 10:52 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-05-26 10:52 UTC (permalink / raw)
To: Zhentao Guo via B4 Relay
Cc: linux-amlogic, devicetree, neil.armstrong, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] CONFIG_VIDEO_AMLOGIC_VDEC selects MESON_SM without satisfying its ARM64_4K_PAGES dependency, causing Kconfig warnings under COMPILE_TEST or when using 16K/64K pages.
- [Low] CONFIG_VIDEO_AMLOGIC_VDEC is inserted manually out of order in arm64 defconfig.
--
commit 482a11927128f3e0aa6ba1ce2517d29726af8f3b
Author: Zhentao Guo <zhentao.guo@amlogic.com>
arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC
This commit enables the Amlogic V4L2 stateless video decoder driver as a
module in the arm64 defconfig.
> diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
> index 4567f4b34f293..14caac24d2009 100644
> --- a/arch/arm64/configs/defconfig
> +++ b/arch/arm64/configs/defconfig
> @@ -913,6 +913,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
[Severity: Low]
Does enabling this configuration expose a dependency issue in the driver's
Kconfig?
In the Kconfig definition for VIDEO_AMLOGIC_VDEC introduced earlier in this
patchset, it unconditionally uses select MESON_SM. However, MESON_SM has a
strict dependency on ARM64_4K_PAGES.
Selecting a symbol without meeting its dependencies forces it on, which can
trigger unmet direct dependency warnings during builds on architectures or
configurations where ARM64_4K_PAGES is not met. This can happen on non-ARM64
COMPILE_TEST builds or ARM64 builds configured with 16K or 64K pages.
Could the driver use depends on MESON_SM instead of select to avoid
breaking automated Kconfig build testing?
[Severity: Low]
This isn't a bug, but was this defconfig change generated using make
savedefconfig?
In the media platform Kconfig hierarchy, the Amlogic platform Kconfig is
included before the Amphion platform Kconfig. Running make savedefconfig
would naturally place CONFIG_VIDEO_AMLOGIC_VDEC before
CONFIG_VIDEO_AMPHION_VPU.
Manually inserting it after Amphion will cause unnecessary churn the next
time make savedefconfig is executed.
> CONFIG_VIDEO_CADENCE_CSI2RX=m
> CONFIG_VIDEO_WAVE_VPU=m
> CONFIG_VIDEO_E5010_JPEG_ENC=m
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com?part=6
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-05-26 10:52 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-26 8:40 [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Zhentao Guo via B4 Relay
2026-05-26 8:40 ` [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call Zhentao Guo via B4 Relay
2026-05-26 9:01 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor Zhentao Guo via B4 Relay
2026-05-26 9:18 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder Zhentao Guo via B4 Relay
2026-05-26 9:29 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver Zhentao Guo via B4 Relay
2026-05-26 10:04 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs Zhentao Guo via B4 Relay
2026-05-26 10:38 ` sashiko-bot
2026-05-26 8:40 ` [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC Zhentao Guo via B4 Relay
2026-05-26 10:52 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox