Devicetree
 help / color / mirror / Atom feed
* [PATCH v6 0/9] Add support for Wave6 video codec driver
@ 2026-06-24  7:20 Nas Chung
  2026-06-24  7:20 ` [PATCH v6 1/9] media: v4l2-common: Fix P010 format info Nas Chung
                   ` (8 more replies)
  0 siblings, 9 replies; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung

This patch series introduces support for the Chips&Media Wave6 video
codec IP, a completely different hardware architecture compared to Wave5.

The wave6 driver is a M2M stateful encoder/decoder driver.
It supports various video formats, including H.264 and H.265, for both encoding
and decoding.
While other versions of the Wave6 IP may support VP9 decoding and AV1 decoding
and encoding those formats are not implemented or validated in this driver at
this time.

On NXP i.MX SoCs, the Wave6 hardware exposes one control register region and
four interface register regions for one shared video processing engine.
VPU Control region, Manages shared resources such as firmware memory.
VPU Core region, Provides encoding and decoding capabilities.
The control and interface regions are distinct DMA requesters and can be
associated with separate IOMMU stream IDs, allowing DMA isolation between them.

The firmware tested by this driver has been upstreamed in linux-firmware:
- Path: cnm/wave633c_imx9_codec_fw.bin

This driver has been tested with GStreamer on:
- NXP i.MX95 board
- pre-silicon FPGA environment

Test results for decoder fluster with -j2 option:
- JVT-AVC_V1, Ran 77/135 tests successfully              in 24.180 secs
- JVT-FR-EXT, Ran 25/69 tests successfully               in 11.157 secs
- JCT-VC-HEVC_V1, Ran 132/147 tests successfully         in 45.534 secs
- All failures are due to unsupported hardware features:
-- 10bit, Resolutions higher than 4K, FMO, MBAFF
-- Extended profile, Field encoding and High422 sreams.

Test results for v4l2-compliance:
v4l2-compliance 1.31.0-5386, 64 bits, 64-bit time_t
v4l2-compliance SHA: 48316b8a20aa 2025-08-12 12:44:56

Compliance test for wave6-dec device /dev/video0:
                fail: v4l2-test-controls.cpp(1204): !have_source_change || !have_eos
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: FAIL
Total for wave6-dec device /dev/video0: 48, Succeeded: 47, Failed: 1, Warnings: 0

Compliance test for wave6-enc device /dev/video1:
                fail: v4l2-test-controls.cpp(1193): node->codec_mask & STATEFUL_ENCODER
        test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: FAIL
Total for wave6-enc device /dev/video1: 48, Succeeded: 47, Failed: 1, Warnings: 0

Note: the failures are all related with the eos event.

Changelog:

v6:
- Fixed the existing P010 format_info entry
- Moved all encoder parameter validation from the HW layer into the V4L2 layer
- Removed redundant HW-abstraction wrappers from the VPU API layer
- Computed frame stride and sizeimage in the V4L2 layer
- Added explicit instance lifecycle helpers to the core driver
- Enabled the VPU only on the tested imx95-19x19-evk board

RFC v5:
- Move all shared resources to the parent node
- Drop child compatible and use data-only interface child nodes
- Update the VPU driver to create child devices and load the core driver

v4:
- Fixed build issues reported by CI tools
- Updated commit messages to use imperative mood
- Avoided using the same name for both nodes and labels in devicetree
- Removed unused labels from YAML examples
- Added description for child(vpu-core) node
- Added iommus property to both parent(vpu) and child(vpu-core) nodes
- Updated probe() functions to use dev_err_probe() when returning -EPROBE_DEFER
- Added wave6_vpu prefix to trace functions
- Updated HEVC decoder profile control to report MAIN_STILL profile
- Fixed bug in multiple instance creation by pre-allocating work buffer
- Fixed interrupt handling by checking INSTANCE_INFO register and instance list

v3:
- Removed ambiguous SUPPORT_FOLLOWER feature
- Used WARN_ON() for unexpected programming errors
- Split thermal device code into wave6-vpu-thermal.c/h
- Dropped wave6_cooling_disable module parameter
- Replaced mutex_lock() with guard()
- Added lockdep_assert_held() to clarify locking regions
- Removed exported function due to dual-license and used function pointer
- Added documentation and validation for state transitions
- Added documentation for device structures
- Added patch to enable VPU device in imx95 DTS
- Updated DT bindings and driver to align with parent(vpu) and child(vpu-core)
- Replaced magic numbers with mask and offset macros when accessing registers
- Placed goto statements after an empty line
- Printed HW info (e.g. product_code) via dev_dbg() for debugging
- Replaced wave6_vpu_dec_give_command() with dedicated functions

v2:
- Refined DT bindings to better represent the hardware
- Reworked driver to align with the parent(VPU) and child(CTRL, CORE)
- Fixed build issues reported by CI tools (Smatch, Sparse, TRACE)
- Improved commit messages with clearer descriptions
- Added kernel-doc for exported functions
- Removed redundant print statements and unused code
- Reordered patches to prevent build failures

Nas Chung (9):
  media: v4l2-common: Fix P010 format info
  dt-bindings: media: nxp: Add Wave6 video codec device
  media: chips-media: wave6: Add Wave6 VPU interface
  media: chips-media: wave6: Add v4l2 m2m driver support
  media: chips-media: wave6: Add Wave6 core driver
  media: chips-media: wave6: Improve debugging capabilities
  media: chips-media: wave6: Add Wave6 thermal cooling device
  media: chips-media: wave6: Add Wave6 control driver
  arm64: dts: freescale: imx95: Add video codec node

 .../bindings/media/nxp,imx95-vpu.yaml         |  163 +
 MAINTAINERS                                   |    8 +
 .../boot/dts/freescale/imx95-19x19-evk.dts    |   11 +
 arch/arm64/boot/dts/freescale/imx95.dtsi      |   36 +
 drivers/media/platform/chips-media/Kconfig    |    1 +
 drivers/media/platform/chips-media/Makefile   |    1 +
 .../media/platform/chips-media/wave6/Kconfig  |   17 +
 .../media/platform/chips-media/wave6/Makefile |   17 +
 .../platform/chips-media/wave6/wave6-hw.c     | 2086 +++++++++++++
 .../platform/chips-media/wave6/wave6-hw.h     |   56 +
 .../chips-media/wave6/wave6-regdefine.h       |  647 ++++
 .../platform/chips-media/wave6/wave6-trace.h  |  289 ++
 .../platform/chips-media/wave6/wave6-vdi.h    |   92 +
 .../chips-media/wave6/wave6-vpu-core.c        |  437 +++
 .../chips-media/wave6/wave6-vpu-core.h        |  126 +
 .../chips-media/wave6/wave6-vpu-dbg.c         |  177 ++
 .../chips-media/wave6/wave6-vpu-dbg.h         |   14 +
 .../chips-media/wave6/wave6-vpu-dec.c         | 1831 +++++++++++
 .../chips-media/wave6/wave6-vpu-enc.c         | 2758 +++++++++++++++++
 .../chips-media/wave6/wave6-vpu-thermal.c     |  139 +
 .../chips-media/wave6/wave6-vpu-thermal.h     |   24 +
 .../chips-media/wave6/wave6-vpu-v4l2.c        |  516 +++
 .../platform/chips-media/wave6/wave6-vpu.c    |  821 +++++
 .../platform/chips-media/wave6/wave6-vpu.h    |  143 +
 .../platform/chips-media/wave6/wave6-vpuapi.c |  343 ++
 .../platform/chips-media/wave6/wave6-vpuapi.h | 1005 ++++++
 .../chips-media/wave6/wave6-vpuconfig.h       |   72 +
 .../chips-media/wave6/wave6-vpuerror.h        |  262 ++
 drivers/media/v4l2-core/v4l2-common.c         |    2 +-
 29 files changed, 12093 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
 create mode 100644 drivers/media/platform/chips-media/wave6/Kconfig
 create mode 100644 drivers/media/platform/chips-media/wave6/Makefile
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-hw.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-hw.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-regdefine.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-trace.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vdi.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-core.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuapi.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuapi.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuconfig.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuerror.h

-- 
2.31.1


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v6 1/9] media: v4l2-common: Fix P010 format info
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:20 ` [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec device Nas Chung
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung

V4L2_PIX_FMT_P010 is a 10-bit 4:2:0 semi-planar format, but its
v4l2_format_info() entry described a 4:2:2 layout with a half-width
chroma plane. Correct bpp and vdiv to match P010's real layout,
consistent with the other semi-planar formats.

Fixes: 5374d8fb75f3 ("media: Add P010 video format")
Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
---
 drivers/media/v4l2-core/v4l2-common.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 65db7340ad38..4de8aa3ef7d2 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -323,7 +323,7 @@ const struct v4l2_format_info *v4l2_format_info(u32 format)
 		{ .format = V4L2_PIX_FMT_NV20,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 5, 10, 0, 0 }, .bpp_div = { 4, 4, 1, 1 }, .hdiv = 2, .vdiv = 1 },
 		{ .format = V4L2_PIX_FMT_NV24,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
 		{ .format = V4L2_PIX_FMT_NV42,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
-		{ .format = V4L2_PIX_FMT_P010,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 2, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
+		{ .format = V4L2_PIX_FMT_P010,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 2, 4, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
 		{ .format = V4L2_PIX_FMT_P012,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 2, 4, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
 
 		{ .format = V4L2_PIX_FMT_YUV410,  .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 4, .vdiv = 4 },
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec device
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
  2026-06-24  7:20 ` [PATCH v6 1/9] media: v4l2-common: Fix P010 format info Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:20 ` [PATCH v6 3/9] media: chips-media: wave6: Add Wave6 VPU interface Nas Chung
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung

Add documentation for the Chips&Media Wave6 video codec on NXP i.MX SoCs.

The hardware contains one control register region and four interface
register regions for a shared video processing engine. The control region
manages shared resources such as firmware memory, while each interface
region has its own MMIO range and interrupt.

The control region and each interface region are distinct DMA requesters
and can be associated with separate IOMMU stream IDs. Represent the
control region as the parent node and the interface register regions as
child nodes to describe these resources.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
---
 .../bindings/media/nxp,imx95-vpu.yaml         | 163 ++++++++++++++++++
 MAINTAINERS                                   |   7 +
 2 files changed, 170 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml

diff --git a/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml b/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
new file mode 100644
index 000000000000..9a5ca53e15a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
@@ -0,0 +1,163 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/nxp,imx95-vpu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Chips&Media Wave6 Series multi-standard codec IP on NXP i.MX SoCs
+
+maintainers:
+  - Nas Chung <nas.chung@chipsnmedia.com>
+  - Jackson Lee <jackson.lee@chipsnmedia.com>
+
+description:
+  The Chips&Media Wave6 codec IP is a multi-standard video encoder/decoder.
+  On NXP i.MX SoCs, the Wave6 codec IP exposes one control register region and
+  four interface register regions for a shared video processing engine.
+  The parent node describes the control region, which has its own MMIO range and
+  manages shared resources such as firmware memory. The child nodes describe the
+  interface register regions. Each interface region has its own MMIO range and
+  interrupt.
+  The control region and the interface regions are distinct DMA requesters.
+  The control region and each interface region can be associated with separate
+  IOMMU stream IDs, allowing DMA isolation between them.
+
+properties:
+  compatible:
+    enum:
+      - nxp,imx95-vpu
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: VPU core clock
+      - description: VPU associated block clock
+
+  clock-names:
+    items:
+      - const: core
+      - const: vpublk
+
+  power-domains:
+    items:
+      - description: Main VPU power domain
+      - description: Performance power domain
+
+  power-domain-names:
+    items:
+      - const: vpu
+      - const: perf
+
+  memory-region:
+    maxItems: 1
+
+  sram:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      phandle to the SRAM node used to store reference data, reducing DMA
+      memory bandwidth.
+
+  iommus:
+    maxItems: 1
+
+  "#cooling-cells":
+    const: 2
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 2
+
+  ranges: true
+
+patternProperties:
+  "^interface@[0-9a-f]+$":
+    type: object
+    description:
+      An interface register region within the Chips&Media Wave6 codec IP.
+      Each region has its own MMIO range and interrupt and can be associated
+      with a separate IOMMU stream ID for DMA isolation.
+    additionalProperties: false
+
+    properties:
+      reg:
+        maxItems: 1
+
+      interrupts:
+        maxItems: 1
+
+      iommus:
+        maxItems: 1
+
+    required:
+      - reg
+      - interrupts
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - power-domains
+  - power-domain-names
+  - memory-region
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/clock/nxp,imx95-clock.h>
+
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      video-codec@4c4c0000 {
+        compatible = "nxp,imx95-vpu";
+        reg = <0x0 0x4c4c0000 0x0 0x10000>;
+        clocks = <&scmi_clk 115>,
+                 <&vpu_blk_ctrl IMX95_CLK_VPUBLK_WAVE>;
+        clock-names = "core", "vpublk";
+        power-domains = <&scmi_devpd 21>,
+                        <&scmi_perf 10>;
+        power-domain-names = "vpu", "perf";
+        memory-region = <&vpu_boot>;
+        sram = <&sram1>;
+        iommus = <&smmu 0x32>;
+        #cooling-cells = <2>;
+        #address-cells = <2>;
+        #size-cells = <2>;
+        ranges;
+
+        interface@4c480000 {
+          reg = <0x0 0x4c480000 0x0 0x10000>;
+          interrupts = <GIC_SPI 299 IRQ_TYPE_LEVEL_HIGH>;
+          iommus = <&smmu 0x33>;
+        };
+
+        interface@4c490000 {
+          reg = <0x0 0x4c490000 0x0 0x10000>;
+          interrupts = <GIC_SPI 300 IRQ_TYPE_LEVEL_HIGH>;
+          iommus = <&smmu 0x34>;
+        };
+
+        interface@4c4a0000 {
+          reg = <0x0 0x4c4a0000 0x0 0x10000>;
+          interrupts = <GIC_SPI 301 IRQ_TYPE_LEVEL_HIGH>;
+          iommus = <&smmu 0x35>;
+        };
+
+        interface@4c4b0000 {
+          reg = <0x0 0x4c4b0000 0x0 0x10000>;
+          interrupts = <GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH>;
+          iommus = <&smmu 0x36>;
+        };
+      };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index efbf808063e5..77ea3a1a966b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28688,6 +28688,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/cnm,wave521c.yaml
 F:	drivers/media/platform/chips-media/wave5/
 
+WAVE6 VPU CODEC DRIVER
+M:	Nas Chung <nas.chung@chipsnmedia.com>
+M:	Jackson Lee <jackson.lee@chipsnmedia.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
+
 WHISKEYCOVE PMIC GPIO DRIVER
 M:	Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
 L:	linux-gpio@vger.kernel.org
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 3/9] media: chips-media: wave6: Add Wave6 VPU interface
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
  2026-06-24  7:20 ` [PATCH v6 1/9] media: v4l2-common: Fix P010 format info Nas Chung
  2026-06-24  7:20 ` [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec device Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:43   ` sashiko-bot
  2026-06-24  7:20 ` [PATCH v6 4/9] media: chips-media: wave6: Add v4l2 m2m driver support Nas Chung
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung,
	Ming Qian

Add an interface layer to manage hardware register configuration
and communication with the Chips&Media Wave6 video codec IP.

The interface provides low-level helper functions used by the
Wave6 core driver to implement video encoding and decoding operations.
It handles command submission to the firmware via MMIO registers,
and waits for a response by polling the firmware busy flag.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
 MAINTAINERS                                   |    1 +
 .../platform/chips-media/wave6/wave6-hw.c     | 2086 +++++++++++++++++
 .../platform/chips-media/wave6/wave6-hw.h     |   56 +
 .../chips-media/wave6/wave6-regdefine.h       |  647 +++++
 .../platform/chips-media/wave6/wave6-vdi.h    |   92 +
 .../platform/chips-media/wave6/wave6-vpuapi.c |  343 +++
 .../platform/chips-media/wave6/wave6-vpuapi.h | 1005 ++++++++
 .../chips-media/wave6/wave6-vpuconfig.h       |   72 +
 .../chips-media/wave6/wave6-vpuerror.h        |  262 +++
 9 files changed, 4564 insertions(+)
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-hw.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-hw.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-regdefine.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vdi.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuapi.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuapi.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuconfig.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpuerror.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 77ea3a1a966b..14864d37cd5b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28694,6 +28694,7 @@ M:	Jackson Lee <jackson.lee@chipsnmedia.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
+F:	drivers/media/platform/chips-media/wave6/
 
 WHISKEYCOVE PMIC GPIO DRIVER
 M:	Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
diff --git a/drivers/media/platform/chips-media/wave6/wave6-hw.c b/drivers/media/platform/chips-media/wave6/wave6-hw.c
new file mode 100644
index 000000000000..b49348d6119c
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-hw.c
@@ -0,0 +1,2086 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 backend interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/iopoll.h>
+#include "wave6-vpu-core.h"
+#include "wave6-hw.h"
+#include "wave6-regdefine.h"
+#include "wave6-trace.h"
+
+void wave6_vpu_writel(struct vpu_core_device *core, u32 addr, u32 data)
+{
+	wave6_vdi_writel(core->reg_base, addr, data);
+	trace_wave6_vpu_writel(core->dev, addr, data);
+}
+
+u32 wave6_vpu_readl(struct vpu_core_device *core, u32 addr)
+{
+	u32 data;
+
+	data = wave6_vdi_readl(core->reg_base, addr);
+	trace_wave6_vpu_readl(core->dev, addr, data);
+
+	return data;
+}
+
+static void wave6_print_reg_err(struct vpu_core_device *core, u32 fail_reason)
+{
+	void *caller = __builtin_return_address(0);
+	struct device *dev = core->dev;
+
+	switch (fail_reason) {
+	case WAVE6_SYSERR_QUEUEING_FAIL:
+		dev_dbg(dev, "%pS: queueing failure 0x%x\n", caller, fail_reason);
+		break;
+	case WAVE6_SYSERR_RESULT_NOT_READY:
+		dev_err(dev, "%pS: result not ready 0x%x\n", caller, fail_reason);
+		break;
+	case WAVE6_SYSERR_ACCESS_VIOLATION_HW:
+		dev_err(dev, "%pS: access violation 0x%x\n", caller, fail_reason);
+		break;
+	case WAVE6_SYSERR_WATCHDOG_TIMEOUT:
+		dev_err(dev, "%pS: watchdog timeout 0x%x\n", caller, fail_reason);
+		break;
+	case WAVE6_SYSERR_BUS_ERROR:
+		dev_err(dev, "%pS: bus error 0x%x\n", caller, fail_reason);
+		break;
+	case WAVE6_SYSERR_DOUBLE_FAULT:
+		dev_err(dev, "%pS: double fault 0x%x\n", caller, fail_reason);
+		break;
+	case WAVE6_SYSERR_VPU_STILL_RUNNING:
+		dev_err(dev, "%pS: still running 0x%x\n", caller, fail_reason);
+		break;
+	default:
+		dev_err(dev, "%pS: failure: 0x%x\n", caller, fail_reason);
+		break;
+	}
+}
+
+static void wave6_dec_set_display_buffer(struct vpu_instance *inst, struct frame_buffer fb)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	int index;
+
+	for (index = 0; index < WAVE6_MAX_FBS; index++) {
+		if (!p_dec_info->disp_buf[index].buf_y) {
+			p_dec_info->disp_buf[index] = fb;
+			p_dec_info->disp_buf[index].index = index;
+			break;
+		}
+	}
+}
+
+static struct frame_buffer wave6_dec_get_display_buffer(struct vpu_instance *inst,
+							dma_addr_t addr)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	int i;
+	struct frame_buffer fb;
+
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		if (p_dec_info->disp_buf[i].buf_y == addr)
+			return p_dec_info->disp_buf[i];
+	}
+
+	memset(&fb, 0, sizeof(struct frame_buffer));
+	fb.index = -1;
+
+	return fb;
+}
+
+static void wave6_dec_remove_display_buffer(struct vpu_instance *inst,
+					    dma_addr_t addr)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	int i;
+
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		if (p_dec_info->disp_buf[i].buf_y == addr) {
+			memset(&p_dec_info->disp_buf[i], 0, sizeof(struct frame_buffer));
+			break;
+		}
+	}
+}
+
+static enum chroma_format_idc get_chroma_format_idc(enum frame_buffer_format fmt)
+{
+	switch (fmt) {
+	case FORMAT_420:
+	case FORMAT_420_P10_16BIT_MSB:
+	case FORMAT_420_P10_16BIT_LSB:
+	case FORMAT_420_P10_32BIT_MSB:
+	case FORMAT_420_P10_32BIT_LSB:
+		return C_FMT_IDC_YUV420;
+	case FORMAT_422:
+	case FORMAT_422_P10_16BIT_MSB:
+	case FORMAT_422_P10_16BIT_LSB:
+	case FORMAT_422_P10_32BIT_MSB:
+	case FORMAT_422_P10_32BIT_LSB:
+	case FORMAT_YUYV:
+	case FORMAT_YVYU:
+	case FORMAT_UYVY:
+	case FORMAT_VYUY:
+	case FORMAT_YUYV_P10_16BIT_MSB:
+	case FORMAT_YVYU_P10_16BIT_MSB:
+	case FORMAT_UYVY_P10_16BIT_MSB:
+	case FORMAT_VYUY_P10_16BIT_MSB:
+	case FORMAT_YUYV_P10_16BIT_LSB:
+	case FORMAT_YVYU_P10_16BIT_LSB:
+	case FORMAT_UYVY_P10_16BIT_LSB:
+	case FORMAT_VYUY_P10_16BIT_LSB:
+	case FORMAT_YUYV_P10_32BIT_MSB:
+	case FORMAT_YVYU_P10_32BIT_MSB:
+	case FORMAT_UYVY_P10_32BIT_MSB:
+	case FORMAT_VYUY_P10_32BIT_MSB:
+	case FORMAT_YUYV_P10_32BIT_LSB:
+	case FORMAT_YVYU_P10_32BIT_LSB:
+	case FORMAT_UYVY_P10_32BIT_LSB:
+	case FORMAT_VYUY_P10_32BIT_LSB:
+		return C_FMT_IDC_YUV422;
+	case FORMAT_444:
+	case FORMAT_444_P10_16BIT_MSB:
+	case FORMAT_444_P10_16BIT_LSB:
+	case FORMAT_444_P10_32BIT_MSB:
+	case FORMAT_444_P10_32BIT_LSB:
+		return C_FMT_IDC_YUV444;
+	case FORMAT_400:
+	case FORMAT_400_P10_16BIT_MSB:
+	case FORMAT_400_P10_16BIT_LSB:
+	case FORMAT_400_P10_32BIT_MSB:
+	case FORMAT_400_P10_32BIT_LSB:
+	case FORMAT_YUV444_24BIT:
+		return C_FMT_IDC_YUV400;
+	case FORMAT_RGB_24BIT_PACKED:
+	case FORMAT_YUV444_24BIT_PACKED:
+	case FORMAT_RGB_32BIT_PACKED:
+	case FORMAT_RGB_P10_32BIT_PACKED:
+	case FORMAT_YUV444_32BIT_PACKED:
+	case FORMAT_YUV444_P10_32BIT_PACKED:
+		return C_FMT_IDC_RGB;
+	default:
+		return C_FMT_IDC_YUV400;
+	}
+}
+
+static int wave6_wait_vpu_busy(struct vpu_core_device *core, unsigned int addr)
+{
+	u32 data;
+
+	lockdep_assert_held(&core->hw_lock);
+
+	return read_poll_timeout(vpu_read_reg, data, !data,
+				 W6_VPU_POLL_DELAY_US, W6_VPU_POLL_TIMEOUT,
+				 false, core, addr);
+}
+
+void wave6_vpu_enable_interrupt(struct vpu_core_device *core)
+{
+	u32 data;
+
+	lockdep_assert_held(&core->hw_lock);
+
+	data = BIT(W6_INT_BIT_ENC_SET_PARAM);
+	data |= BIT(W6_INT_BIT_ENC_PIC);
+	data |= BIT(W6_INT_BIT_INIT_SEQ);
+	data |= BIT(W6_INT_BIT_DEC_PIC);
+	data |= BIT(W6_INT_BIT_BSBUF_ERROR);
+	data |= BIT(W6_INT_BIT_REQ_WORK_BUF);
+	vpu_write_reg(core, W6_VPU_VINT_ENABLE, data);
+}
+
+bool wave6_vpu_is_init(struct vpu_core_device *core)
+{
+	lockdep_assert_held(&core->hw_lock);
+
+	return vpu_read_reg(core, W6_VPU_VCPU_CUR_PC) != 0;
+}
+
+static u32 wave6_vpu_get_product_id(struct vpu_core_device *core)
+{
+	u32 product_id = PRODUCT_ID_NONE;
+	u32 val;
+
+	lockdep_assert_held(&core->hw_lock);
+
+	val = vpu_read_reg(core, W6_VPU_RET_PRODUCT_CODE);
+
+	switch (val) {
+	case WAVE617_CODE:
+		product_id = PRODUCT_ID_617;
+		break;
+	case WAVE627_CODE:
+		product_id = PRODUCT_ID_627;
+		break;
+	case WAVE633_CODE:
+	case WAVE637_CODE:
+	case WAVE663_CODE:
+	case WAVE677_CODE:
+		product_id = PRODUCT_ID_637;
+		break;
+	default:
+		dev_err(core->dev, "Invalid product (%x)\n", val);
+		break;
+	}
+
+	return product_id;
+}
+
+static void wave6_send_command(struct vpu_core_device *core, u32 id, u32 std, u32 cmd)
+{
+	u32 reg_val;
+
+	lockdep_assert_held(&core->hw_lock);
+
+	if (cmd == W6_CMD_CREATE_INSTANCE)
+		reg_val = INSTANCE_INFO_CODEC_STD(std);
+	else
+		reg_val = INSTANCE_INFO_CODEC_STD(std) | INSTANCE_INFO_ID(id);
+	vpu_write_reg(core, W6_CMD_INSTANCE_INFO, reg_val);
+
+	vpu_write_reg(core, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+	vpu_write_reg(core, W6_COMMAND, cmd);
+	vpu_write_reg(core, W6_VPU_HOST_INT_REQ, HOST_INT_REQ_ON);
+
+	trace_wave6_vpu_send_command(core, id, std, cmd);
+}
+
+static int wave6_send_query(struct vpu_core_device *core, u32 id, u32 std,
+			    enum wave6_query_option query_opt)
+{
+	int ret;
+	u32 reg_val;
+
+	lockdep_assert_held(&core->hw_lock);
+
+	vpu_write_reg(core, W6_QUERY_OPTION, query_opt);
+	wave6_send_command(core, id, std, W6_CMD_QUERY);
+
+	ret = wave6_wait_vpu_busy(core, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(core->dev, "query timed out opt=0x%x\n", query_opt);
+		return ret;
+	}
+
+	if (!vpu_read_reg(core, W6_RET_SUCCESS)) {
+		reg_val = vpu_read_reg(core, W6_RET_FAIL_REASON);
+		wave6_print_reg_err(core, reg_val);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int wave6_vpu_get_version(struct vpu_core_device *core)
+{
+	struct vpu_attr *attr = &core->attr;
+	u32 reg_val;
+	u8 *str;
+	int ret;
+	u32 std_def1, conf_feature;
+
+	lockdep_assert_held(&core->hw_lock);
+
+	ret = wave6_send_query(core, 0, 0, W6_QUERY_OPT_GET_VPU_INFO);
+	if (ret)
+		return ret;
+
+	reg_val = vpu_read_reg(core, W6_RET_PRODUCT_NAME);
+	str = (u8 *)&reg_val;
+	attr->product_name[0] = str[3];
+	attr->product_name[1] = str[2];
+	attr->product_name[2] = str[1];
+	attr->product_name[3] = str[0];
+	attr->product_name[4] = 0;
+
+	attr->product_id = wave6_vpu_get_product_id(core);
+	attr->product_code = vpu_read_reg(core, W6_VPU_RET_PRODUCT_CODE);
+	attr->product_version = vpu_read_reg(core, W6_RET_PRODUCT_VERSION);
+	attr->fw_version = vpu_read_reg(core, W6_RET_FW_API_VERSION);
+	attr->fw_revision = vpu_read_reg(core, W6_RET_FW_VERSION);
+	attr->hw_version = vpu_read_reg(core, W6_RET_CONF_HW_VERSION);
+	std_def1 = vpu_read_reg(core, W6_RET_STD_DEF1);
+	conf_feature = vpu_read_reg(core, W6_RET_CONF_FEATURE);
+
+	attr->support_decoders = 0;
+	attr->support_encoders = 0;
+	attr->support_decoders |= STD_DEF1_HEVC_DEC(std_def1) << W_HEVC_DEC;
+	attr->support_hevc10bit_dec = CONF_FEATURE_HEVC10BIT_DEC(conf_feature);
+	attr->support_decoders |= STD_DEF1_AVC_DEC(std_def1) << W_AVC_DEC;
+	attr->support_avc10bit_dec = CONF_FEATURE_AVC10BIT_DEC(conf_feature);
+	attr->support_encoders |= STD_DEF1_HEVC_ENC(std_def1) << W_HEVC_ENC;
+	attr->support_hevc10bit_enc = CONF_FEATURE_HEVC10BIT_ENC(conf_feature);
+	attr->support_encoders |= STD_DEF1_AVC_ENC(std_def1) << W_AVC_ENC;
+	attr->support_avc10bit_enc = CONF_FEATURE_AVC10BIT_ENC(conf_feature);
+
+	return 0;
+}
+
+int wave6_vpu_build_up_dec_param(struct vpu_instance *inst,
+				 struct dec_open_param *param)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	u32 reg_val;
+	int ret;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	p_dec_info->sec_axi.use_dec_ip = true;
+	p_dec_info->sec_axi.use_dec_lf_row = true;
+	switch (inst->std) {
+	case W_HEVC_DEC:
+		p_dec_info->seq_change_mask = SEQ_CHANGE_ENABLE_ALL_HEVC;
+		break;
+	case W_AVC_DEC:
+		p_dec_info->seq_change_mask = SEQ_CHANGE_ENABLE_ALL_AVC;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_TEMP_BASE, param->inst_buffer.temp_base);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_TEMP_SIZE, param->inst_buffer.temp_size);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_BS_PARAM, VPU_STREAM_ENDIAN);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_ADDR_EXT, param->ext_addr_vcpu);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_DISP_MODE, param->disp_mode);
+
+	reg_val = CREATE_INST_CORE_INFO_CQ_DEPTH(COMMAND_QUEUE_DEPTH) |
+		  CREATE_INST_CORE_INFO_CORE_IDC(SINGLE_CORE_IDC) |
+		  CREATE_INST_CORE_INFO_CORE_NUM(SINGLE_CORE);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_CORE_INFO, reg_val);
+
+	reg_val = CREATE_INST_PRIORITY_SECURITY_FLAG(param->is_secure_inst) |
+		  CREATE_INST_PRIORITY_VALUE(param->inst_priority);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_PRIORITY, reg_val);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_TIMEOUT_CYCLE_COUNT,
+		      W6_VPU_TIMEOUT_CYCLE_COUNT);
+
+	wave6_send_command(inst->dev, 0, inst->std, W6_CMD_CREATE_INSTANCE);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		u32 reason_code = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+
+		wave6_print_reg_err(inst->dev, reason_code);
+		return -EIO;
+	}
+
+	inst->id = vpu_read_reg(inst->dev, W6_RET_INSTANCE_ID);
+
+	return 0;
+}
+
+int wave6_vpu_dec_issue_seq_init(struct vpu_instance *inst)
+{
+	struct dec_info *p_dec_info;
+	u32 reg_val;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_dec_info = &inst->codec_info->dec_info;
+
+	vpu_write_reg(inst->dev, W6_CMD_DEC_INIT_SEQ_BS_RD_PTR, p_dec_info->stream_rd_ptr);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_INIT_SEQ_BS_WR_PTR, p_dec_info->stream_wr_ptr);
+
+	reg_val = DEC_PIC_BS_OPTION_STREAM_END(p_dec_info->stream_end) |
+		  DEC_PIC_BS_OPTION_EXPLICIT_END_ON;
+	vpu_write_reg(inst->dev, W6_CMD_DEC_INIT_SEQ_BS_OPTION, reg_val);
+	reg_val = INIT_SEQ_OPTION_MODE(W6_INIT_SEQ_OPT_NORMAL);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_INIT_SEQ_OPTION, reg_val);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_INIT_SEQ);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		u32 reason_code = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+
+		wave6_print_reg_err(inst->dev, reason_code);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void wave6_get_dec_seq_result(struct vpu_instance *inst, struct dec_seq_info *info)
+{
+	u32 reg_val;
+	u32 profile, profile_comp;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	info->rd_ptr = wave6_vpu_dec_get_rd_ptr(inst);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_PIC_SIZE);
+	info->pic_width = DEC_PIC_SIZE_WIDTH(reg_val);
+	info->pic_height = DEC_PIC_SIZE_HEIGHT(reg_val);
+
+	info->min_frame_buffer_count = vpu_read_reg(inst->dev, W6_RET_DEC_NUM_REQUIRED_FBC_FB);
+	info->frame_buf_delay = vpu_read_reg(inst->dev, W6_RET_DEC_NUM_REORDER_DELAY);
+	info->req_mv_buffer_count = vpu_read_reg(inst->dev, W6_RET_DEC_NUM_REQUIRED_COL_BUF);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_CROP_TOP_BOTTOM);
+	info->pic_crop_rect.top = DEC_CROP_TOP(reg_val);
+	info->pic_crop_rect.bottom = info->pic_height - DEC_CROP_BOTTOM(reg_val);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_CROP_LEFT_RIGHT);
+	info->pic_crop_rect.left = DEC_CROP_LEFT(reg_val);
+	info->pic_crop_rect.right = info->pic_width - DEC_CROP_RIGHT(reg_val);
+
+	info->f_rate_numerator = vpu_read_reg(inst->dev, W6_RET_DEC_FRAME_RATE_NR);
+	info->f_rate_denominator = vpu_read_reg(inst->dev, W6_RET_DEC_FRAME_RATE_DR);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_COLOR_SAMPLE);
+	info->luma_bit_depth = DEC_COLOR_SAMPLE_L_BIT_DEPTH(reg_val);
+	info->chroma_bit_depth = DEC_COLOR_SAMPLE_C_BIT_DEPTH(reg_val);
+	info->c_fmt_idc = DEC_COLOR_SAMPLE_C_FMT_IDC(reg_val);
+	info->aspect_rate_info = DEC_COLOR_SAMPLE_ASPECT_RATIO_IDC(reg_val);
+	info->is_ext_sar = info->aspect_rate_info == H264_VUI_SAR_IDC_EXTENDED;
+	if (info->is_ext_sar)
+		info->aspect_rate_info = vpu_read_reg(inst->dev, W6_RET_DEC_ASPECT_RATIO);
+	info->bitrate = vpu_read_reg(inst->dev, W6_RET_DEC_BIT_RATE);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_SEQ_PARAM);
+	info->level = DEC_SEQ_PARAM_LEVEL(reg_val);
+	info->tier = DEC_SEQ_PARAM_TIER(reg_val);
+
+	profile = DEC_SEQ_PARAM_PROFILE(reg_val);
+	profile_comp = DEC_SEQ_PARAM_PROFILE_COMPATIBILITY(reg_val);
+	if (inst->std == W_HEVC_DEC) {
+		info->profile = profile;
+		if (!info->profile) {
+			if ((profile_comp & PROFILE_COMPATIBILITY_MAIN) &&
+			    (profile_comp & PROFILE_COMPATIBILITY_MAIN10))
+				info->profile = HEVC_PROFILE_MAIN;
+			else if (profile_comp & PROFILE_COMPATIBILITY_MAIN10)
+				info->profile = HEVC_PROFILE_MAIN10;
+			else if (profile_comp & PROFILE_COMPATIBILITY_STILL_PICTURE)
+				info->profile = HEVC_PROFILE_STILLPICTURE;
+			else
+				info->profile = HEVC_PROFILE_MAIN;
+		}
+	} else if (inst->std == W_AVC_DEC) {
+		if (profile == PROFILE_H264_BP)
+			info->profile = H264_PROFILE_BP;
+		else if (profile == PROFILE_H264_HP)
+			info->profile = H264_PROFILE_HP;
+		else if (profile == PROFILE_H264_MP)
+			info->profile = H264_PROFILE_MP;
+		else if (profile == PROFILE_H264_HIGH10)
+			info->profile = H264_PROFILE_HIGH10;
+		else if (profile == PROFILE_H264_EXTENDED)
+			info->profile = H264_PROFILE_EXTENDED;
+		else
+			info->profile = H264_PROFILE_BP;
+	}
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_COLOR_CONFIG);
+	if (reg_val) {
+		info->color.video_signal_type_present = true;
+		info->color.color_description_present = DEC_COLOR_CONFIG_COLOR_PRESENT(reg_val);
+		info->color.color_primaries = DEC_COLOR_CONFIG_COLOR_PRIMARIES(reg_val);
+		info->color.transfer_characteristics = DEC_COLOR_CONFIG_TRANS_CHAR(reg_val);
+		info->color.matrix_coefficients = DEC_COLOR_CONFIG_MATRIX_COEFF(reg_val);
+		info->color.color_range = DEC_COLOR_CONFIG_COLOR_RANGE(reg_val);
+	} else {
+		info->color.video_signal_type_present = false;
+	}
+}
+
+int wave6_vpu_dec_get_seq_info(struct vpu_instance *inst, struct dec_seq_info *info)
+{
+	int ret;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	ret = wave6_send_query(inst->dev, inst->id, inst->std, W6_QUERY_OPT_GET_RESULT);
+	if (ret)
+		return ret;
+
+	if (vpu_read_reg(inst->dev, W6_RET_DEC_DECODING_SUCCESS) != 1) {
+		info->err_reason = vpu_read_reg(inst->dev, W6_RET_DEC_ERR_INFO);
+		ret = -EIO;
+	} else {
+		info->warn_info = vpu_read_reg(inst->dev, W6_RET_DEC_WARN_INFO);
+	}
+
+	wave6_get_dec_seq_result(inst, info);
+
+	return ret;
+}
+
+int wave6_vpu_dec_register_frame_buffer(struct vpu_instance *inst)
+{
+	struct dec_info *p_dec_info;
+	struct frame_buffer *fb_arr;
+	size_t fbc_remain, mv_remain, fbc_idx = 0, mv_idx = 0;
+	size_t i, k, group_num, fbc_count, mv_count;
+	dma_addr_t fbc_cr_tbl_addr;
+	u32 reg_val;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_dec_info = &inst->codec_info->dec_info;
+
+	fb_arr = &inst->frame_buf[0];
+	fbc_count = p_dec_info->seq_info.min_frame_buffer_count;
+	mv_count = p_dec_info->seq_info.req_mv_buffer_count;
+
+	for (i = 0; i < fbc_count; i++) {
+		if (!inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i].daddr)
+			return -EINVAL;
+		if (!inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i].daddr)
+			return -EINVAL;
+	}
+	for (i = 0; i < mv_count; i++) {
+		if (!inst->aux_vbuf[AUX_BUF_MV_COL][i].daddr)
+			return -EINVAL;
+	}
+
+	reg_val = SET_FB_PIC_SIZE_WIDTH(p_dec_info->seq_info.pic_width) |
+		  SET_FB_PIC_SIZE_HEIGHT(p_dec_info->seq_info.pic_height);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_PIC_SIZE, reg_val);
+	reg_val = SET_FB_PIC_INFO_C_FMT_IDC(p_dec_info->seq_info.c_fmt_idc) |
+		  SET_FB_PIC_INFO_L_BIT_DEPTH(p_dec_info->seq_info.luma_bit_depth) |
+		  SET_FB_PIC_INFO_C_BIT_DEPTH(p_dec_info->seq_info.chroma_bit_depth);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_PIC_INFO, reg_val);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_DEFAULT_CDF, 0);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_SEGMAP, 0);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_MV_COL_PRE_ENT, 0);
+
+	fbc_remain = fbc_count;
+	mv_remain = mv_count;
+	group_num = (fbc_count > mv_count) ? ((ALIGN(fbc_count, 16) / 16) - 1) :
+					     ((ALIGN(mv_count, 16) / 16) - 1);
+	for (i = 0; i <= group_num; i++) {
+		bool first_group = i == 0;
+		bool last_group = i == group_num;
+		u32 set_fbc_num = (fbc_remain >= 16) ? 16 : fbc_remain;
+		u32 set_mv_num = (mv_remain >= 16) ? 16 : mv_remain;
+		u32 fbc_start_no = i * 16;
+		u32 fbc_end_no = fbc_start_no + set_fbc_num - 1;
+		u32 mv_start_no = i * 16;
+		u32 mv_end_no = mv_start_no + set_mv_num - 1;
+
+		reg_val = SET_FB_OPTION_ENDIAN(VDI_128BIT_BIG_ENDIAN);
+		if (first_group)
+			reg_val |= SET_FB_OPTION_START;
+		if (last_group)
+			reg_val |= SET_FB_OPTION_END;
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_OPTION, reg_val);
+
+		reg_val = SET_FB_NUM_FBC_START_IDX(fbc_start_no) |
+			  SET_FB_NUM_FBC_END_IDX(fbc_end_no) |
+			  SET_FB_NUM_MV_COL_START_IDX(mv_start_no) |
+			  SET_FB_NUM_MV_COL_END_IDX(mv_end_no);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_NUM, reg_val);
+
+		for (k = 0; k < set_fbc_num; k++) {
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_Y(k),
+				      fb_arr[fbc_idx].buf_y);
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_C(k),
+				      fb_arr[fbc_idx].buf_cb);
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_CR(k),
+				      fb_arr[fbc_idx].buf_cr);
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_Y_OFFSET(k),
+				      inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][fbc_idx].daddr);
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_C_OFFSET(k),
+				      inst->aux_vbuf[AUX_BUF_FBC_C_TBL][fbc_idx].daddr);
+			fbc_cr_tbl_addr = inst->aux_vbuf[AUX_BUF_FBC_C_TBL][fbc_idx].daddr +
+					  (inst->aux_vbuf[AUX_BUF_FBC_C_TBL][fbc_idx].size >> 1);
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_CR_OFFSET(k),
+				      fbc_cr_tbl_addr);
+			fbc_idx++;
+		}
+		fbc_remain -= k;
+
+		for (k = 0; k < set_mv_num; k++) {
+			vpu_write_reg(inst->dev, W6_CMD_SET_FB_MV_COL(k),
+				      inst->aux_vbuf[AUX_BUF_MV_COL][mv_idx].daddr);
+			mv_idx++;
+		}
+		mv_remain -= k;
+
+		wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_SET_FB);
+		ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+		if (ret) {
+			dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+			return ret;
+		}
+
+		if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS))
+			return -EIO;
+	}
+
+	return 0;
+}
+
+int wave6_vpu_dec_register_display_buffer(struct vpu_instance *inst, struct frame_buffer fb)
+{
+	int ret;
+	struct dec_info *p_dec_info;
+	u32 reg_val;
+	u32 c_fmt_idc, out_fmt, out_mode;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_dec_info = &inst->codec_info->dec_info;
+
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_SCL_PARAM,
+		      inst->scaler_info.enable);
+	reg_val = SET_DISP_SCL_PIC_SIZE_WIDTH(inst->scaler_info.width) |
+		  SET_DISP_SCL_PIC_SIZE_HEIGHT(inst->scaler_info.height);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_SCL_PIC_SIZE, reg_val);
+	reg_val = SET_DISP_PIC_SIZE_WIDTH(p_dec_info->seq_info.pic_width) |
+		  SET_DISP_PIC_SIZE_HEIGHT(p_dec_info->seq_info.pic_height);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_PIC_SIZE, reg_val);
+
+	c_fmt_idc = get_chroma_format_idc(p_dec_info->wtl_format);
+	switch (p_dec_info->wtl_format) {
+	case FORMAT_420_P10_16BIT_MSB:
+	case FORMAT_422_P10_16BIT_MSB:
+	case FORMAT_444_P10_16BIT_MSB:
+	case FORMAT_400_P10_16BIT_MSB:
+		out_mode = (WTL_RIGHT_JUSTIFIED << 2) | WTL_PIXEL_16BIT;
+		break;
+	case FORMAT_420_P10_16BIT_LSB:
+	case FORMAT_422_P10_16BIT_LSB:
+	case FORMAT_444_P10_16BIT_LSB:
+	case FORMAT_400_P10_16BIT_LSB:
+		out_mode = (WTL_LEFT_JUSTIFIED << 2) | WTL_PIXEL_16BIT;
+		break;
+	case FORMAT_420_P10_32BIT_MSB:
+	case FORMAT_422_P10_32BIT_MSB:
+	case FORMAT_444_P10_32BIT_MSB:
+	case FORMAT_400_P10_32BIT_MSB:
+		out_mode = (WTL_RIGHT_JUSTIFIED << 2) | WTL_PIXEL_32BIT;
+		break;
+	case FORMAT_420_P10_32BIT_LSB:
+	case FORMAT_422_P10_32BIT_LSB:
+	case FORMAT_444_P10_32BIT_LSB:
+	case FORMAT_400_P10_32BIT_LSB:
+		out_mode = (WTL_LEFT_JUSTIFIED << 2) | WTL_PIXEL_32BIT;
+		break;
+	default:
+		out_mode = (WTL_RIGHT_JUSTIFIED << 2) | WTL_PIXEL_8BIT;
+		break;
+	}
+	out_fmt = (inst->nv21 << 1) | inst->cbcr_interleave;
+
+	reg_val = SET_DISP_COMMON_PIC_INFO_BWB_ON |
+		  SET_DISP_COMMON_PIC_INFO_C_FMT_IDC(c_fmt_idc) |
+		  SET_DISP_COMMON_PIC_INFO_PIXEL_ORDER(PIXEL_ORDER_INCREASING) |
+		  SET_DISP_COMMON_PIC_INFO_OUT_MODE(out_mode) |
+		  SET_DISP_COMMON_PIC_INFO_OUT_FMT(out_fmt) |
+		  SET_DISP_COMMON_PIC_INFO_STRIDE(fb.stride);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_COMMON_PIC_INFO, reg_val);
+	reg_val = SET_DISP_OPTION_ENDIAN(VDI_128BIT_BIG_ENDIAN);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_OPTION, reg_val);
+	reg_val = SET_DISP_PIC_INFO_L_BIT_DEPTH(fb.luma_bit_depth) |
+		  SET_DISP_PIC_INFO_C_BIT_DEPTH(fb.chroma_bit_depth) |
+		  SET_DISP_PIC_INFO_C_FMT_IDC(fb.c_fmt_idc);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_PIC_INFO, reg_val);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_Y_BASE, fb.buf_y);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_CB_BASE, fb.buf_cb);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_CR_BASE, fb.buf_cr);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_DEC_SET_DISP);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS))
+		return -EIO;
+
+	wave6_dec_set_display_buffer(inst, fb);
+
+	return 0;
+}
+
+int wave6_vpu_dec_start_one_frame(struct vpu_instance *inst, struct dec_param *param,
+				  u32 *fail_res)
+{
+	struct dec_info *p_dec_info;
+	u32 reg_val;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_dec_info = &inst->codec_info->dec_info;
+
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_BS_RD_PTR, p_dec_info->stream_rd_ptr);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_BS_WR_PTR, p_dec_info->stream_wr_ptr);
+
+	reg_val = DEC_PIC_BS_OPTION_STREAM_END(p_dec_info->stream_end) |
+		  DEC_PIC_BS_OPTION_EXPLICIT_END_ON;
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_BS_OPTION, reg_val);
+
+	reg_val = DEC_PIC_SEC_AXI_IP_ENABLE(p_dec_info->sec_axi.use_dec_ip) |
+		  DEC_PIC_SEC_AXI_LF_ENABLE(p_dec_info->sec_axi.use_dec_lf_row);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_SEC_AXI, reg_val);
+
+	reg_val = DEC_PIC_OPTION_MODE(W6_DEC_PIC_OPT_NORMAL);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_OPTION, reg_val);
+
+	reg_val = DEC_PIC_TEMPORAL_ID_PLUS1_SPATIAL(DECODE_ALL_SPATIAL_LAYERS) |
+		  DEC_PIC_TEMPORAL_ID_PLUS1(DECODE_ALL_TEMPORAL_LAYERS);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_TEMPORAL_ID_PLUS1, reg_val);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_SEQ_CHANGE_ENABLE_FLAG,
+		      p_dec_info->seq_change_mask);
+	reg_val = lower_32_bits(param->timestamp);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_TIMESTAMP_LOW, reg_val);
+	reg_val = upper_32_bits(param->timestamp);
+	vpu_write_reg(inst->dev, W6_CMD_DEC_PIC_TIMESTAMP_HIGH, reg_val);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_DEC_PIC);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		*fail_res = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+		wave6_print_reg_err(inst->dev, *fail_res);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int wave6_vpu_dec_get_output_info(struct vpu_instance *inst, struct dec_output_info *info)
+{
+	struct dec_info *p_dec_info;
+	u32 reg_val, i;
+	int decoded_idx = -1, disp_idx = -1;
+	int ret;
+
+	if (WARN_ON(!info))
+		return -EINVAL;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_dec_info = &inst->codec_info->dec_info;
+
+	ret = wave6_send_query(inst->dev, inst->id, inst->std, W6_QUERY_OPT_GET_RESULT);
+	if (ret) {
+		info->rd_ptr = p_dec_info->stream_rd_ptr;
+		info->wr_ptr = p_dec_info->stream_wr_ptr;
+		return ret;
+	}
+
+	info->decoding_success = vpu_read_reg(inst->dev, W6_RET_DEC_DECODING_SUCCESS);
+	if (!info->decoding_success)
+		info->error_reason = vpu_read_reg(inst->dev, W6_RET_DEC_ERR_INFO);
+	else
+		info->warn_info = vpu_read_reg(inst->dev, W6_RET_DEC_WARN_INFO);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_PIC_TYPE);
+	info->ctu_size = DEC_PIC_TYPE_CTU_SIZE(reg_val);
+	info->nal_type = DEC_PIC_TYPE_NAL_UNIT_TYPE(reg_val);
+
+	if (reg_val & DEC_PIC_TYPE_B)
+		info->pic_type = PIC_TYPE_B;
+	else if (reg_val & DEC_PIC_TYPE_P)
+		info->pic_type = PIC_TYPE_P;
+	else if (reg_val & DEC_PIC_TYPE_I)
+		info->pic_type = PIC_TYPE_I;
+	else
+		info->pic_type = PIC_TYPE_MAX;
+	if (inst->std == W_HEVC_DEC) {
+		if (info->pic_type == PIC_TYPE_I &&
+		    (info->nal_type == H265_NAL_UNIT_TYPE_IDR_W_RADL ||
+		     info->nal_type == H265_NAL_UNIT_TYPE_IDR_N_LP))
+			info->pic_type = PIC_TYPE_IDR;
+	} else if (inst->std == W_AVC_DEC) {
+		if (info->pic_type == PIC_TYPE_I &&
+		    info->nal_type == H264_NAL_UNIT_TYPE_IDR_PICTURE)
+			info->pic_type = PIC_TYPE_IDR;
+	}
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_DECODED_FLAG);
+	if (reg_val) {
+		struct frame_buffer fb;
+		dma_addr_t addr = vpu_read_reg(inst->dev, W6_RET_DEC_DECODED_ADDR);
+
+		fb = wave6_dec_get_display_buffer(inst, addr);
+		info->frame_decoded_addr = addr;
+		info->frame_decoded = true;
+		decoded_idx = fb.index;
+	}
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_DISPLAY_FLAG);
+	if (reg_val) {
+		struct frame_buffer fb;
+		dma_addr_t addr = vpu_read_reg(inst->dev, W6_RET_DEC_DISPLAY_ADDR);
+
+		fb = wave6_dec_get_display_buffer(inst, addr);
+		info->frame_display_addr = addr;
+		info->frame_display = true;
+		disp_idx = fb.index;
+	}
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_DISP_IDC);
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		if (reg_val & (1 << i)) {
+			dma_addr_t addr;
+
+			addr = vpu_read_reg(inst->dev, W6_RET_DEC_DISP_LINEAR_ADDR(i));
+
+			info->disp_frame_addr[info->disp_frame_num] = addr;
+			info->disp_frame_num++;
+		}
+	}
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_RELEASE_IDC);
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		if (reg_val & (1 << i)) {
+			dma_addr_t addr;
+
+			addr = vpu_read_reg(inst->dev, W6_RET_DEC_DISP_LINEAR_ADDR(i));
+
+			wave6_dec_remove_display_buffer(inst, addr);
+			info->release_disp_frame_addr[info->release_disp_frame_num] = addr;
+			info->release_disp_frame_num++;
+		}
+	}
+
+	if (inst->std == W_HEVC_DEC) {
+		info->decoded_poc = -1;
+		info->display_poc = -1;
+		if (decoded_idx >= 0)
+			info->decoded_poc = vpu_read_reg(inst->dev, W6_RET_DEC_PIC_POC);
+	} else if (inst->std == W_AVC_DEC) {
+		info->decoded_poc = -1;
+		info->display_poc = -1;
+		if (decoded_idx >= 0)
+			info->decoded_poc = vpu_read_reg(inst->dev, W6_RET_DEC_PIC_POC);
+	}
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_PIC_SIZE);
+	info->dec_pic_width = DEC_PIC_SIZE_WIDTH(reg_val);
+	info->dec_pic_height = DEC_PIC_SIZE_HEIGHT(reg_val);
+
+	info->rd_ptr = wave6_vpu_dec_get_rd_ptr(inst);
+	info->wr_ptr = p_dec_info->stream_wr_ptr;
+	info->byte_pos_frame_start = vpu_read_reg(inst->dev, W6_RET_DEC_AU_START_POS);
+	info->byte_pos_frame_end = vpu_read_reg(inst->dev, W6_RET_DEC_AU_END_POS);
+	info->stream_end = vpu_read_reg(inst->dev, W6_RET_DEC_STREAM_END);
+	info->notification_flags = vpu_read_reg(inst->dev, W6_RET_DEC_NOTIFICATION);
+	info->last_frame_in_au = vpu_read_reg(inst->dev, W6_RET_DEC_LAST_FRAME_FLAG);
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_DEC_TIMESTAMP_LOW);
+	info->timestamp = vpu_read_reg(inst->dev, W6_RET_DEC_TIMESTAMP_HIGH);
+	info->timestamp = (info->timestamp << 32) | reg_val;
+
+	info->cycle.host_cmd_s = vpu_read_reg(inst->dev, W6_RET_CQ_IN_TICK);
+	info->cycle.host_cmd_e = vpu_read_reg(inst->dev, W6_RET_RQ_OUT_TICK);
+	info->cycle.proc_s = vpu_read_reg(inst->dev, W6_RET_HW_RUN_TICK);
+	info->cycle.proc_e = vpu_read_reg(inst->dev, W6_RET_HW_DONE_TICK);
+	info->cycle.vpu_s = vpu_read_reg(inst->dev, W6_RET_FW_RUN_TICK);
+	info->cycle.vpu_e = vpu_read_reg(inst->dev, W6_RET_FW_DONE_TICK);
+	info->cycle.frame_cycle = (info->cycle.vpu_e - info->cycle.host_cmd_s) *
+				  CYCLE_PER_TICK_VALUE;
+	info->cycle.proc_cycle = (info->cycle.proc_e - info->cycle.proc_s) *
+				 CYCLE_PER_TICK_VALUE;
+	info->cycle.vpu_cycle = ((info->cycle.vpu_e - info->cycle.vpu_s) -
+				 (info->cycle.proc_e - info->cycle.proc_s)) *
+				 CYCLE_PER_TICK_VALUE;
+
+	if (decoded_idx >= 0 && decoded_idx < WAVE6_MAX_FBS)
+		p_dec_info->dec_out_info[decoded_idx].decoded_poc = info->decoded_poc;
+
+	if (disp_idx >= 0 && disp_idx < WAVE6_MAX_FBS) {
+		info->display_poc = p_dec_info->dec_out_info[disp_idx].decoded_poc;
+		info->disp_pic_width = p_dec_info->dec_out_info[disp_idx].dec_pic_width;
+		info->disp_pic_height = p_dec_info->dec_out_info[disp_idx].dec_pic_height;
+	}
+
+	info->sequence_no = p_dec_info->seq_info.sequence_no;
+	if (decoded_idx >= 0 && decoded_idx < WAVE6_MAX_FBS)
+		p_dec_info->dec_out_info[decoded_idx] = *info;
+
+	if (info->notification_flags & DEC_NOTI_FLAG_SEQ_CHANGE) {
+		wave6_get_dec_seq_result(inst, &p_dec_info->seq_info);
+		p_dec_info->seq_info.sequence_no++;
+	}
+
+	return 0;
+}
+
+int wave6_vpu_dec_fini_seq(struct vpu_instance *inst, u32 *fail_res)
+{
+	int ret;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_DESTROY_INSTANCE);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret)
+		return -ETIMEDOUT;
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		*fail_res = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+		wave6_print_reg_err(inst->dev, *fail_res);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+dma_addr_t wave6_vpu_dec_get_rd_ptr(struct vpu_instance *inst)
+{
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	return vpu_read_reg(inst->dev, W6_RET_DEC_BS_RD_PTR);
+}
+
+int wave6_vpu_dec_flush_instance(struct vpu_instance *inst)
+{
+	struct dec_info *p_dec_info;
+	int ret, index;
+	u32 remove_idc;
+	u32 using_idc;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_dec_info = &inst->codec_info->dec_info;
+	if (!p_dec_info->seq_info_obtained)
+		return -EINVAL;
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_FLUSH_INSTANCE);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret)
+		return -ETIMEDOUT;
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		u32 reg_val;
+
+		reg_val = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+		wave6_print_reg_err(inst->dev, reg_val);
+		return -EIO;
+	}
+
+	ret = wave6_send_query(inst->dev, inst->id, inst->std, W6_QUERY_OPT_GET_FLUSH_CMD_INFO);
+	if (ret)
+		return ret;
+
+	remove_idc = vpu_read_reg(inst->dev, W6_RET_DEC_FLUSH_CMD_BUF_STATE_UNUSED_IDC) |
+		     vpu_read_reg(inst->dev, W6_RET_DEC_FLUSH_CMD_BUF_STATE_USED_IDC);
+	if (remove_idc)
+		dev_dbg(inst->dev->dev, "%s: remove_idc 0x%x\n", __func__, remove_idc);
+
+	using_idc = vpu_read_reg(inst->dev, W6_RET_DEC_FLUSH_CMD_BUF_STATE_USING_IDC);
+	if (using_idc)
+		dev_dbg(inst->dev->dev, "%s: using_idc 0x%x\n", __func__, using_idc);
+
+	for (index = 0; index < WAVE6_MAX_FBS; index++) {
+		dma_addr_t addr;
+
+		if (!((remove_idc >> index) & 0x1))
+			continue;
+
+		addr = vpu_read_reg(inst->dev, W6_RET_DEC_FLUSH_CMD_DISP_ADDR(index));
+		wave6_dec_remove_display_buffer(inst, addr);
+	}
+
+	return 0;
+}
+
+struct enc_cmd_set_param_reg {
+	u32 enable;
+	u32 src_size;
+	u32 custom_map_endian;
+	u32 sps;
+	u32 pps;
+	u32 gop;
+	u32 intra;
+	u32 conf_win0;
+	u32 conf_win1;
+	u32 rdo;
+	u32 slice;
+	u32 intra_refresh;
+	u32 intra_qp;
+	u32 rc_frame_rate;
+	u32 rc_target_rate;
+	u32 rc;
+	u32 hvs;
+	u32 rc_max_bitrate;
+	u32 rc_vbv_buffer_size;
+	u32 inter_qp;
+	u32 rot;
+	u32 num_units_in_tick;
+	u32 time_scale;
+	u32 num_ticks_poc_diff_one;
+	u32 max_intra_pic_bit;
+	u32 max_inter_pic_bit;
+	u32 bg;
+	u32 non_vcl_param;
+	u32 vui_rbsp_addr;
+	u32 hrd_rbsp_addr;
+	u32 qround_offset;
+	u32 quant1;
+	u32 quant2;
+	u32 custom_gop;
+	u32 custom_gop_pic[MAX_CUSTOM_GOP_NUM];
+	u32 tile_param;
+	u32 custom_lambda[MAX_CUSTOM_LAMBDA_NUM];
+	u32 temp_layer_qp[MAX_NUM_CHANGEABLE_TEMP_LAYER];
+	u32 scaler_size;
+	u32 scaler;
+	u32 color;
+	u32 sar;
+	u32 sar_extended;
+};
+
+struct enc_cmd_change_param_reg {
+	u32 enable;
+	u32 rc_target_rate;
+};
+
+int wave6_vpu_build_up_enc_param(struct device *dev, struct vpu_instance *inst,
+				 struct enc_open_param *param)
+{
+	struct enc_info *p_enc_info = &inst->codec_info->enc_info;
+	u32 reg_val;
+	int ret;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	p_enc_info->seq_info_obtained = false;
+	p_enc_info->sec_axi.use_enc_rdo = true;
+	p_enc_info->sec_axi.use_enc_lf = true;
+	p_enc_info->mir_dir = param->mir_dir;
+	p_enc_info->rot_angle = param->rot_angle;
+
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_TEMP_BASE, param->inst_buffer.temp_base);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_TEMP_SIZE, param->inst_buffer.temp_size);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_AR_TABLE_BASE, param->inst_buffer.ar_base);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_BS_PARAM, VPU_STREAM_ENDIAN);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_SRC_OPT, 0);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_ADDR_EXT, param->ext_addr_vcpu);
+
+	reg_val = CREATE_INST_CORE_INFO_CQ_DEPTH(COMMAND_QUEUE_DEPTH) |
+		  CREATE_INST_CORE_INFO_CORE_IDC(SINGLE_CORE_IDC) |
+		  CREATE_INST_CORE_INFO_CORE_NUM(SINGLE_CORE);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_CORE_INFO, reg_val);
+
+	reg_val = CREATE_INST_PRIORITY_SECURITY_FLAG(param->is_secure_inst) |
+		  CREATE_INST_PRIORITY_VALUE(param->inst_priority);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_PRIORITY, reg_val);
+	vpu_write_reg(inst->dev, W6_CMD_CREATE_INST_TIMEOUT_CYCLE_COUNT,
+		      W6_VPU_TIMEOUT_CYCLE_COUNT);
+
+	wave6_send_command(inst->dev, 0, inst->std, W6_CMD_CREATE_INSTANCE);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		u32 reason_code = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+
+		wave6_print_reg_err(inst->dev, reason_code);
+		return -EIO;
+	}
+
+	inst->id = vpu_read_reg(inst->dev, W6_RET_INSTANCE_ID);
+
+	return 0;
+}
+
+static int wave6_set_enc_crop_info(u32 codec, struct enc_codec_param *param,
+				   u32 rot_mir, int width, int height)
+{
+	int aligned_width = (codec == W_HEVC_ENC) ? ALIGN(width, 32) : ALIGN(width, 16);
+	int aligned_height = (codec == W_HEVC_ENC) ? ALIGN(height, 32) : ALIGN(height, 16);
+	int pad_right, pad_bot;
+	int crop_right, crop_left, crop_top, crop_bot;
+
+	if (codec == W_HEVC_ENC &&
+	    (!rot_mir ||
+	     rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_MIRV | SET_PARAM_ROT_180)))
+		return 0;
+
+	pad_right = aligned_width - width;
+	pad_bot = aligned_height - height;
+
+	if (param->conf_win.right > 0)
+		crop_right = param->conf_win.right + pad_right;
+	else
+		crop_right = pad_right;
+
+	if (param->conf_win.bottom > 0)
+		crop_bot = param->conf_win.bottom + pad_bot;
+	else
+		crop_bot = pad_bot;
+
+	crop_top = param->conf_win.top;
+	crop_left = param->conf_win.left;
+
+	param->conf_win.top = crop_top;
+	param->conf_win.left = crop_left;
+	param->conf_win.bottom = crop_bot;
+	param->conf_win.right = crop_right;
+
+	if (rot_mir == SET_PARAM_ROT_90 ||
+	    rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_MIRV | SET_PARAM_ROT_270)) {
+		param->conf_win.top = crop_right;
+		param->conf_win.left = crop_top;
+		param->conf_win.bottom = crop_left;
+		param->conf_win.right = crop_bot;
+	} else if (rot_mir == SET_PARAM_ROT_180 ||
+		   rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_MIRV)) {
+		param->conf_win.top = crop_bot;
+		param->conf_win.left = crop_right;
+		param->conf_win.bottom = crop_top;
+		param->conf_win.right = crop_left;
+	} else if (rot_mir == SET_PARAM_ROT_270 ||
+		   rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_MIRV | SET_PARAM_ROT_90)) {
+		param->conf_win.top = crop_left;
+		param->conf_win.left = crop_bot;
+		param->conf_win.bottom = crop_right;
+		param->conf_win.right = crop_top;
+	} else if (rot_mir == SET_PARAM_ROT_MIRV ||
+		   rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_180)) {
+		param->conf_win.top = crop_bot;
+		param->conf_win.bottom = crop_top;
+	} else if (rot_mir == SET_PARAM_ROT_MIRH ||
+		   rot_mir == (SET_PARAM_ROT_MIRV | SET_PARAM_ROT_180)) {
+		param->conf_win.left = crop_right;
+		param->conf_win.right = crop_left;
+	} else if (rot_mir == (SET_PARAM_ROT_MIRV | SET_PARAM_ROT_90) ||
+		   rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_270)) {
+		param->conf_win.top = crop_left;
+		param->conf_win.left = crop_top;
+		param->conf_win.bottom = crop_right;
+		param->conf_win.right = crop_bot;
+	} else if (rot_mir == (SET_PARAM_ROT_MIRV | SET_PARAM_ROT_270) ||
+		   rot_mir == (SET_PARAM_ROT_MIRH | SET_PARAM_ROT_90)) {
+		param->conf_win.top = crop_right;
+		param->conf_win.left = crop_bot;
+		param->conf_win.bottom = crop_left;
+		param->conf_win.right = crop_top;
+	}
+
+	return 0;
+}
+
+static void wave6_update_enc_info(struct enc_info *p_enc_info)
+{
+	struct enc_open_param op = p_enc_info->open_param;
+
+	p_enc_info->width = op.pic_width;
+	p_enc_info->height = op.pic_height;
+	p_enc_info->c_fmt_idc = get_chroma_format_idc(op.output_format);
+}
+
+static void wave6_gen_set_param_reg_common(struct enc_info *p_enc_info, enum codec_std std,
+					   struct enc_cmd_set_param_reg *reg)
+{
+	struct enc_open_param *p_open_param = &p_enc_info->open_param;
+	struct enc_codec_param *p_param = &p_open_param->codec_param;
+	unsigned int i;
+	u32 rot_mir = 0;
+
+	switch (p_enc_info->rot_angle) {
+	case ROT_0:
+		break;
+	case ROT_90:
+		rot_mir |= SET_PARAM_ROT_90;
+		break;
+	case ROT_180:
+		rot_mir |= SET_PARAM_ROT_180;
+		break;
+	case ROT_270:
+		rot_mir |= SET_PARAM_ROT_270;
+		break;
+	}
+
+	switch (p_enc_info->mir_dir) {
+	case MIR_NONE:
+		break;
+	case MIR_VER:
+		rot_mir |= SET_PARAM_ROT_MIRV;
+		break;
+	case MIR_HOR:
+		rot_mir |= SET_PARAM_ROT_MIRH;
+		break;
+	case MIR_HOR_VER:
+		rot_mir |= SET_PARAM_ROT_MIRH | SET_PARAM_ROT_MIRV;
+		break;
+	}
+
+	wave6_set_enc_crop_info(std, p_param, rot_mir, p_enc_info->width, p_enc_info->height);
+
+	reg->custom_map_endian = VPU_USER_DATA_ENDIAN;
+	if (rot_mir)
+		reg->rot = rot_mir | SET_PARAM_ROT_ENABLE;
+	reg->src_size = SET_PARAM_SRC_SIZE_HEIGHT(p_enc_info->height) |
+			SET_PARAM_SRC_SIZE_WIDTH(p_enc_info->width);
+	reg->conf_win0 = SET_PARAM_CONF_WIN0_BOTTOM(p_param->conf_win.bottom) |
+			 SET_PARAM_CONF_WIN0_TOP(p_param->conf_win.top);
+	reg->conf_win1 = SET_PARAM_CONF_WIN1_RIGHT(p_param->conf_win.right) |
+			 SET_PARAM_CONF_WIN1_LEFT(p_param->conf_win.left);
+	reg->gop = SET_PARAM_GOP_TEMP_LAYER_CNT(p_param->temp_layer_cnt) |
+		   SET_PARAM_GOP_TEMP_LAYER3_QP_ENABLE(p_param->temp_layer[3].change_qp) |
+		   SET_PARAM_GOP_TEMP_LAYER2_QP_ENABLE(p_param->temp_layer[2].change_qp) |
+		   SET_PARAM_GOP_TEMP_LAYER1_QP_ENABLE(p_param->temp_layer[1].change_qp) |
+		   SET_PARAM_GOP_TEMP_LAYER0_QP_ENABLE(p_param->temp_layer[0].change_qp) |
+		   SET_PARAM_GOP_PRESET_IDX(p_param->gop_preset_idx);
+	for (i = 0; i < MAX_NUM_CHANGEABLE_TEMP_LAYER; i++) {
+		reg->temp_layer_qp[i] = SET_PARAM_TEMP_LAYER_QP_B(p_param->temp_layer[i].qp_b) |
+					SET_PARAM_TEMP_LAYER_QP_P(p_param->temp_layer[i].qp_p) |
+					SET_PARAM_TEMP_LAYER_QP_I(p_param->temp_layer[i].qp_i);
+	}
+	reg->intra_refresh = SET_PARAM_INTRA_REFRESH_ARGUMENT(p_param->intra_refresh_arg) |
+			     SET_PARAM_INTRA_REFRESH_MODE(p_param->intra_refresh_mode);
+	reg->intra_qp = SET_PARAM_INTRA_QP_MAX(p_param->max_qp_i) |
+			SET_PARAM_INTRA_QP_MIN(p_param->min_qp_i);
+	reg->inter_qp = SET_PARAM_INTER_QP_MAX_B(p_param->max_qp_b) |
+			SET_PARAM_INTER_QP_MIN_B(p_param->min_qp_b) |
+			SET_PARAM_INTER_QP_MAX_P(p_param->max_qp_p) |
+			SET_PARAM_INTER_QP_MIN_P(p_param->min_qp_p);
+	reg->rc_frame_rate = p_param->frame_rate;
+	reg->rc_target_rate = p_param->bitrate;
+	reg->rc_max_bitrate = p_param->max_bitrate;
+	reg->rc_vbv_buffer_size = p_param->cpb_size;
+	reg->rc = SET_PARAM_RC_UPDATE_SPEED(p_param->rc_update_speed) |
+		  SET_PARAM_RC_INITIAL_LEVEL(p_param->rc_initial_level) |
+		  SET_PARAM_RC_INITIAL_QP(p_param->rc_initial_qp) |
+		  SET_PARAM_RC_MODE(p_param->rc_mode) |
+		  SET_PARAM_RC_PIC_LEVEL_MAX_DELTA_QP(p_param->pic_rc_max_dqp) |
+		  SET_PARAM_RC_VBV_OVERFLOW_DROP_FRAME(p_param->en_skip_frame) |
+		  SET_PARAM_RC_CU_LEVEL_ENABLE(p_param->en_cu_level_rate_control) |
+		  SET_PARAM_RC_ENABLE(p_param->en_rate_control);
+	reg->hvs = SET_PARAM_HVS_MAX_DELTA_QP(p_param->max_delta_qp) |
+		   SET_PARAM_HVS_QP_SCALE(p_param->hvs_qp_scale_div2);
+	reg->num_units_in_tick = p_param->num_units_in_tick;
+	reg->time_scale = p_param->time_scale;
+	reg->num_ticks_poc_diff_one = p_param->num_ticks_poc_diff_one;
+	reg->max_intra_pic_bit = p_param->max_intra_pic_bit;
+	reg->max_inter_pic_bit = p_param->max_inter_pic_bit;
+	reg->bg = SET_PARAM_BG_DELTA_QP(p_param->bg_delta_qp) |
+		  SET_PARAM_BG_THRESHOLD_MEAN_DIFF(p_param->bg_th_mean_diff) |
+		  SET_PARAM_BG_THRESHOLD_MAX_DIFF(p_param->bg_th_diff) |
+		  SET_PARAM_BG_ENABLE(p_param->en_bg_detect);
+	reg->qround_offset = SET_PARAM_QROUND_OFFSET_INTER(p_param->qround_inter) |
+			     SET_PARAM_QROUND_OFFSET_INTRA(p_param->qround_intra);
+	reg->custom_gop = p_param->gop_param.size;
+	for (i = 0; i < p_param->gop_param.size; i++) {
+		struct custom_gop_pic_param pic = p_param->gop_param.pic[i];
+
+		reg->custom_gop_pic[i] = SET_PARAM_CUSTOM_GOP_PIC_TEMP_ID(pic.temporal_id) |
+					 SET_PARAM_CUSTOM_GOP_PIC_REF1_POC(pic.ref_poc_l1) |
+					 SET_PARAM_CUSTOM_GOP_PIC_REF0_POC(pic.ref_poc_l0) |
+					 SET_PARAM_CUSTOM_GOP_PIC_MULTI_REF_P(pic.multi_ref_p) |
+					 SET_PARAM_CUSTOM_GOP_PIC_QP(pic.pic_qp) |
+					 SET_PARAM_CUSTOM_GOP_PIC_POC_OFFSET(pic.poc_offset) |
+					 SET_PARAM_CUSTOM_GOP_PIC_TYPE(pic.pic_type);
+	}
+	for (i = 0; i < MAX_CUSTOM_LAMBDA_NUM; i++) {
+		reg->custom_lambda[i] = SET_PARAM_CUSTOM_LAMBDA_SSD(p_param->lambda_ssd[i]) |
+					SET_PARAM_CUSTOM_LAMBDA_SAD(p_param->lambda_sad[i]);
+	}
+	reg->scaler_size = SET_PARAM_SCALER_SIZE_HEIGHT(p_enc_info->height) |
+			   SET_PARAM_SCALER_SIZE_WIDTH(p_enc_info->width);
+	reg->scaler = SET_PARAM_SCALER_COEF_MODE(p_enc_info->scaler.coef_mode) |
+		      SET_PARAM_SCALER_ENABLE(p_enc_info->scaler.enable);
+	reg->color = SET_PARAM_COLOR_RANGE(p_param->color.color_range) |
+		     SET_PARAM_COLOR_MATRIX_COEFF(p_param->color.matrix_coefficients) |
+		     SET_PARAM_COLOR_TRANS_CHAR(p_param->color.transfer_characteristics) |
+		     SET_PARAM_COLOR_PRIMARIES(p_param->color.color_primaries) |
+		     SET_PARAM_COLOR_DESCRIPTION_PRESENT_ON;
+	reg->sar = SET_PARAM_SAR_ASPECT_RATIO_IDC(p_param->sar.idc) |
+		   SET_PARAM_SAR_ASPECT_RATIO_ENABLE(p_param->sar.enable);
+	reg->sar_extended = SET_PARAM_SAR_EXTENDED_HEIGHT(p_param->sar.height) |
+			    SET_PARAM_SAR_EXTENDED_WIDTH(p_param->sar.width);
+}
+
+static void wave6_gen_set_param_reg_hevc(struct enc_info *p_enc_info,
+					 struct enc_cmd_set_param_reg *reg)
+{
+	struct enc_open_param *p_open_param = &p_enc_info->open_param;
+	struct enc_codec_param *p_param = &p_open_param->codec_param;
+
+	reg->sps = SET_PARAM_SPS_DEFAULT_SCALING_LIST(p_param->en_scaling_list) |
+		   SET_PARAM_SPS_STILL_PICTURE(p_param->en_still_picture) |
+		   SET_PARAM_SPS_AUTO_LEVEL_ADJUSTING_ON |
+		   SET_PARAM_SPS_STRONG_INTRA_SMOOTHING(p_param->en_intra_smooth) |
+		   SET_PARAM_SPS_INTRA_TRANSFORM_SKIP_ON |
+		   SET_PARAM_SPS_SAMPLE_ADAPTIVE_OFFSET(p_param->en_sao) |
+		   SET_PARAM_SPS_TEMPORAL_MVP(p_param->en_temporal_mvp) |
+		   SET_PARAM_SPS_LONGTERM_REFERENCE(p_param->en_longterm) |
+		   SET_PARAM_SPS_C_FMT_IDC(p_enc_info->c_fmt_idc) |
+		   SET_PARAM_SPS_BIT_DEPTH(p_param->internal_bit_depth) |
+		   SET_PARAM_SPS_TIER(p_param->tier) |
+		   SET_PARAM_SPS_LEVEL(p_param->level) |
+		   SET_PARAM_SPS_PROFILE(p_param->profile);
+	reg->pps = SET_PARAM_PPS_CR_QP_OFFSET(p_param->cr_qp_offset) |
+		   SET_PARAM_PPS_CB_QP_OFFSET(p_param->cb_qp_offset) |
+		   SET_PARAM_PPS_TC_OFFSET_DIV2(p_param->tc_offset_div2) |
+		   SET_PARAM_PPS_BETA_OFFSET_DIV2(p_param->beta_offset_div2) |
+		   SET_PARAM_PPS_DEBLOCKING_FILTER(!p_param->en_dbk) |
+		   SET_PARAM_PPS_LF_SLICE_BOUNDARY(p_param->en_lf_slice_boundary) |
+		   SET_PARAM_PPS_CONST_INTRA_PREDICTION(p_param->en_const_intra_pred);
+	reg->intra = SET_PARAM_INTRA_PERIOD(p_param->intra_period) |
+		     SET_PARAM_INTRA_HEADER_MODE(p_param->forced_idr_header) |
+		     SET_PARAM_INTRA_QP(p_param->qp) |
+		     SET_PARAM_INTRA_REFRESH_TYPE(p_param->decoding_refresh_type);
+	reg->rdo = SET_PARAM_RDO_CUSTOM_LAMBDA(p_param->en_custom_lambda) |
+		   SET_PARAM_RDO_ME_SEARCH_CENTER_ON |
+		   SET_PARAM_RDO_QROUND_OFFSET(p_param->en_qround_offset) |
+		   SET_PARAM_RDO_ADAPTIVE_ROUND_ON |
+		   SET_PARAM_RDO_HVS_QP(p_param->en_hvs_qp);
+	reg->slice = SET_PARAM_SLICE_ARGUMENT(p_param->slice_arg) |
+		     SET_PARAM_SLICE_MODE(p_param->slice_mode);
+	reg->quant2 = SET_PARAM_QUANT2_LAMBDA_DQP_INTER(p_param->lambda_dqp_inter) |
+		      SET_PARAM_QUANT2_LAMBDA_DQP_INTRA(p_param->lambda_dqp_intra);
+	reg->non_vcl_param = SET_PARAM_NON_VCL_PARAM_ENCODE_VUI;
+}
+
+static void wave6_gen_set_param_reg_avc(struct enc_info *p_enc_info,
+					struct enc_cmd_set_param_reg *reg)
+{
+	struct enc_open_param *p_open_param = &p_enc_info->open_param;
+	struct enc_codec_param *p_param = &p_open_param->codec_param;
+
+	reg->sps = SET_PARAM_SPS_DEFAULT_SCALING_LIST(p_param->en_scaling_list) |
+		   SET_PARAM_SPS_AUTO_LEVEL_ADJUSTING_ON |
+		   SET_PARAM_SPS_LONGTERM_REFERENCE(p_param->en_longterm) |
+		   SET_PARAM_SPS_C_FMT_IDC(p_enc_info->c_fmt_idc) |
+		   SET_PARAM_SPS_BIT_DEPTH(p_param->internal_bit_depth) |
+		   SET_PARAM_SPS_LEVEL(p_param->level) |
+		   SET_PARAM_SPS_PROFILE(p_param->profile);
+	reg->pps = SET_PARAM_PPS_ENTROPY_CODING_MODE(p_param->en_cabac) |
+		   SET_PARAM_PPS_TRANSFORM8X8(p_param->en_transform8x8) |
+		   SET_PARAM_PPS_CR_QP_OFFSET(p_param->cr_qp_offset) |
+		   SET_PARAM_PPS_CB_QP_OFFSET(p_param->cb_qp_offset) |
+		   SET_PARAM_PPS_TC_OFFSET_DIV2(p_param->tc_offset_div2) |
+		   SET_PARAM_PPS_BETA_OFFSET_DIV2(p_param->beta_offset_div2) |
+		   SET_PARAM_PPS_DEBLOCKING_FILTER(!p_param->en_dbk) |
+		   SET_PARAM_PPS_LF_SLICE_BOUNDARY(p_param->en_lf_slice_boundary) |
+		   SET_PARAM_PPS_CONST_INTRA_PREDICTION(p_param->en_const_intra_pred);
+	reg->intra = SET_PARAM_INTRA_HEADER_MODE_AVC(p_param->forced_idr_header) |
+		     SET_PARAM_INTRA_IDR_PERIOD_AVC(p_param->idr_period) |
+		     SET_PARAM_INTRA_PERIOD_AVC(p_param->intra_period) |
+		     SET_PARAM_INTRA_QP_AVC(p_param->qp);
+	reg->rdo = SET_PARAM_RDO_CUSTOM_LAMBDA(p_param->en_custom_lambda) |
+		   SET_PARAM_RDO_QROUND_OFFSET(p_param->en_qround_offset) |
+		   SET_PARAM_RDO_ADAPTIVE_ROUND_ON |
+		   SET_PARAM_RDO_HVS_QP(p_param->en_hvs_qp);
+	reg->slice = SET_PARAM_SLICE_ARGUMENT(p_param->slice_arg) |
+		     SET_PARAM_SLICE_MODE(p_param->slice_mode);
+	reg->quant2 = SET_PARAM_QUANT2_LAMBDA_DQP_INTER(p_param->lambda_dqp_inter) |
+		      SET_PARAM_QUANT2_LAMBDA_DQP_INTRA(p_param->lambda_dqp_intra);
+	reg->non_vcl_param = SET_PARAM_NON_VCL_PARAM_ENCODE_VUI;
+}
+
+static void wave6_gen_change_param_reg_common(struct vpu_instance *inst,
+					      struct enc_info *p_enc_info,
+					      struct enc_cmd_change_param_reg *reg)
+{
+	if (p_enc_info->open_param.codec_param.bitrate != inst->enc_ctrls.bitrate) {
+		reg->enable |= SET_PARAM_ENABLE_RC_TARGET_RATE;
+		reg->rc_target_rate = inst->enc_ctrls.bitrate;
+		p_enc_info->open_param.codec_param.bitrate = inst->enc_ctrls.bitrate;
+	}
+}
+
+int wave6_vpu_enc_issue_seq_init(struct vpu_instance *inst)
+{
+	struct enc_cmd_set_param_reg reg;
+	struct enc_info *p_enc_info;
+	u32 i;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_enc_info = &inst->codec_info->enc_info;
+
+	memset(&reg, 0, sizeof(struct enc_cmd_set_param_reg));
+
+	wave6_update_enc_info(p_enc_info);
+
+	wave6_gen_set_param_reg_common(p_enc_info, inst->std, &reg);
+	if (inst->std == W_HEVC_ENC)
+		wave6_gen_set_param_reg_hevc(p_enc_info, &reg);
+	else if (inst->std == W_AVC_ENC)
+		wave6_gen_set_param_reg_avc(p_enc_info, &reg);
+
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_OPTION, W6_SET_PARAM_OPT_COMMON);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_ENABLE, reg.enable);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SRC_SIZE, reg.src_size);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_CUSTOM_MAP_ENDIAN, reg.custom_map_endian);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SPS, reg.sps);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_PPS, reg.pps);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_GOP, reg.gop);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_INTRA, reg.intra);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_CONF_WIN0, reg.conf_win0);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_CONF_WIN1, reg.conf_win1);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RDO, reg.rdo);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SLICE, reg.slice);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_INTRA_REFRESH, reg.intra_refresh);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_INTRA_QP, reg.intra_qp);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RC_FRAME_RATE, reg.rc_frame_rate);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RC_TARGET_RATE, reg.rc_target_rate);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RC, reg.rc);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_HVS, reg.hvs);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RC_MAX_BITRATE, reg.rc_max_bitrate);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RC_VBV_BUFFER_SIZE, reg.rc_vbv_buffer_size);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_INTER_QP, reg.inter_qp);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_ROT, reg.rot);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_NUM_UNITS_IN_TICK, reg.num_units_in_tick);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_TIME_SCALE, reg.time_scale);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_NUM_TICKS_POC_DIFF_ONE,
+		      reg.num_ticks_poc_diff_one);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_MAX_INTRA_PIC_BIT, reg.max_intra_pic_bit);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_MAX_INTER_PIC_BIT, reg.max_inter_pic_bit);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_BG, reg.bg);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_NON_VCL_PARAM, reg.non_vcl_param);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_VUI_RBSP_ADDR, reg.vui_rbsp_addr);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_HRD_RBSP_ADDR, reg.hrd_rbsp_addr);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_QROUND_OFFSET, reg.qround_offset);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_QUANT1, reg.quant1);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_QUANT2, reg.quant2);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_CUSTOM_GOP, reg.custom_gop);
+	for (i = 0; i < MAX_CUSTOM_GOP_NUM; i++)
+		vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_CUSTOM_GOP_PIC(i),
+			      reg.custom_gop_pic[i]);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_TILE_PARAM, reg.tile_param);
+	for (i = 0; i < MAX_CUSTOM_LAMBDA_NUM; i++)
+		vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_CUSTOM_LAMBDA(i),
+			      reg.custom_lambda[i]);
+	for (i = 0; i < MAX_NUM_CHANGEABLE_TEMP_LAYER; i++)
+		vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_TEMP_LAYER_QP(i),
+			      reg.temp_layer_qp[i]);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SCALER_SIZE, reg.scaler_size);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SCALER, reg.scaler);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_COLOR, reg.color);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SAR, reg.sar);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_SAR_EXTENDED, reg.sar_extended);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_ENC_SET_PARAM);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		u32 reason_code = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+
+		wave6_print_reg_err(inst->dev, reason_code);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int wave6_vpu_enc_get_seq_info(struct vpu_instance *inst, struct enc_seq_info *info)
+{
+	int ret;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	ret = wave6_send_query(inst->dev, inst->id, inst->std, W6_QUERY_OPT_GET_RESULT);
+	if (ret)
+		return ret;
+
+	if (vpu_read_reg(inst->dev, W6_RET_ENC_ENCODING_SUCCESS) != 1) {
+		info->err_reason = vpu_read_reg(inst->dev, W6_RET_ENC_ERR_INFO);
+		ret = -EIO;
+	} else {
+		info->warn_info = vpu_read_reg(inst->dev, W6_RET_ENC_WARN_INFO);
+	}
+
+	info->min_frame_buffer_count = vpu_read_reg(inst->dev, W6_RET_ENC_NUM_REQUIRED_FBC_FB);
+	info->min_src_frame_count = vpu_read_reg(inst->dev, W6_RET_ENC_MIN_SRC_BUF_NUM);
+	info->max_latency_pictures = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_MAX_LATENCY_PICTURES);
+	info->req_mv_buffer_count = vpu_read_reg(inst->dev, W6_RET_ENC_NUM_REQUIRED_COL_BUF);
+
+	return ret;
+}
+
+int wave6_vpu_enc_issue_seq_change(struct vpu_instance *inst, bool *changed)
+{
+	struct enc_cmd_change_param_reg reg;
+	struct enc_info *p_enc_info;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_enc_info = &inst->codec_info->enc_info;
+
+	memset(&reg, 0, sizeof(struct enc_cmd_change_param_reg));
+
+	wave6_gen_change_param_reg_common(inst, p_enc_info, &reg);
+
+	if (!reg.enable)
+		return 0;
+
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_OPTION, W6_SET_PARAM_OPT_CHANGE_PARAM);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_ENABLE, reg.enable);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_SET_PARAM_RC_TARGET_RATE, reg.rc_target_rate);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_ENC_SET_PARAM);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_warn(inst->dev->dev, "enc set param timed out\n");
+		return ret;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		u32 reason_code = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+
+		wave6_print_reg_err(inst->dev, reason_code);
+		return -EIO;
+	}
+
+	*changed = true;
+
+	return 0;
+}
+
+struct enc_cmd_set_fb_reg {
+	u32 option;
+	u32 pic_info;
+	u32 pic_size;
+	u32 num_fb;
+	u32 fbc_stride;
+	u32 fbc_y[WAVE6_MAX_FBS];
+	u32 fbc_c[WAVE6_MAX_FBS];
+	u32 fbc_cr[WAVE6_MAX_FBS];
+	u32 fbc_y_offset[WAVE6_MAX_FBS];
+	u32 fbc_c_offset[WAVE6_MAX_FBS];
+	u32 fbc_cr_offset[WAVE6_MAX_FBS];
+	u32 mv_col[WAVE6_MAX_FBS];
+	u32 sub_sampled[WAVE6_MAX_FBS];
+	u32 default_cdf;
+};
+
+static void wave6_gen_set_fb_reg(struct vpu_instance *inst, struct enc_cmd_set_fb_reg *reg)
+{
+	struct frame_buffer *fb_arr = &inst->frame_buf[0];
+	struct enc_info *p_enc_info = &inst->codec_info->enc_info;
+	u32 fbc_count = p_enc_info->seq_info.min_frame_buffer_count;
+	u32 mv_count = p_enc_info->seq_info.req_mv_buffer_count;
+	u32 i;
+
+	reg->option = SET_FB_OPTION_END | SET_FB_OPTION_START;
+	reg->pic_info = SET_FB_PIC_INFO_STRIDE(fb_arr[0].stride);
+	reg->pic_size = SET_FB_PIC_SIZE_WIDTH(fb_arr[0].stride) |
+			SET_FB_PIC_SIZE_HEIGHT(fb_arr[0].height);
+	reg->num_fb = SET_FB_NUM_FBC_END_IDX(fbc_count - 1) |
+		      SET_FB_NUM_MV_COL_END_IDX(mv_count - 1);
+	reg->fbc_stride = SET_FB_FBC_STRIDE_L(fb_arr[0].stride) |
+			  SET_FB_FBC_STRIDE_C(fb_arr[0].stride_c);
+	reg->default_cdf = 0;
+
+	for (i = 0; i < fbc_count; i++) {
+		reg->fbc_y[i] = fb_arr[i].buf_y;
+		reg->fbc_c[i] = fb_arr[i].buf_cb;
+		reg->fbc_cr[i] = fb_arr[i].buf_cr;
+		reg->fbc_y_offset[i] = inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i].daddr;
+		reg->fbc_c_offset[i] = inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i].daddr;
+		reg->fbc_cr_offset[i] = inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i].daddr +
+					(inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i].size >> 1);
+		reg->sub_sampled[i] = inst->aux_vbuf[AUX_BUF_SUB_SAMPLE][i].daddr;
+	}
+	for (i = 0; i < mv_count; i++)
+		reg->mv_col[i] = inst->aux_vbuf[AUX_BUF_MV_COL][i].daddr;
+}
+
+int wave6_vpu_enc_register_frame_buffer(struct vpu_instance *inst)
+{
+	struct enc_cmd_set_fb_reg *reg;
+	struct enc_info *p_enc_info;
+	u32 fbc_count, mv_count, idx;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_enc_info = &inst->codec_info->enc_info;
+
+	fbc_count = p_enc_info->seq_info.min_frame_buffer_count;
+	mv_count = p_enc_info->seq_info.req_mv_buffer_count;
+
+	for (idx = 0; idx < fbc_count; idx++) {
+		if (!inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][idx].daddr)
+			return -EINVAL;
+		if (!inst->aux_vbuf[AUX_BUF_FBC_C_TBL][idx].daddr)
+			return -EINVAL;
+		if (!inst->aux_vbuf[AUX_BUF_SUB_SAMPLE][idx].daddr)
+			return -EINVAL;
+	}
+	for (idx = 0; idx < mv_count; idx++) {
+		if (!inst->aux_vbuf[AUX_BUF_MV_COL][idx].daddr)
+			return -EINVAL;
+	}
+
+	reg = kzalloc_obj(*reg);
+	if (!reg)
+		return -ENOMEM;
+
+	wave6_gen_set_fb_reg(inst, reg);
+
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_OPTION, reg->option);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_PIC_INFO, reg->pic_info);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_PIC_SIZE, reg->pic_size);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_NUM, reg->num_fb);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_STRIDE, reg->fbc_stride);
+	vpu_write_reg(inst->dev, W6_CMD_SET_FB_DEFAULT_CDF, reg->default_cdf);
+	for (idx = 0; idx < fbc_count; idx++) {
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_Y(idx), reg->fbc_y[idx]);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_C(idx), reg->fbc_c[idx]);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_CR(idx), reg->fbc_cr[idx]);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_Y_OFFSET(idx),
+			      reg->fbc_y_offset[idx]);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_C_OFFSET(idx),
+			      reg->fbc_c_offset[idx]);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_FBC_CR_OFFSET(idx),
+			      reg->fbc_cr_offset[idx]);
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_SUB_SAMPLED(idx),
+			      reg->sub_sampled[idx]);
+	}
+	for (idx = 0; idx < mv_count; idx++)
+		vpu_write_reg(inst->dev, W6_CMD_SET_FB_MV_COL(idx), reg->mv_col[idx]);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_SET_FB);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		goto exit;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		ret = -EIO;
+		goto exit;
+	}
+
+	ret = 0;
+
+exit:
+	kfree(reg);
+	return ret;
+}
+
+struct enc_cmd_enc_pic_reg {
+	u32 bs_start;
+	u32 bs_size;
+	u32 bs_option;
+	u32 sec_axi;
+	u32 report;
+	u32 mv_histo0;
+	u32 mv_histo1;
+	u32 custom_map_param;
+	u32 custom_map_addr;
+	u32 src_pic_idx;
+	u32 src_addr_y;
+	u32 src_addr_u;
+	u32 src_addr_v;
+	u32 src_stride;
+	u32 src_fmt;
+	u32 src_axi_sel;
+	u32 code_option;
+	u32 param;
+	u32 longterm_pic;
+	u32 prefix_sei_nal_addr;
+	u32 prefix_sei_info;
+	u32 suffix_sei_nal_addr;
+	u32 suffix_sei_info;
+	u32 timestamp_low;
+	u32 timestamp_high;
+	u32 csc_coeff[MAX_CSC_COEFF_NUM];
+};
+
+static void wave6_gen_enc_pic_reg(struct enc_info *p_enc_info, bool cbcr_interleave, bool nv21,
+				  struct enc_param *opt, struct enc_cmd_enc_pic_reg *reg)
+{
+	struct enc_open_param open = p_enc_info->open_param;
+	struct enc_codec_param param = open.codec_param;
+	bool is_lsb = false;
+	bool is_10bit = false;
+	bool is_3p4b = false;
+	bool is_cr_first = nv21;
+	u32 c_fmt_idc = get_chroma_format_idc(open.src_format);
+	u32 stride_c = opt->source_frame->stride_c;
+	bool is_ayuv = false;
+	bool is_csc_format = false;
+	bool is_24bit = false;
+	bool is_packed = false;
+	bool is_packed_uv_first = false;
+
+	if ((c_fmt_idc == C_FMT_IDC_YUV422 || c_fmt_idc == C_FMT_IDC_YUV444) &&
+	    p_enc_info->c_fmt_idc == C_FMT_IDC_YUV420)
+		stride_c *= 2;
+
+	switch (open.src_format) {
+	case FORMAT_420:
+	case FORMAT_422:
+	case FORMAT_444:
+	case FORMAT_400:
+	case FORMAT_YUYV:
+	case FORMAT_YVYU:
+	case FORMAT_UYVY:
+	case FORMAT_VYUY:
+		is_lsb = false;
+		is_3p4b = false;
+		break;
+	case FORMAT_420_P10_16BIT_MSB:
+	case FORMAT_422_P10_16BIT_MSB:
+	case FORMAT_444_P10_16BIT_MSB:
+	case FORMAT_400_P10_16BIT_MSB:
+	case FORMAT_YUYV_P10_16BIT_MSB:
+	case FORMAT_YVYU_P10_16BIT_MSB:
+	case FORMAT_UYVY_P10_16BIT_MSB:
+	case FORMAT_VYUY_P10_16BIT_MSB:
+		is_lsb = false;
+		is_10bit = true;
+		is_3p4b = false;
+		break;
+	case FORMAT_420_P10_16BIT_LSB:
+	case FORMAT_422_P10_16BIT_LSB:
+	case FORMAT_444_P10_16BIT_LSB:
+	case FORMAT_400_P10_16BIT_LSB:
+	case FORMAT_YUYV_P10_16BIT_LSB:
+	case FORMAT_YVYU_P10_16BIT_LSB:
+	case FORMAT_UYVY_P10_16BIT_LSB:
+	case FORMAT_VYUY_P10_16BIT_LSB:
+		is_lsb = true;
+		is_10bit = true;
+		is_3p4b = false;
+		break;
+	case FORMAT_420_P10_32BIT_MSB:
+	case FORMAT_422_P10_32BIT_MSB:
+	case FORMAT_444_P10_32BIT_MSB:
+	case FORMAT_400_P10_32BIT_MSB:
+	case FORMAT_YUYV_P10_32BIT_MSB:
+	case FORMAT_YVYU_P10_32BIT_MSB:
+	case FORMAT_UYVY_P10_32BIT_MSB:
+	case FORMAT_VYUY_P10_32BIT_MSB:
+		is_lsb = false;
+		is_10bit = true;
+		is_3p4b = true;
+		break;
+	case FORMAT_420_P10_32BIT_LSB:
+	case FORMAT_422_P10_32BIT_LSB:
+	case FORMAT_444_P10_32BIT_LSB:
+	case FORMAT_400_P10_32BIT_LSB:
+	case FORMAT_YUYV_P10_32BIT_LSB:
+	case FORMAT_YVYU_P10_32BIT_LSB:
+	case FORMAT_UYVY_P10_32BIT_LSB:
+	case FORMAT_VYUY_P10_32BIT_LSB:
+		is_lsb = true;
+		is_10bit = true;
+		is_3p4b = true;
+		break;
+	case FORMAT_RGB_32BIT_PACKED:
+		is_ayuv = false;
+		is_csc_format = true;
+		break;
+	case FORMAT_RGB_P10_32BIT_PACKED:
+		is_ayuv = false;
+		is_csc_format = true;
+		is_10bit = true;
+		break;
+	case FORMAT_YUV444_32BIT_PACKED:
+		is_ayuv = true;
+		is_csc_format = true;
+		break;
+	case FORMAT_YUV444_P10_32BIT_PACKED:
+		is_ayuv = true;
+		is_csc_format = true;
+		is_10bit = true;
+		break;
+	case FORMAT_RGB_24BIT_PACKED:
+		is_ayuv = false;
+		is_csc_format = true;
+		is_24bit = true;
+		break;
+	case FORMAT_YUV444_24BIT_PACKED:
+		is_ayuv = true;
+		is_csc_format = true;
+		is_24bit = true;
+		break;
+	case FORMAT_YUV444_24BIT:
+		is_ayuv = true;
+		break;
+	default:
+		break;
+	}
+
+	switch (open.src_format) {
+	case FORMAT_YUYV:
+	case FORMAT_YUYV_P10_16BIT_MSB:
+	case FORMAT_YUYV_P10_16BIT_LSB:
+	case FORMAT_YUYV_P10_32BIT_MSB:
+	case FORMAT_YUYV_P10_32BIT_LSB:
+		is_packed = true;
+		break;
+	case FORMAT_YVYU:
+	case FORMAT_YVYU_P10_16BIT_MSB:
+	case FORMAT_YVYU_P10_16BIT_LSB:
+	case FORMAT_YVYU_P10_32BIT_MSB:
+	case FORMAT_YVYU_P10_32BIT_LSB:
+		is_packed = true;
+		is_cr_first = true;
+		break;
+	case FORMAT_UYVY:
+	case FORMAT_UYVY_P10_16BIT_MSB:
+	case FORMAT_UYVY_P10_16BIT_LSB:
+	case FORMAT_UYVY_P10_32BIT_MSB:
+	case FORMAT_UYVY_P10_32BIT_LSB:
+		is_packed = true;
+		is_packed_uv_first = true;
+		break;
+	case FORMAT_VYUY:
+	case FORMAT_VYUY_P10_16BIT_MSB:
+	case FORMAT_VYUY_P10_16BIT_LSB:
+	case FORMAT_VYUY_P10_32BIT_MSB:
+	case FORMAT_VYUY_P10_32BIT_LSB:
+		is_packed = true;
+		is_packed_uv_first = true;
+		is_cr_first = true;
+		break;
+	default:
+		break;
+	}
+
+	reg->src_fmt = ENC_PIC_SRC_FMT_C_FMT_IDC(c_fmt_idc) |
+		       ENC_PIC_SRC_FMT_CSC_24BIT(is_24bit) |
+		       ENC_PIC_SRC_FMT_CSC_AYUV(is_ayuv) |
+		       ENC_PIC_SRC_FMT_CSC_ENABLE(is_csc_format) |
+		       ENC_PIC_SRC_FMT_CSC_FMT_ORDER(opt->csc.fmt_order) |
+		       ENC_PIC_SRC_FMT_ENDIAN(open.source_endian) |
+		       ENC_PIC_SRC_FMT_10BIT_ORDER(is_lsb) |
+		       ENC_PIC_SRC_FMT_10BIT_3PIXEL_4BYTE(is_3p4b) |
+		       ENC_PIC_SRC_FMT_10BIT_ENABLE(is_10bit) |
+		       ENC_PIC_SRC_FMT_YUV422_PACKED_ORDER(is_packed_uv_first) |
+		       ENC_PIC_SRC_FMT_CBCR_ORDER(is_cr_first) |
+		       ENC_PIC_SRC_FMT_2PLANE(cbcr_interleave) |
+		       ENC_PIC_SRC_FMT_YUV422_PACKED(is_packed);
+	reg->bs_start = opt->pic_stream_buffer_addr;
+	reg->bs_size = opt->pic_stream_buffer_size;
+	reg->sec_axi = ENC_PIC_SEC_AXI_RDO_ENABLE(p_enc_info->sec_axi.use_enc_rdo) |
+		       ENC_PIC_SEC_AXI_LF_ENABLE(p_enc_info->sec_axi.use_enc_lf);
+	reg->report = ENC_PIC_REPORT_MV_HISTO_ENABLE(param.en_report_mv_histo);
+	reg->mv_histo0 = ENC_PIC_MV_HISTO0_THRESHOLD0(param.mv_histo_th0) |
+			 ENC_PIC_MV_HISTO0_THRESHOLD1(param.mv_histo_th1);
+	reg->mv_histo1 = ENC_PIC_MV_HISTO1_THRESHOLD2(param.mv_histo_th2) |
+			 ENC_PIC_MV_HISTO1_THRESHOLD3(param.mv_histo_th3);
+	reg->src_pic_idx = (opt->src_end) ? ENC_PIC_SRC_PIC_IDX_END : opt->src_idx;
+	reg->src_addr_y = opt->source_frame->buf_y;
+	reg->src_addr_u = opt->source_frame->buf_cb;
+	reg->src_addr_v = opt->source_frame->buf_cr;
+	reg->src_stride = ENC_PIC_SRC_STRIDE_L(opt->source_frame->stride) |
+			  ENC_PIC_SRC_STRIDE_C(stride_c);
+	reg->src_axi_sel = ENC_PIC_SRC_AXI_SEL_PRP_PORT;
+	reg->code_option = ENC_PIC_CODE_OPTION_ENCODE_VCL |
+			   ENC_PIC_CODE_OPTION_ENCODE_HEADER;
+	reg->param = ENC_PIC_PARAM_INTRA_4X4(param.intra_4x4) |
+		     ENC_PIC_PARAM_FORCE_PIC_TYPE(opt->force_pic_type) |
+		     ENC_PIC_PARAM_FORCE_PIC_TYPE_ENABLE(opt->force_pic) |
+		     ENC_PIC_PARAM_FORCE_PIC_QP_B(opt->force_pic_qp_b) |
+		     ENC_PIC_PARAM_FORCE_PIC_QP_P(opt->force_pic_qp_p) |
+		     ENC_PIC_PARAM_FORCE_PIC_QP_I(opt->force_pic_qp_i) |
+		     ENC_PIC_PARAM_FORCE_PIC_QP_ENABLE(opt->force_pic_qp) |
+		     ENC_PIC_PARAM_PIC_SKIP_FLAG(opt->skip_picture);
+	reg->timestamp_low = lower_32_bits(opt->timestamp);
+	reg->timestamp_high = upper_32_bits(opt->timestamp);
+	reg->csc_coeff[0] = ENC_PIC_CSC_COEFF0_RY(opt->csc.coef_ry) |
+			    ENC_PIC_CSC_COEFF0_GY(opt->csc.coef_gy) |
+			    ENC_PIC_CSC_COEFF0_BY(opt->csc.coef_by);
+	reg->csc_coeff[1] = ENC_PIC_CSC_COEFF1_RCB(opt->csc.coef_rcb) |
+			    ENC_PIC_CSC_COEFF1_GCB(opt->csc.coef_gcb) |
+			    ENC_PIC_CSC_COEFF1_BCB(opt->csc.coef_bcb);
+	reg->csc_coeff[2] = ENC_PIC_CSC_COEFF2_RCR(opt->csc.coef_rcr) |
+			    ENC_PIC_CSC_COEFF2_GCR(opt->csc.coef_gcr) |
+			    ENC_PIC_CSC_COEFF2_BCR(opt->csc.coef_bcr);
+	reg->csc_coeff[3] = ENC_PIC_CSC_COEFF3_OFFSET_Y(opt->csc.offset_y) |
+			    ENC_PIC_CSC_COEFF3_OFFSET_CB(opt->csc.offset_cb) |
+			    ENC_PIC_CSC_COEFF3_OFFSET_CR(opt->csc.offset_cr);
+}
+
+int wave6_vpu_enc_start_one_frame(struct vpu_instance *inst, struct enc_param *param,
+				  u32 *fail_res)
+{
+	struct enc_cmd_enc_pic_reg reg;
+	struct enc_info *p_enc_info;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	p_enc_info = &inst->codec_info->enc_info;
+
+	memset(&reg, 0, sizeof(struct enc_cmd_enc_pic_reg));
+
+	wave6_gen_enc_pic_reg(p_enc_info, inst->cbcr_interleave,
+			      inst->nv21, param, &reg);
+
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_BS_START, reg.bs_start);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_BS_SIZE, reg.bs_size);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_BS_OPTION, reg.bs_option);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SEC_AXI, reg.sec_axi);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_REPORT, reg.report);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_MV_HISTO0, reg.mv_histo0);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_MV_HISTO1, reg.mv_histo1);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CUSTOM_MAP_PARAM, reg.custom_map_param);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CUSTOM_MAP_ADDR, reg.custom_map_addr);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_PIC_IDX, reg.src_pic_idx);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_ADDR_Y, reg.src_addr_y);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_ADDR_U, reg.src_addr_u);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_ADDR_V, reg.src_addr_v);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_STRIDE, reg.src_stride);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_FMT, reg.src_fmt);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SRC_AXI_SEL, reg.src_axi_sel);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CODE_OPTION, reg.code_option);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_PARAM, reg.param);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_LONGTERM_PIC, reg.longterm_pic);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_PREFIX_SEI_NAL_ADDR, reg.prefix_sei_nal_addr);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_PREFIX_SEI_INFO, reg.prefix_sei_info);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SUFFIX_SEI_NAL_ADDR, reg.suffix_sei_nal_addr);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_SUFFIX_SEI_INFO, reg.suffix_sei_info);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_TIMESTAMP_LOW, reg.timestamp_low);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_TIMESTAMP_HIGH, reg.timestamp_high);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CSC_COEFF0, reg.csc_coeff[0]);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CSC_COEFF1, reg.csc_coeff[1]);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CSC_COEFF2, reg.csc_coeff[2]);
+	vpu_write_reg(inst->dev, W6_CMD_ENC_PIC_CSC_COEFF3, reg.csc_coeff[3]);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_ENC_PIC);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: timeout\n", __func__);
+		return -ETIMEDOUT;
+	}
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		*fail_res = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+		wave6_print_reg_err(inst->dev, *fail_res);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int wave6_vpu_enc_get_output_info(struct vpu_instance *inst, struct enc_output_info *info)
+{
+	u32 reg_val;
+	int ret;
+
+	if (WARN_ON(!info))
+		return -EINVAL;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	ret = wave6_send_query(inst->dev, inst->id, inst->std, W6_QUERY_OPT_GET_RESULT);
+	if (ret)
+		return ret;
+
+	info->encoding_success = vpu_read_reg(inst->dev, W6_RET_ENC_ENCODING_SUCCESS);
+	if (!info->encoding_success)
+		info->error_reason = vpu_read_reg(inst->dev, W6_RET_ENC_ERR_INFO);
+	else
+		info->warn_info = vpu_read_reg(inst->dev, W6_RET_ENC_WARN_INFO);
+
+	info->enc_pic_cnt = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_NUM);
+	info->pic_type = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_TYPE);
+	info->enc_vcl_nut = vpu_read_reg(inst->dev, W6_RET_ENC_VCL_NUT);
+	info->non_ref_pic = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_NON_REF_PIC_FLAG);
+	info->num_of_slices = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_SLICE_NUM);
+	info->pic_skipped = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_SKIP);
+	info->num_of_intra = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_NUM_INTRA);
+	info->num_of_merge = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_NUM_MERGE);
+	info->num_of_skip_block = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_NUM_SKIP);
+	info->avg_ctu_qp = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_AVG_CTU_QP);
+	info->enc_pic_byte = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_BYTE);
+	info->enc_gop_pic_idx = vpu_read_reg(inst->dev, W6_RET_ENC_GOP_PIC_IDX);
+	info->enc_pic_poc = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_POC);
+	info->enc_src_idx = vpu_read_reg(inst->dev, W6_RET_ENC_USED_SRC_IDX);
+	info->wr_ptr = vpu_read_reg(inst->dev, W6_RET_ENC_WR_PTR);
+	info->rd_ptr = vpu_read_reg(inst->dev, W6_RET_ENC_RD_PTR);
+	info->bitstream_buffer = vpu_read_reg(inst->dev, W6_RET_ENC_RD_PTR);
+	info->pic_distortion_low = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_DIST_LOW);
+	info->pic_distortion_high = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_DIST_HIGH);
+	info->mv_histo.cnt0 = vpu_read_reg(inst->dev, W6_RET_ENC_HISTO_CNT0);
+	info->mv_histo.cnt1 = vpu_read_reg(inst->dev, W6_RET_ENC_HISTO_CNT1);
+	info->mv_histo.cnt2 = vpu_read_reg(inst->dev, W6_RET_ENC_HISTO_CNT2);
+	info->mv_histo.cnt3 = vpu_read_reg(inst->dev, W6_RET_ENC_HISTO_CNT3);
+	info->mv_histo.cnt4 = vpu_read_reg(inst->dev, W6_RET_ENC_HISTO_CNT4);
+	info->fme_sum.lower_x0 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME0_X_DIR_LOWER);
+	info->fme_sum.higher_x0 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME0_X_DIR_HIGHER);
+	info->fme_sum.lower_y0 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME0_Y_DIR_LOWER);
+	info->fme_sum.higher_y0 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME0_Y_DIR_HIGHER);
+	info->fme_sum.lower_x1 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME1_X_DIR_LOWER);
+	info->fme_sum.higher_x1 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME1_X_DIR_HIGHER);
+	info->fme_sum.lower_y1 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME1_Y_DIR_LOWER);
+	info->fme_sum.higher_y1 = vpu_read_reg(inst->dev, W6_RET_ENC_SUM_ME1_Y_DIR_HIGHER);
+	info->src_y_addr = vpu_read_reg(inst->dev, W6_RET_ENC_SRC_Y_ADDR);
+	info->custom_map_addr = vpu_read_reg(inst->dev, W6_RET_ENC_CUSTOM_MAP_ADDR);
+	info->prefix_sei_nal_addr = vpu_read_reg(inst->dev, W6_RET_ENC_PREFIX_SEI_NAL_ADDR);
+	info->suffix_sei_nal_addr = vpu_read_reg(inst->dev, W6_RET_ENC_SUFFIX_SEI_NAL_ADDR);
+
+	info->recon_frame_index = vpu_read_reg(inst->dev, W6_RET_ENC_PIC_IDX);
+	if (info->recon_frame_index == RECON_IDX_FLAG_HEADER_ONLY)
+		info->bitstream_size = info->enc_pic_byte;
+	else if (info->recon_frame_index < 0)
+		info->bitstream_size = 0;
+	else
+		info->bitstream_size = info->enc_pic_byte;
+
+	reg_val = vpu_read_reg(inst->dev, W6_RET_ENC_TIMESTAMP_LOW);
+	info->timestamp = vpu_read_reg(inst->dev, W6_RET_ENC_TIMESTAMP_HIGH);
+	info->timestamp = (info->timestamp << 32) | reg_val;
+
+	info->cycle.host_cmd_s = vpu_read_reg(inst->dev, W6_RET_CQ_IN_TICK);
+	info->cycle.host_cmd_e = vpu_read_reg(inst->dev, W6_RET_RQ_OUT_TICK);
+	info->cycle.proc_s = vpu_read_reg(inst->dev, W6_RET_HW_RUN_TICK);
+	info->cycle.proc_e = vpu_read_reg(inst->dev, W6_RET_HW_DONE_TICK);
+	info->cycle.vpu_s = vpu_read_reg(inst->dev, W6_RET_FW_RUN_TICK);
+	info->cycle.vpu_e = vpu_read_reg(inst->dev, W6_RET_FW_DONE_TICK);
+	info->cycle.frame_cycle = (info->cycle.vpu_e - info->cycle.host_cmd_s) *
+				  CYCLE_PER_TICK_VALUE;
+	info->cycle.proc_cycle = (info->cycle.proc_e - info->cycle.proc_s) *
+				 CYCLE_PER_TICK_VALUE;
+	info->cycle.vpu_cycle = ((info->cycle.vpu_e - info->cycle.vpu_s) -
+				 (info->cycle.proc_e - info->cycle.proc_s)) *
+				 CYCLE_PER_TICK_VALUE;
+
+	return 0;
+}
+
+int wave6_vpu_enc_fini_seq(struct vpu_instance *inst, u32 *fail_res)
+{
+	int ret;
+
+	lockdep_assert_held(&inst->dev->hw_lock);
+
+	wave6_send_command(inst->dev, inst->id, inst->std, W6_CMD_DESTROY_INSTANCE);
+	ret = wave6_wait_vpu_busy(inst->dev, W6_VPU_BUSY_STATUS);
+	if (ret)
+		return -ETIMEDOUT;
+
+	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
+		*fail_res = vpu_read_reg(inst->dev, W6_RET_FAIL_REASON);
+		wave6_print_reg_err(inst->dev, *fail_res);
+		return -EIO;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-hw.h b/drivers/media/platform/chips-media/wave6/wave6-hw.h
new file mode 100644
index 000000000000..956404e36b96
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-hw.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 backend interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_HW_H__
+#define __WAVE6_HW_H__
+
+enum product_id {
+	PRODUCT_ID_617,
+	PRODUCT_ID_627,
+	PRODUCT_ID_637,
+	PRODUCT_ID_NONE,
+};
+
+#define COMMAND_QUEUE_DEPTH		1
+#define SINGLE_CORE_IDC			1
+#define SINGLE_CORE			1
+
+#define DECODE_ALL_TEMPORAL_LAYERS	0
+#define DECODE_ALL_SPATIAL_LAYERS	0
+
+#define PIXEL_ORDER_INCREASING		1
+
+#define WTL_RIGHT_JUSTIFIED		0
+#define WTL_LEFT_JUSTIFIED		1
+#define WTL_PIXEL_8BIT			0
+#define WTL_PIXEL_16BIT			1
+#define WTL_PIXEL_32BIT			2
+
+#define MAX_CSC_COEFF_NUM		4
+
+#define CYCLE_PER_TICK_VALUE		256
+
+#define H265_NAL_UNIT_TYPE_IDR_W_RADL	19
+#define H265_NAL_UNIT_TYPE_IDR_N_LP	20
+
+#define H264_NAL_UNIT_TYPE_IDR_PICTURE	5
+
+bool wave6_vpu_is_init(struct vpu_core_device *core);
+int wave6_vpu_get_version(struct vpu_core_device *core);
+void wave6_vpu_enable_interrupt(struct vpu_core_device *core);
+
+int wave6_vpu_build_up_dec_param(struct vpu_instance *inst, struct dec_open_param *param);
+int wave6_vpu_dec_get_seq_info(struct vpu_instance *inst, struct dec_seq_info *info);
+int wave6_vpu_dec_fini_seq(struct vpu_instance *inst, u32 *fail_res);
+dma_addr_t wave6_vpu_dec_get_rd_ptr(struct vpu_instance *inst);
+
+int wave6_vpu_build_up_enc_param(struct device *dev, struct vpu_instance *inst,
+				 struct enc_open_param *param);
+int wave6_vpu_enc_get_seq_info(struct vpu_instance *inst, struct enc_seq_info *info);
+int wave6_vpu_enc_fini_seq(struct vpu_instance *inst, u32 *fail_res);
+
+#endif /* __WAVE6_HW_H__ */
diff --git a/drivers/media/platform/chips-media/wave6/wave6-regdefine.h b/drivers/media/platform/chips-media/wave6/wave6-regdefine.h
new file mode 100644
index 000000000000..e65686e3d0f3
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-regdefine.h
@@ -0,0 +1,647 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 register definitions
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_REGDEFINE_H__
+#define __WAVE6_REGDEFINE_H__
+
+enum wave6_command {
+	W6_CMD_INIT_VPU		= 0x0001,
+	W6_CMD_WAKEUP_VPU	= 0x0002,
+	W6_CMD_SLEEP_VPU	= 0x0004,
+	W6_CMD_CREATE_INSTANCE	= 0x0008,
+	W6_CMD_FLUSH_INSTANCE	= 0x0010,
+	W6_CMD_DESTROY_INSTANCE	= 0x0020,
+	W6_CMD_INIT_SEQ		= 0x0040,
+	W6_CMD_SET_FB		= 0x0080,
+	W6_CMD_DEC_PIC		= 0x0100,
+	W6_CMD_ENC_PIC		= 0x0100,
+	W6_CMD_ENC_SET_PARAM	= 0x0200,
+	W6_CMD_DEC_SET_DISP	= 0x0400,
+	W6_CMD_INIT_WORK_BUF	= 0x1000,
+	W6_CMD_QUERY		= 0x4000,
+};
+
+enum wave6_init_seq_option {
+	W6_INIT_SEQ_OPT_NORMAL		= 1,
+	W6_INIT_SEQ_OPT_W_THUMBNAIL	= 17,
+};
+
+enum wave6_set_param_option {
+	W6_SET_PARAM_OPT_COMMON		= 0,
+	W6_SET_PARAM_OPT_CHANGE_PARAM	= 1,
+};
+
+enum wave6_dec_pic_option {
+	W6_DEC_PIC_OPT_NORMAL		= 0,
+	W6_DEC_PIC_OPT_W_THUMBNAIL	= 16,
+	W6_DEC_PIC_OPT_SKIP_NON_IRAP	= 17,
+	W6_DEC_PIC_OPT_SKIP_NON_REF_PIC	= 19,
+};
+
+enum wave6_query_option {
+	W6_QUERY_OPT_GET_VPU_INFO	= 0,
+	W6_QUERY_OPT_GET_RESULT		= 2,
+	W6_QUERY_OPT_GET_FLUSH_CMD_INFO	= 10,
+};
+
+enum wave6_interrupt_bit {
+	W6_INT_BIT_INIT_VPU		= 0,
+	W6_INT_BIT_WAKEUP_VPU		= 1,
+	W6_INT_BIT_SLEEP_VPU		= 2,
+	W6_INT_BIT_CREATE_INSTANCE	= 3,
+	W6_INT_BIT_FLUSH_INSTANCE	= 4,
+	W6_INT_BIT_DESTROY_INSTANCE	= 5,
+	W6_INT_BIT_INIT_SEQ		= 6,
+	W6_INT_BIT_SET_FB		= 7,
+	W6_INT_BIT_DEC_PIC		= 8,
+	W6_INT_BIT_ENC_PIC		= 8,
+	W6_INT_BIT_ENC_SET_PARAM	= 9,
+	W6_INT_BIT_SET_DISP		= 10,
+	W6_INT_BIT_REQ_WORK_BUF		= 12,
+	W6_INT_BIT_BSBUF_ERROR		= 15,
+};
+
+#define W6_REG_BASE	0x00000000
+#define W6_CMD_REG_BASE	0x00000200
+#define W6_CMD_REG_END	0x00000600
+
+#define W6_VPU_VCPU_CUR_PC				(W6_REG_BASE + 0x0004)
+#define W6_VPU_VINT_REASON_CLEAR			(W6_REG_BASE + 0x0034)
+#define W6_VPU_HOST_INT_REQ				(W6_REG_BASE + 0x0038)
+#define		HOST_INT_REQ_ON				BIT(0)
+#define W6_VPU_VINT_CLEAR				(W6_REG_BASE + 0x003C)
+#define		VINT_CLEAR				BIT(0)
+#define W6_VPU_VPU_INT_STS				(W6_REG_BASE + 0x0044)
+#define W6_VPU_VINT_ENABLE				(W6_REG_BASE + 0x0048)
+#define W6_VPU_VINT_REASON				(W6_REG_BASE + 0x004C)
+#define W6_VPU_REMAP_CTRL_GB				(W6_REG_BASE + 0x0060)
+#define		REMAP_CTRL_ON				BIT(31)
+#define		REMAP_CTRL_INDEX(x)			FIELD_PREP(GENMASK(15, 12), (x))
+#define		REMAP_CTRL_PAGE_SIZE_ON			BIT(11)
+#define		REMAP_CTRL_PAGE_SIZE(x)			FIELD_PREP(GENMASK(8, 0), ((x) >> 12))
+#define W6_VPU_REMAP_VADDR_GB				(W6_REG_BASE + 0x0064)
+#define W6_VPU_REMAP_PADDR_GB				(W6_REG_BASE + 0x0068)
+#define W6_VPU_REMAP_CORE_START_GB			(W6_REG_BASE + 0x006C)
+#define		REMAP_CORE_START_ON			BIT(0)
+#define W6_VPU_BUSY_STATUS				(W6_REG_BASE + 0x0070)
+#define		BUSY_STATUS_SET				BIT(0)
+#define W6_VPU_RET_PRODUCT_CODE				(W6_REG_BASE + 0x0094)
+
+/* COMMON */
+#define W6_COMMAND_GB					(W6_REG_BASE + 0x104)
+#define W6_COMMAND					(W6_REG_BASE + 0x200)
+#define W6_QUERY_OPTION					(W6_REG_BASE + 0x204)
+#define W6_CMD_INSTANCE_INFO				(W6_REG_BASE + 0x210)
+#define		INSTANCE_INFO_CODEC_STD(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		INSTANCE_INFO_ID(x)			FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_INIT_VPU_SEC_AXI_BASE_CORE0		(W6_REG_BASE + 0x364)
+#define W6_CMD_INIT_VPU_SEC_AXI_SIZE_CORE0		(W6_REG_BASE + 0x368)
+#define W6_CMD_SET_WORK_BUF_ADDR			(W6_REG_BASE + 0x5F0)
+#define W6_CMD_SET_WORK_BUF_SIZE			(W6_REG_BASE + 0x5F4)
+#define		SET_WORK_BUF_SIZE_ACK			0
+#define W6_RET_SUCCESS					(W6_REG_BASE + 0x208)
+#define W6_RET_FAIL_REASON				(W6_REG_BASE + 0x20C)
+#define W6_RET_INT_INSTANCE_INFO			(W6_REG_BASE + 0x21C)
+#define		INT_INSTANCE_INFO_CLEAR			0
+#define W6_RET_INSTANCE_ID				(W6_REG_BASE + 0x220)
+#define W6_RET_CQ_IN_TICK				(W6_REG_BASE + 0x23C)
+#define W6_RET_FW_RUN_TICK				(W6_REG_BASE + 0x240)
+#define W6_RET_HW_RUN_TICK				(W6_REG_BASE + 0x244)
+#define W6_RET_HW_DONE_TICK				(W6_REG_BASE + 0x248)
+#define W6_RET_FW_DONE_TICK				(W6_REG_BASE + 0x24C)
+#define W6_RET_RQ_OUT_TICK				(W6_REG_BASE + 0x250)
+
+/* COMMON - QUERY : GET_VPU_INFO */
+#define W6_RET_FW_VERSION				(W6_REG_BASE + 0x300)
+#define W6_RET_PRODUCT_NAME				(W6_REG_BASE + 0x304)
+#define W6_RET_PRODUCT_VERSION				(W6_REG_BASE + 0x308)
+#define W6_RET_STD_DEF0					(W6_REG_BASE + 0x30C)
+#define W6_RET_STD_DEF1					(W6_REG_BASE + 0x310)
+#define		STD_DEF1_AVC_DEC(x)			FIELD_GET(GENMASK(3, 3), (x))
+#define		STD_DEF1_HEVC_DEC(x)			FIELD_GET(GENMASK(2, 2), (x))
+#define		STD_DEF1_AVC_ENC(x)			FIELD_GET(GENMASK(1, 1), (x))
+#define		STD_DEF1_HEVC_ENC(x)			FIELD_GET(GENMASK(0, 0), (x))
+#define W6_RET_CONF_FEATURE				(W6_REG_BASE + 0x314)
+#define		CONF_FEATURE_AVC10BIT_ENC(x)		FIELD_GET(GENMASK(11, 11), (x))
+#define		CONF_FEATURE_AVC10BIT_DEC(x)		FIELD_GET(GENMASK(9, 9), (x))
+#define		CONF_FEATURE_HEVC10BIT_ENC(x)		FIELD_GET(GENMASK(3, 3), (x))
+#define		CONF_FEATURE_HEVC10BIT_DEC(x)		FIELD_GET(GENMASK(1, 1), (x))
+#define W6_RET_CONF_DATE				(W6_REG_BASE + 0x318)
+#define W6_RET_CONF_HW_VERSION				(W6_REG_BASE + 0x31C)
+#define W6_RET_CONF_TYPE				(W6_REG_BASE + 0x320)
+#define W6_RET_FW_API_VERSION				(W6_REG_BASE + 0x32C)
+#define W6_RET_SHA_ID					(W6_REG_BASE + 0x330)
+
+/* COMMON - CREATE_INSTANCE */
+#define W6_CMD_CREATE_INST_BS_PARAM			(W6_REG_BASE + 0x310)
+#define W6_CMD_CREATE_INST_SRC_OPT			(W6_REG_BASE + 0x314)
+#define W6_CMD_CREATE_INST_ADDR_EXT			(W6_REG_BASE + 0x318)
+#define W6_CMD_CREATE_INST_DISP_MODE			(W6_REG_BASE + 0x31C)
+#define W6_CMD_CREATE_INST_CORE_INFO			(W6_REG_BASE + 0x330)
+#define		CREATE_INST_CORE_INFO_CQ_DEPTH(x)	FIELD_PREP(GENMASK(10, 8), (x))
+#define		CREATE_INST_CORE_INFO_CORE_IDC(x)	FIELD_PREP(GENMASK(7, 4), (x))
+#define		CREATE_INST_CORE_INFO_CORE_NUM(x)	FIELD_PREP(GENMASK(3, 0), (x))
+#define W6_CMD_CREATE_INST_PRIORITY			(W6_REG_BASE + 0x334)
+#define		CREATE_INST_PRIORITY_SECURITY_FLAG(x)	FIELD_PREP(GENMASK(8, 8), (x))
+#define		CREATE_INST_PRIORITY_VALUE(x)		FIELD_PREP(GENMASK(4, 0), (x))
+#define W6_CMD_CREATE_INST_TEMP_BASE			(W6_REG_BASE + 0x348)
+#define W6_CMD_CREATE_INST_TEMP_SIZE			(W6_REG_BASE + 0x34C)
+#define W6_CMD_CREATE_INST_AR_TABLE_BASE		(W6_REG_BASE + 0x358)
+#define W6_CMD_CREATE_INST_TIMEOUT_CYCLE_COUNT		(W6_REG_BASE + 0x3A8)
+
+/* COMMON - SET_FB */
+#define W6_CMD_SET_FB_OPTION				(W6_REG_BASE + 0x204)
+#define		SET_FB_OPTION_ENDIAN(x)			FIELD_PREP(GENMASK(19, 16), (x))
+#define		SET_FB_OPTION_END			BIT(4)
+#define		SET_FB_OPTION_START			BIT(3)
+#define W6_CMD_SET_FB_PIC_INFO				(W6_REG_BASE + 0x300)
+#define		SET_FB_PIC_INFO_C_FMT_IDC(x)		FIELD_PREP(GENMASK(26, 25), (x))
+#define		SET_FB_PIC_INFO_L_BIT_DEPTH(x)		FIELD_PREP(GENMASK(24, 21), (x))
+#define		SET_FB_PIC_INFO_C_BIT_DEPTH(x)		FIELD_PREP(GENMASK(20, 17), (x))
+#define		SET_FB_PIC_INFO_STRIDE(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_SET_FB_PIC_SIZE				(W6_REG_BASE + 0x304)
+#define		SET_FB_PIC_SIZE_WIDTH(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_FB_PIC_SIZE_HEIGHT(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_SET_FB_NUM				(W6_REG_BASE + 0x308)
+#define		SET_FB_NUM_FBC_START_IDX(x)		FIELD_PREP(GENMASK(28, 24), (x))
+#define		SET_FB_NUM_FBC_END_IDX(x)		FIELD_PREP(GENMASK(20, 16), (x))
+#define		SET_FB_NUM_MV_COL_START_IDX(x)		FIELD_PREP(GENMASK(9, 5), (x))
+#define		SET_FB_NUM_MV_COL_END_IDX(x)		FIELD_PREP(GENMASK(4, 0), (x))
+#define W6_CMD_SET_FB_FBC_STRIDE			(W6_REG_BASE + 0x30C)
+#define		SET_FB_FBC_STRIDE_L(x)			FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_FB_FBC_STRIDE_C(x)			FIELD_PREP(GENMASK(15, 0), (x))
+
+/* W6_CMD_SET_FB_FBC_Y(x)
+ * Address range: 0x310, 0x328, ..., 0x478 (step = 0x18)
+ */
+#define W6_CMD_SET_FB_FBC_Y(x)				(W6_REG_BASE + 0x310 + ((x) * 0x18))
+
+/* W6_CMD_SET_FB_FBC_C(x)
+ * Address range: 0x314, 0x32C, ..., 0x47C (step = 0x18)
+ */
+#define W6_CMD_SET_FB_FBC_C(x)				(W6_REG_BASE + 0x314 + ((x) * 0x18))
+
+/* W6_CMD_SET_FB_FBC_Y_OFFSET(x)
+ * Address range: 0x318, 0x330, ..., 0x480 (step = 0x18)
+ */
+#define W6_CMD_SET_FB_FBC_Y_OFFSET(x)			(W6_REG_BASE + 0x318 + ((x) * 0x18))
+
+/* W6_CMD_SET_FB_FBC_C_OFFSET(x)
+ * Address range: 0x31C, 0x334, ..., 0x484 (step = 0x18)
+ */
+#define W6_CMD_SET_FB_FBC_C_OFFSET(x)			(W6_REG_BASE + 0x31C + ((x) * 0x18))
+
+/* W6_CMD_SET_FB_MV_COL(x)
+ * Address range: 0x320, 0x338, ..., 0x488 (step = 0x18)
+ */
+#define W6_CMD_SET_FB_MV_COL(x)				(W6_REG_BASE + 0x320 + ((x) * 0x18))
+
+/* W6_CMD_SET_FB_SUB_SAMPLED(x)
+ * Address range: 0x324, 0x33C, ..., 0x48C (step = 0x18)
+ */
+#define W6_CMD_SET_FB_SUB_SAMPLED(x)			(W6_REG_BASE + 0x324 + ((x) * 0x18))
+#define W6_CMD_SET_FB_DEFAULT_CDF			(W6_REG_BASE + 0x494)
+#define W6_CMD_SET_FB_SEGMAP				(W6_REG_BASE + 0x498)
+#define W6_CMD_SET_FB_MV_COL_PRE_ENT			(W6_REG_BASE + 0x4DC)
+
+/* W6_CMD_SET_FB_FBC_CR(x)
+ * Address range: 0x4F0, 0x4F8, ..., 0x568 (step = 0x8)
+ */
+#define W6_CMD_SET_FB_FBC_CR(x)				(W6_REG_BASE + 0x4F0 + ((x) * 0x8))
+
+/* W6_CMD_SET_FB_FBC_CR_OFFSET(x)
+ * Address range: 0x4F4, 0x4FC, ..., 0x56C (step = 0x8)
+ */
+#define W6_CMD_SET_FB_FBC_CR_OFFSET(x)			(W6_REG_BASE + 0x4F4 + ((x) * 0x8))
+
+/* DECODER - INIT_SEQ */
+#define W6_CMD_DEC_INIT_SEQ_OPTION			(W6_REG_BASE + 0x204)
+#define		INIT_SEQ_OPTION_MODE(x)			FIELD_PREP(GENMASK(5, 0), (x))
+#define W6_CMD_DEC_INIT_SEQ_BS_RD_PTR			(W6_REG_BASE + 0x300)
+#define W6_CMD_DEC_INIT_SEQ_BS_WR_PTR			(W6_REG_BASE + 0x304)
+#define W6_CMD_DEC_INIT_SEQ_BS_OPTION			(W6_REG_BASE + 0x308)
+
+/* DECODER - SET_DISP */
+#define W6_CMD_DEC_SET_DISP_OPTION			(W6_REG_BASE + 0x204)
+#define		SET_DISP_OPTION_ENDIAN(x)		FIELD_PREP(GENMASK(19, 16), (x))
+#define W6_CMD_DEC_SET_DISP_COMMON_PIC_INFO		(W6_REG_BASE + 0x300)
+#define		SET_DISP_COMMON_PIC_INFO_BWB_ON		BIT(28)
+#define		SET_DISP_COMMON_PIC_INFO_C_FMT_IDC(x)	FIELD_PREP(GENMASK(25, 24), (x))
+#define		SET_DISP_COMMON_PIC_INFO_PIXEL_ORDER(x)	FIELD_PREP(GENMASK(23, 23), (x))
+#define		SET_DISP_COMMON_PIC_INFO_OUT_MODE(x)	FIELD_PREP(GENMASK(22, 20), (x))
+#define		SET_DISP_COMMON_PIC_INFO_OUT_FMT(x)	FIELD_PREP(GENMASK(18, 16), (x))
+#define		SET_DISP_COMMON_PIC_INFO_STRIDE(x)	FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_DEC_SET_DISP_PIC_SIZE			(W6_REG_BASE + 0x304)
+#define		SET_DISP_PIC_SIZE_WIDTH(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_DISP_PIC_SIZE_HEIGHT(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_DEC_SET_DISP_PIC_INFO			(W6_REG_BASE + 0x308)
+#define		SET_DISP_PIC_INFO_L_BIT_DEPTH(x)	FIELD_PREP(GENMASK(25, 22), (x))
+#define		SET_DISP_PIC_INFO_C_BIT_DEPTH(x)	FIELD_PREP(GENMASK(21, 18), (x))
+#define		SET_DISP_PIC_INFO_C_FMT_IDC(x)		FIELD_PREP(GENMASK(17, 16), (x))
+#define W6_CMD_DEC_SET_DISP_Y_BASE			(W6_REG_BASE + 0x30C)
+#define W6_CMD_DEC_SET_DISP_CB_BASE			(W6_REG_BASE + 0x310)
+#define W6_CMD_DEC_SET_DISP_CR_BASE			(W6_REG_BASE + 0x314)
+#define W6_CMD_DEC_SET_DISP_SCL_PARAM			(W6_REG_BASE + 0x318)
+#define W6_CMD_DEC_SET_DISP_SCL_PIC_SIZE		(W6_REG_BASE + 0x31C)
+#define		SET_DISP_SCL_PIC_SIZE_WIDTH(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_DISP_SCL_PIC_SIZE_HEIGHT(x)		FIELD_PREP(GENMASK(15, 0), (x))
+
+/* DECODER - DEC_PIC */
+#define W6_CMD_DEC_PIC_OPTION				(W6_REG_BASE + 0x204)
+#define		DEC_PIC_OPTION_MODE(x)			FIELD_PREP(GENMASK(5, 0), (x))
+#define W6_CMD_DEC_PIC_BS_RD_PTR			(W6_REG_BASE + 0x300)
+#define W6_CMD_DEC_PIC_BS_WR_PTR			(W6_REG_BASE + 0x304)
+#define W6_CMD_DEC_PIC_BS_OPTION			(W6_REG_BASE + 0x308)
+#define		DEC_PIC_BS_OPTION_STREAM_END(x)		FIELD_PREP(GENMASK(1, 1), (x))
+#define		DEC_PIC_BS_OPTION_EXPLICIT_END_ON	BIT(0)
+#define W6_CMD_DEC_PIC_SEC_AXI				(W6_REG_BASE + 0x30C)
+#define		DEC_PIC_SEC_AXI_IP_ENABLE(x)		FIELD_PREP(GENMASK(1, 1), (x))
+#define		DEC_PIC_SEC_AXI_LF_ENABLE(x)		FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_DEC_PIC_SEQ_CHANGE_ENABLE_FLAG		(W6_REG_BASE + 0x310)
+#define W6_CMD_DEC_PIC_TEMPORAL_ID_PLUS1		(W6_REG_BASE + 0x318)
+#define		DEC_PIC_TEMPORAL_ID_PLUS1_SPATIAL(x)	FIELD_PREP(GENMASK(11, 9), (x))
+#define		DEC_PIC_TEMPORAL_ID_PLUS1(x)		FIELD_PREP(GENMASK(7, 0), (x))
+#define W6_CMD_DEC_PIC_TIMESTAMP_LOW			(W6_REG_BASE + 0x354)
+#define W6_CMD_DEC_PIC_TIMESTAMP_HIGH			(W6_REG_BASE + 0x358)
+
+/* DECODER - QUERY : GET_RESULT */
+#define W6_RET_DEC_BS_RD_PTR				(W6_REG_BASE + 0x30C)
+#define W6_RET_DEC_SEQ_PARAM				(W6_REG_BASE + 0x310)
+#define		DEC_SEQ_PARAM_TIER(x)			FIELD_GET(GENMASK(29, 29), (x))
+#define		DEC_SEQ_PARAM_PROFILE(x)		FIELD_GET(GENMASK(28, 24), (x))
+#define		PROFILE_H264_BP				2
+#define		PROFILE_H264_HP				4
+#define		PROFILE_H264_MP				13
+#define		PROFILE_H264_HIGH10			14
+#define		PROFILE_H264_EXTENDED			24
+#define		DEC_SEQ_PARAM_PROFILE_COMPATIBILITY(x)	FIELD_GET(GENMASK(19, 12), (x))
+#define		PROFILE_COMPATIBILITY_MAIN		BIT(1)
+#define		PROFILE_COMPATIBILITY_MAIN10		BIT(2)
+#define		PROFILE_COMPATIBILITY_STILL_PICTURE	BIT(3)
+#define		DEC_SEQ_PARAM_LEVEL(x)			FIELD_GET(GENMASK(7, 0), (x))
+#define W6_RET_DEC_COLOR_SAMPLE				(W6_REG_BASE + 0x314)
+#define		DEC_COLOR_SAMPLE_ASPECT_RATIO_IDC(x)	FIELD_GET(GENMASK(23, 16), (x))
+#define		DEC_COLOR_SAMPLE_C_FMT_IDC(x)		FIELD_GET(GENMASK(11, 8), (x))
+#define		DEC_COLOR_SAMPLE_C_BIT_DEPTH(x)		FIELD_GET(GENMASK(7, 4), (x))
+#define		DEC_COLOR_SAMPLE_L_BIT_DEPTH(x)		FIELD_GET(GENMASK(3, 0), (x))
+#define W6_RET_DEC_ASPECT_RATIO				(W6_REG_BASE + 0x318)
+#define W6_RET_DEC_BIT_RATE				(W6_REG_BASE + 0x31C)
+#define W6_RET_DEC_FRAME_RATE_NR			(W6_REG_BASE + 0x320)
+#define W6_RET_DEC_FRAME_RATE_DR			(W6_REG_BASE + 0x324)
+#define W6_RET_DEC_NUM_REQUIRED_FBC_FB			(W6_REG_BASE + 0x328)
+#define W6_RET_DEC_NUM_REORDER_DELAY			(W6_REG_BASE + 0x32C)
+#define W6_RET_DEC_NOTIFICATION				(W6_REG_BASE + 0x334)
+#define W6_RET_DEC_PIC_SIZE				(W6_REG_BASE + 0x33C)
+#define		DEC_PIC_SIZE_WIDTH(x)			FIELD_GET(GENMASK(31, 16), (x))
+#define		DEC_PIC_SIZE_HEIGHT(x)			FIELD_GET(GENMASK(15, 0), (x))
+#define W6_RET_DEC_CROP_TOP_BOTTOM			(W6_REG_BASE + 0x340)
+#define		DEC_CROP_TOP(x)				FIELD_GET(GENMASK(31, 16), (x))
+#define		DEC_CROP_BOTTOM(x)			FIELD_GET(GENMASK(15, 0), (x))
+#define W6_RET_DEC_CROP_LEFT_RIGHT			(W6_REG_BASE + 0x344)
+#define		DEC_CROP_LEFT(x)			FIELD_GET(GENMASK(31, 16), (x))
+#define		DEC_CROP_RIGHT(x)			FIELD_GET(GENMASK(15, 0), (x))
+#define W6_RET_DEC_AU_START_POS				(W6_REG_BASE + 0x348)
+#define W6_RET_DEC_AU_END_POS				(W6_REG_BASE + 0x34C)
+#define W6_RET_DEC_PIC_TYPE				(W6_REG_BASE + 0x350)
+#define		DEC_PIC_TYPE_CTU_SIZE(x)		(16 << FIELD_GET(GENMASK(11, 10), (x)))
+#define		DEC_PIC_TYPE_NAL_UNIT_TYPE(x)		FIELD_GET(GENMASK(9, 4), (x))
+#define		DEC_PIC_TYPE_B				BIT(2)
+#define		DEC_PIC_TYPE_P				BIT(1)
+#define		DEC_PIC_TYPE_I				BIT(0)
+#define W6_RET_DEC_PIC_POC				(W6_REG_BASE + 0x354)
+#define W6_RET_DEC_RECOVERY_POINT			(W6_REG_BASE + 0x358)
+#define W6_RET_DEC_DECODED_ADDR				(W6_REG_BASE + 0x360)
+#define W6_RET_DEC_DISPLAY_ADDR				(W6_REG_BASE + 0x364)
+#define W6_RET_DEC_ERR_CTB_NUM				(W6_REG_BASE + 0x370)
+#define W6_RET_DEC_DISPLAY_FLAG				(W6_REG_BASE + 0x3A8)
+#define W6_RET_DEC_RELEASE_IDC				(W6_REG_BASE + 0x3AC)
+#define W6_RET_DEC_DISP_IDC				(W6_REG_BASE + 0x3B0)
+#define W6_RET_DEC_STREAM_END				(W6_REG_BASE + 0x3C0)
+#define W6_RET_DEC_DECODED_FLAG				(W6_REG_BASE + 0x3C4)
+#define W6_RET_DEC_WARN_INFO				(W6_REG_BASE + 0x3CC)
+#define W6_RET_DEC_ERR_INFO				(W6_REG_BASE + 0x3D0)
+#define W6_RET_DEC_DECODING_SUCCESS			(W6_REG_BASE + 0x3D4)
+#define W6_RET_DEC_TIMESTAMP_LOW			(W6_REG_BASE + 0x3D8)
+#define W6_RET_DEC_TIMESTAMP_HIGH			(W6_REG_BASE + 0x3DC)
+#define W6_RET_DEC_LAST_FRAME_FLAG			(W6_REG_BASE + 0x3E0)
+#define W6_RET_DEC_NUM_REQUIRED_COL_BUF			(W6_REG_BASE + 0x3E4)
+
+/* W6_RET_DEC_DISP_LINEAR_ADDR(x)
+ * Address range: 0x3E8, 0x3EC, ..., 0x460 (step = 0x4)
+ */
+#define W6_RET_DEC_DISP_LINEAR_ADDR(x)			(W6_REG_BASE + 0x3E8 + ((x) * 0x4))
+#define W6_RET_DEC_COLOR_CONFIG				(W6_REG_BASE + 0x57C)
+#define		DEC_COLOR_CONFIG_COLOR_RANGE(x)		FIELD_GET(GENMASK(25, 25), (x))
+#define		DEC_COLOR_CONFIG_MATRIX_COEFF(x)	FIELD_GET(GENMASK(24, 17), (x))
+#define		DEC_COLOR_CONFIG_TRANS_CHAR(x)		FIELD_GET(GENMASK(16, 9), (x))
+#define		DEC_COLOR_CONFIG_COLOR_PRIMARIES(x)	FIELD_GET(GENMASK(8, 1), (x))
+#define		DEC_COLOR_CONFIG_COLOR_PRESENT(x)	FIELD_GET(GENMASK(0, 0), (x))
+
+/* DECODER - QUERY : GET_FLUSH_CMD_INFO */
+
+/* W6_RET_DEC_FLUSH_CMD_DISP_ADDR(x)
+ * Address range: 0x300, 0x304, ..., 0x378 (step = 0x4)
+ */
+#define W6_RET_DEC_FLUSH_CMD_DISP_ADDR(x)		(W6_REG_BASE + 0x300 + ((x) * 0x4))
+#define W6_RET_DEC_FLUSH_CMD_BUF_STATE_UNUSED_IDC	(W6_REG_BASE + 0x57C)
+#define W6_RET_DEC_FLUSH_CMD_BUF_STATE_USED_IDC		(W6_REG_BASE + 0x580)
+#define W6_RET_DEC_FLUSH_CMD_BUF_STATE_USING_IDC	(W6_REG_BASE + 0x584)
+
+/* ENCODER - SET_PARAM */
+#define W6_CMD_ENC_SET_PARAM_OPTION			(W6_REG_BASE + 0x204)
+#define W6_CMD_ENC_SET_PARAM_ENABLE			(W6_REG_BASE + 0x300)
+#define		SET_PARAM_ENABLE_RC_TARGET_RATE		BIT(10)
+#define W6_CMD_ENC_SET_PARAM_SRC_SIZE			(W6_REG_BASE + 0x304)
+#define		SET_PARAM_SRC_SIZE_HEIGHT(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_SRC_SIZE_WIDTH(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_CUSTOM_MAP_ENDIAN		(W6_REG_BASE + 0x308)
+#define W6_CMD_ENC_SET_PARAM_SPS			(W6_REG_BASE + 0x30C)
+#define		SET_PARAM_SPS_DEFAULT_SCALING_LIST(x)	FIELD_PREP(GENMASK(31, 31), (x))
+#define		SET_PARAM_SPS_STILL_PICTURE(x)		FIELD_PREP(GENMASK(30, 30), (x))
+#define		SET_PARAM_SPS_AUTO_LEVEL_ADJUSTING_ON	BIT(28)
+#define		SET_PARAM_SPS_STRONG_INTRA_SMOOTHING(x)	FIELD_PREP(GENMASK(27, 27), (x))
+#define		SET_PARAM_SPS_INTRA_TRANSFORM_SKIP_ON	BIT(25)
+#define		SET_PARAM_SPS_SAMPLE_ADAPTIVE_OFFSET(x)	FIELD_PREP(GENMASK(24, 24), (x))
+#define		SET_PARAM_SPS_TEMPORAL_MVP(x)		FIELD_PREP(GENMASK(23, 23), (x))
+#define		SET_PARAM_SPS_LONGTERM_REFERENCE(x)	FIELD_PREP(GENMASK(21, 21), (x))
+#define		SET_PARAM_SPS_C_FMT_IDC(x)		FIELD_PREP(GENMASK(20, 19), (x))
+#define		SET_PARAM_SPS_BIT_DEPTH(x)		FIELD_PREP(GENMASK(18, 14), (x))
+#define		SET_PARAM_SPS_TIER(x)			FIELD_PREP(GENMASK(13, 12), (x))
+#define		SET_PARAM_SPS_LEVEL(x)			FIELD_PREP(GENMASK(11, 3), (x))
+#define		SET_PARAM_SPS_PROFILE(x)		FIELD_PREP(GENMASK(2, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_PPS			(W6_REG_BASE + 0x310)
+#define		SET_PARAM_PPS_ENTROPY_CODING_MODE(x)	FIELD_PREP(GENMASK(30, 30), (x))
+#define		SET_PARAM_PPS_TRANSFORM8X8(x)		FIELD_PREP(GENMASK(29, 29), (x))
+#define		SET_PARAM_PPS_CR_QP_OFFSET(x)		FIELD_PREP(GENMASK(23, 19), (x))
+#define		SET_PARAM_PPS_CB_QP_OFFSET(x)		FIELD_PREP(GENMASK(18, 14), (x))
+#define		SET_PARAM_PPS_TC_OFFSET_DIV2(x)		FIELD_PREP(GENMASK(13, 10), (x))
+#define		SET_PARAM_PPS_BETA_OFFSET_DIV2(x)	FIELD_PREP(GENMASK(9, 6), (x))
+#define		SET_PARAM_PPS_DEBLOCKING_FILTER(x)	FIELD_PREP(GENMASK(5, 5), (x))
+#define		SET_PARAM_PPS_LF_SLICE_BOUNDARY(x)	FIELD_PREP(GENMASK(2, 2), (x))
+#define		SET_PARAM_PPS_CONST_INTRA_PREDICTION(x)	FIELD_PREP(GENMASK(1, 1), (x))
+#define W6_CMD_ENC_SET_PARAM_GOP			(W6_REG_BASE + 0x314)
+#define		SET_PARAM_GOP_TEMP_LAYER_CNT(x)		FIELD_PREP(GENMASK(19, 16), (x))
+#define		SET_PARAM_GOP_TEMP_LAYER3_QP_ENABLE(x)	FIELD_PREP(GENMASK(11, 11), (x))
+#define		SET_PARAM_GOP_TEMP_LAYER2_QP_ENABLE(x)	FIELD_PREP(GENMASK(10, 10), (x))
+#define		SET_PARAM_GOP_TEMP_LAYER1_QP_ENABLE(x)	FIELD_PREP(GENMASK(9, 9), (x))
+#define		SET_PARAM_GOP_TEMP_LAYER0_QP_ENABLE(x)	FIELD_PREP(GENMASK(8, 8), (x))
+#define		SET_PARAM_GOP_PRESET_IDX(x)		FIELD_PREP(GENMASK(7, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_INTRA			(W6_REG_BASE + 0x318)
+#define		SET_PARAM_INTRA_PERIOD(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_INTRA_HEADER_MODE(x)		FIELD_PREP(GENMASK(10, 9), (x))
+#define		SET_PARAM_INTRA_QP(x)			FIELD_PREP(GENMASK(8, 3), (x))
+#define		SET_PARAM_INTRA_REFRESH_TYPE(x)		FIELD_PREP(GENMASK(2, 0), (x))
+
+/* W6_CMD_ENC_SET_PARAM_INTRA for AVC specific fields */
+#define		SET_PARAM_INTRA_HEADER_MODE_AVC(x)	FIELD_PREP(GENMASK(29, 28), (x))
+#define		SET_PARAM_INTRA_IDR_PERIOD_AVC(x)	FIELD_PREP(GENMASK(27, 17), (x))
+#define		SET_PARAM_INTRA_PERIOD_AVC(x)		FIELD_PREP(GENMASK(16, 6), (x))
+#define		SET_PARAM_INTRA_QP_AVC(x)		FIELD_PREP(GENMASK(5, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_CONF_WIN0			(W6_REG_BASE + 0x31C)
+#define		SET_PARAM_CONF_WIN0_BOTTOM(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_CONF_WIN0_TOP(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_CONF_WIN1			(W6_REG_BASE + 0x320)
+#define		SET_PARAM_CONF_WIN1_RIGHT(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_CONF_WIN1_LEFT(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_RDO			(W6_REG_BASE + 0x324)
+#define		SET_PARAM_RDO_CUSTOM_LAMBDA(x)		FIELD_PREP(GENMASK(22, 22), (x))
+#define		SET_PARAM_RDO_ME_SEARCH_CENTER_ON	BIT(21)
+#define		SET_PARAM_RDO_QROUND_OFFSET(x)		FIELD_PREP(GENMASK(17, 17), (x))
+#define		SET_PARAM_RDO_ADAPTIVE_ROUND_ON		BIT(3)
+#define		SET_PARAM_RDO_HVS_QP(x)			FIELD_PREP(GENMASK(2, 2), (x))
+#define W6_CMD_ENC_SET_PARAM_SLICE			(W6_REG_BASE + 0x328)
+#define		SET_PARAM_SLICE_ARGUMENT(x)		FIELD_PREP(GENMASK(31, 3), (x))
+#define		SET_PARAM_SLICE_MODE(x)			FIELD_PREP(GENMASK(2, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_INTRA_REFRESH		(W6_REG_BASE + 0x32C)
+#define		SET_PARAM_INTRA_REFRESH_ARGUMENT(x)	FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_INTRA_REFRESH_MODE(x)		FIELD_PREP(GENMASK(2, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_INTRA_QP			(W6_REG_BASE + 0x330)
+#define		SET_PARAM_INTRA_QP_MAX(x)		FIELD_PREP(GENMASK(11, 6), (x))
+#define		SET_PARAM_INTRA_QP_MIN(x)		FIELD_PREP(GENMASK(5, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_RC_FRAME_RATE		(W6_REG_BASE + 0x334)
+#define W6_CMD_ENC_SET_PARAM_RC_TARGET_RATE		(W6_REG_BASE + 0x338)
+#define W6_CMD_ENC_SET_PARAM_RC				(W6_REG_BASE + 0x33C)
+#define		SET_PARAM_RC_UPDATE_SPEED(x)		FIELD_PREP(GENMASK(31, 24), (x))
+#define		SET_PARAM_RC_INITIAL_LEVEL(x)		FIELD_PREP(GENMASK(23, 20), (x))
+#define		SET_PARAM_RC_INITIAL_QP(x)		FIELD_PREP(GENMASK(19, 14), (x))
+#define		SET_PARAM_RC_MODE(x)			FIELD_PREP(GENMASK(13, 13), (x))
+#define		SET_PARAM_RC_PIC_LEVEL_MAX_DELTA_QP(x)	FIELD_PREP(GENMASK(12, 7), (x))
+#define		SET_PARAM_RC_VBV_OVERFLOW_DROP_FRAME(x)	FIELD_PREP(GENMASK(3, 3), (x))
+#define		SET_PARAM_RC_CU_LEVEL_ENABLE(x)		FIELD_PREP(GENMASK(1, 1), (x))
+#define		SET_PARAM_RC_ENABLE(x)			FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_HVS			(W6_REG_BASE + 0x340)
+#define		SET_PARAM_HVS_MAX_DELTA_QP(x)		FIELD_PREP(GENMASK(17, 12), (x))
+#define		SET_PARAM_HVS_QP_SCALE(x)		FIELD_PREP(GENMASK(3, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_RC_MAX_BITRATE		(W6_REG_BASE + 0x344)
+#define W6_CMD_ENC_SET_PARAM_RC_VBV_BUFFER_SIZE		(W6_REG_BASE + 0x348)
+#define W6_CMD_ENC_SET_PARAM_INTER_QP			(W6_REG_BASE + 0x34C)
+#define		SET_PARAM_INTER_QP_MAX_B(x)		FIELD_PREP(GENMASK(23, 18), (x))
+#define		SET_PARAM_INTER_QP_MIN_B(x)		FIELD_PREP(GENMASK(17, 12), (x))
+#define		SET_PARAM_INTER_QP_MAX_P(x)		FIELD_PREP(GENMASK(11, 6), (x))
+#define		SET_PARAM_INTER_QP_MIN_P(x)		FIELD_PREP(GENMASK(5, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_ROT			(W6_REG_BASE + 0x350)
+#define		SET_PARAM_ROT_MIRH			BIT(4)
+#define		SET_PARAM_ROT_MIRV			BIT(3)
+#define		SET_PARAM_ROT_270			FIELD_PREP(GENMASK(2, 1), 3)
+#define		SET_PARAM_ROT_180			FIELD_PREP(GENMASK(2, 1), 2)
+#define		SET_PARAM_ROT_90			FIELD_PREP(GENMASK(2, 1), 1)
+#define		SET_PARAM_ROT_ENABLE			BIT(0)
+#define W6_CMD_ENC_SET_PARAM_NUM_UNITS_IN_TICK		(W6_REG_BASE + 0x354)
+#define W6_CMD_ENC_SET_PARAM_TIME_SCALE			(W6_REG_BASE + 0x358)
+#define W6_CMD_ENC_SET_PARAM_NUM_TICKS_POC_DIFF_ONE	(W6_REG_BASE + 0x35C)
+#define W6_CMD_ENC_SET_PARAM_MAX_INTRA_PIC_BIT		(W6_REG_BASE + 0x360)
+#define W6_CMD_ENC_SET_PARAM_MAX_INTER_PIC_BIT		(W6_REG_BASE + 0x364)
+#define W6_CMD_ENC_SET_PARAM_BG				(W6_REG_BASE + 0x36C)
+#define		SET_PARAM_BG_DELTA_QP(x)		FIELD_PREP(GENMASK(29, 24), (x))
+#define		SET_PARAM_BG_THRESHOLD_MEAN_DIFF(x)	FIELD_PREP(GENMASK(17, 10), (x))
+#define		SET_PARAM_BG_THRESHOLD_MAX_DIFF(x)	FIELD_PREP(GENMASK(9, 1), (x))
+#define		SET_PARAM_BG_ENABLE(x)			FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_NON_VCL_PARAM		(W6_REG_BASE + 0x370)
+#define		SET_PARAM_NON_VCL_PARAM_ENCODE_VUI	BIT(1)
+#define W6_CMD_ENC_SET_PARAM_VUI_RBSP_ADDR		(W6_REG_BASE + 0x374)
+#define W6_CMD_ENC_SET_PARAM_HRD_RBSP_ADDR		(W6_REG_BASE + 0x378)
+#define W6_CMD_ENC_SET_PARAM_QROUND_OFFSET		(W6_REG_BASE + 0x380)
+#define		SET_PARAM_QROUND_OFFSET_INTER(x)	FIELD_PREP(GENMASK(23, 13), (x))
+#define		SET_PARAM_QROUND_OFFSET_INTRA(x)	FIELD_PREP(GENMASK(12, 2), (x))
+#define W6_CMD_ENC_SET_PARAM_QUANT1			(W6_REG_BASE + 0x384)
+#define W6_CMD_ENC_SET_PARAM_QUANT2			(W6_REG_BASE + 0x388)
+#define		SET_PARAM_QUANT2_LAMBDA_DQP_INTER(x)	FIELD_PREP(GENMASK(19, 14), (x))
+#define		SET_PARAM_QUANT2_LAMBDA_DQP_INTRA(x)	FIELD_PREP(GENMASK(13, 8), (x))
+#define W6_CMD_ENC_SET_PARAM_CUSTOM_GOP			(W6_REG_BASE + 0x38C)
+
+/* W6_CMD_ENC_SET_PARAM_CUSTOM_GOP_PIC(x)
+ * Address range: 0x390, 0x394, ..., 0x3AC (step = 0x4)
+ */
+#define W6_CMD_ENC_SET_PARAM_CUSTOM_GOP_PIC(x)		(W6_REG_BASE + 0x390 + ((x) * 0x4))
+#define		SET_PARAM_CUSTOM_GOP_PIC_TEMP_ID(x)	FIELD_PREP(GENMASK(29, 26), (x))
+#define		SET_PARAM_CUSTOM_GOP_PIC_REF1_POC(x)	FIELD_PREP(GENMASK(25, 20), (x))
+#define		SET_PARAM_CUSTOM_GOP_PIC_REF0_POC(x)	FIELD_PREP(GENMASK(19, 14), (x))
+#define		SET_PARAM_CUSTOM_GOP_PIC_MULTI_REF_P(x)	FIELD_PREP(GENMASK(13, 13), (x))
+#define		SET_PARAM_CUSTOM_GOP_PIC_QP(x)		FIELD_PREP(GENMASK(12, 7), (x))
+#define		SET_PARAM_CUSTOM_GOP_PIC_POC_OFFSET(x)	FIELD_PREP(GENMASK(6, 2), (x))
+#define		SET_PARAM_CUSTOM_GOP_PIC_TYPE(x)	FIELD_PREP(GENMASK(1, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_TILE_PARAM			(W6_REG_BASE + 0x3D0)
+
+/* W6_CMD_ENC_SET_PARAM_CUSTOM_LAMBDA(x)
+ * Address range: 0x3D4, 0x3D8, ..., 0x4A0 (step = 0x4)
+ */
+#define W6_CMD_ENC_SET_PARAM_CUSTOM_LAMBDA(x)		(W6_REG_BASE + 0x3D4 + ((x) * 0x4))
+#define		SET_PARAM_CUSTOM_LAMBDA_SSD(x)		FIELD_PREP(GENMASK(20, 7), (x))
+#define		SET_PARAM_CUSTOM_LAMBDA_SAD(x)		FIELD_PREP(GENMASK(6, 0), (x))
+
+/* W6_CMD_ENC_SET_PARAM_TEMP_LAYER_QP(x)
+ * Address range: 0x4A4, 0x4A8, ..., 0x4B0 (step = 0x4)
+ */
+#define W6_CMD_ENC_SET_PARAM_TEMP_LAYER_QP(x)		(W6_REG_BASE + 0x4A4 + ((x) * 0x4))
+#define		SET_PARAM_TEMP_LAYER_QP_B(x)		FIELD_PREP(GENMASK(17, 12), (x))
+#define		SET_PARAM_TEMP_LAYER_QP_P(x)		FIELD_PREP(GENMASK(11, 6), (x))
+#define		SET_PARAM_TEMP_LAYER_QP_I(x)		FIELD_PREP(GENMASK(5, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_SCALER_SIZE		(W6_REG_BASE + 0x4B4)
+#define		SET_PARAM_SCALER_SIZE_HEIGHT(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_SCALER_SIZE_WIDTH(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_SCALER			(W6_REG_BASE + 0x4B8)
+#define		SET_PARAM_SCALER_COEF_MODE(x)		FIELD_PREP(GENMASK(2, 1), (x))
+#define		SET_PARAM_SCALER_ENABLE(x)		FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_COLOR			(W6_REG_BASE + 0x4F8)
+#define		SET_PARAM_COLOR_RANGE(x)		FIELD_PREP(GENMASK(25, 25), (x))
+#define		SET_PARAM_COLOR_MATRIX_COEFF(x)		FIELD_PREP(GENMASK(24, 17), (x))
+#define		SET_PARAM_COLOR_TRANS_CHAR(x)		FIELD_PREP(GENMASK(16, 9), (x))
+#define		SET_PARAM_COLOR_PRIMARIES(x)		FIELD_PREP(GENMASK(8, 1), (x))
+#define		SET_PARAM_COLOR_DESCRIPTION_PRESENT_ON	BIT(0)
+#define W6_CMD_ENC_SET_PARAM_SAR			(W6_REG_BASE + 0x4FC)
+#define		SET_PARAM_SAR_ASPECT_RATIO_IDC(x)	FIELD_PREP(GENMASK(8, 1), (x))
+#define		SET_PARAM_SAR_ASPECT_RATIO_ENABLE(x)	FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_SET_PARAM_SAR_EXTENDED		(W6_REG_BASE + 0x500)
+#define		SET_PARAM_SAR_EXTENDED_HEIGHT(x)	FIELD_PREP(GENMASK(31, 16), (x))
+#define		SET_PARAM_SAR_EXTENDED_WIDTH(x)		FIELD_PREP(GENMASK(15, 0), (x))
+
+/* ENCODER - ENC_PIC */
+#define W6_CMD_ENC_PIC_BS_START				(W6_REG_BASE + 0x300)
+#define W6_CMD_ENC_PIC_BS_SIZE				(W6_REG_BASE + 0x304)
+#define W6_CMD_ENC_PIC_BS_OPTION			(W6_REG_BASE + 0x308)
+#define W6_CMD_ENC_PIC_SEC_AXI				(W6_REG_BASE + 0x30C)
+#define		ENC_PIC_SEC_AXI_RDO_ENABLE(x)		FIELD_PREP(GENMASK(1, 1), (x))
+#define		ENC_PIC_SEC_AXI_LF_ENABLE(x)		FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_PIC_REPORT				(W6_REG_BASE + 0x310)
+#define		ENC_PIC_REPORT_MV_HISTO_ENABLE(x)	FIELD_PREP(GENMASK(1, 1), (x))
+#define W6_CMD_ENC_PIC_MV_HISTO0			(W6_REG_BASE + 0x318)
+#define		ENC_PIC_MV_HISTO0_THRESHOLD0(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		ENC_PIC_MV_HISTO0_THRESHOLD1(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_PIC_MV_HISTO1			(W6_REG_BASE + 0x31C)
+#define		ENC_PIC_MV_HISTO1_THRESHOLD2(x)		FIELD_PREP(GENMASK(31, 16), (x))
+#define		ENC_PIC_MV_HISTO1_THRESHOLD3(x)		FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_PIC_CUSTOM_MAP_PARAM			(W6_REG_BASE + 0x320)
+#define W6_CMD_ENC_PIC_CUSTOM_MAP_ADDR			(W6_REG_BASE + 0x324)
+#define W6_CMD_ENC_PIC_SRC_PIC_IDX			(W6_REG_BASE + 0x32C)
+#define		ENC_PIC_SRC_PIC_IDX_END			0xFFFFFFFF
+#define W6_CMD_ENC_PIC_SRC_ADDR_Y			(W6_REG_BASE + 0x330)
+#define W6_CMD_ENC_PIC_SRC_ADDR_U			(W6_REG_BASE + 0x334)
+#define W6_CMD_ENC_PIC_SRC_ADDR_V			(W6_REG_BASE + 0x338)
+#define W6_CMD_ENC_PIC_SRC_STRIDE			(W6_REG_BASE + 0x33C)
+#define		ENC_PIC_SRC_STRIDE_L(x)			FIELD_PREP(GENMASK(31, 16), (x))
+#define		ENC_PIC_SRC_STRIDE_C(x)			FIELD_PREP(GENMASK(15, 0), (x))
+#define W6_CMD_ENC_PIC_SRC_FMT				(W6_REG_BASE + 0x340)
+#define		ENC_PIC_SRC_FMT_C_FMT_IDC(x)		FIELD_PREP(GENMASK(30, 28), (x))
+#define		ENC_PIC_SRC_FMT_CSC_24BIT(x)		FIELD_PREP(GENMASK(25, 25), (x))
+#define		ENC_PIC_SRC_FMT_CSC_AYUV(x)		FIELD_PREP(GENMASK(24, 24), (x))
+#define		ENC_PIC_SRC_FMT_CSC_ENABLE(x)		FIELD_PREP(GENMASK(20, 20), (x))
+#define		ENC_PIC_SRC_FMT_CSC_FMT_ORDER(x)	FIELD_PREP(GENMASK(19, 16), (x))
+#define		ENC_PIC_SRC_FMT_ENDIAN(x)		FIELD_PREP(GENMASK(15, 12), (x))
+#define		ENC_PIC_SRC_FMT_10BIT_ORDER(x)		FIELD_PREP(GENMASK(6, 6), (x))
+#define		ENC_PIC_SRC_FMT_10BIT_3PIXEL_4BYTE(x)	FIELD_PREP(GENMASK(5, 5), (x))
+#define		ENC_PIC_SRC_FMT_10BIT_ENABLE(x)		FIELD_PREP(GENMASK(4, 4), (x))
+#define		ENC_PIC_SRC_FMT_YUV422_PACKED_ORDER(x)	FIELD_PREP(GENMASK(3, 3), (x))
+#define		ENC_PIC_SRC_FMT_CBCR_ORDER(x)		FIELD_PREP(GENMASK(2, 2), (x))
+#define		ENC_PIC_SRC_FMT_2PLANE(x)		FIELD_PREP(GENMASK(1, 1), (x))
+#define		ENC_PIC_SRC_FMT_YUV422_PACKED(x)	FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_PIC_SRC_AXI_SEL			(W6_REG_BASE + 0x348)
+#define		ENC_PIC_SRC_AXI_SEL_PRIMARY_PORT	1
+#define		ENC_PIC_SRC_AXI_SEL_PRP_PORT		0
+#define W6_CMD_ENC_PIC_CODE_OPTION			(W6_REG_BASE + 0x34C)
+#define		ENC_PIC_CODE_OPTION_ENCODE_VCL		BIT(1)
+#define		ENC_PIC_CODE_OPTION_ENCODE_HEADER	BIT(0)
+#define W6_CMD_ENC_PIC_PARAM				(W6_REG_BASE + 0x350)
+#define		ENC_PIC_PARAM_INTRA_4X4(x)		FIELD_PREP(GENMASK(29, 28), (x))
+#define		ENC_PIC_PARAM_FORCE_PIC_TYPE(x)		FIELD_PREP(GENMASK(23, 21), (x))
+#define		ENC_PIC_PARAM_FORCE_PIC_TYPE_ENABLE(x)	FIELD_PREP(GENMASK(20, 20), (x))
+#define		ENC_PIC_PARAM_FORCE_PIC_QP_B(x)		FIELD_PREP(GENMASK(19, 14), (x))
+#define		ENC_PIC_PARAM_FORCE_PIC_QP_P(x)		FIELD_PREP(GENMASK(13, 8), (x))
+#define		ENC_PIC_PARAM_FORCE_PIC_QP_I(x)		FIELD_PREP(GENMASK(7, 2), (x))
+#define		ENC_PIC_PARAM_FORCE_PIC_QP_ENABLE(x)	FIELD_PREP(GENMASK(1, 1), (x))
+#define		ENC_PIC_PARAM_PIC_SKIP_FLAG(x)		FIELD_PREP(GENMASK(0, 0), (x))
+#define W6_CMD_ENC_PIC_LONGTERM_PIC			(W6_REG_BASE + 0x354)
+#define W6_CMD_ENC_PIC_PREFIX_SEI_NAL_ADDR		(W6_REG_BASE + 0x358)
+#define W6_CMD_ENC_PIC_PREFIX_SEI_INFO			(W6_REG_BASE + 0x35C)
+#define W6_CMD_ENC_PIC_SUFFIX_SEI_NAL_ADDR		(W6_REG_BASE + 0x360)
+#define W6_CMD_ENC_PIC_SUFFIX_SEI_INFO			(W6_REG_BASE + 0x364)
+#define W6_CMD_ENC_PIC_CSC_COEFF0			(W6_REG_BASE + 0x374)
+#define		ENC_PIC_CSC_COEFF0_RY(x)		FIELD_PREP(GENMASK(29, 20), (x))
+#define		ENC_PIC_CSC_COEFF0_GY(x)		FIELD_PREP(GENMASK(19, 10), (x))
+#define		ENC_PIC_CSC_COEFF0_BY(x)		FIELD_PREP(GENMASK(9, 0), (x))
+#define W6_CMD_ENC_PIC_CSC_COEFF1			(W6_REG_BASE + 0x378)
+#define		ENC_PIC_CSC_COEFF1_RCB(x)		FIELD_PREP(GENMASK(29, 20), (x))
+#define		ENC_PIC_CSC_COEFF1_GCB(x)		FIELD_PREP(GENMASK(19, 10), (x))
+#define		ENC_PIC_CSC_COEFF1_BCB(x)		FIELD_PREP(GENMASK(9, 0), (x))
+#define W6_CMD_ENC_PIC_CSC_COEFF2			(W6_REG_BASE + 0x37C)
+#define		ENC_PIC_CSC_COEFF2_RCR(x)		FIELD_PREP(GENMASK(29, 20), (x))
+#define		ENC_PIC_CSC_COEFF2_GCR(x)		FIELD_PREP(GENMASK(19, 10), (x))
+#define		ENC_PIC_CSC_COEFF2_BCR(x)		FIELD_PREP(GENMASK(9, 0), (x))
+#define W6_CMD_ENC_PIC_CSC_COEFF3			(W6_REG_BASE + 0x380)
+#define		ENC_PIC_CSC_COEFF3_OFFSET_Y(x)		FIELD_PREP(GENMASK(29, 20), (x))
+#define		ENC_PIC_CSC_COEFF3_OFFSET_CB(x)		FIELD_PREP(GENMASK(19, 10), (x))
+#define		ENC_PIC_CSC_COEFF3_OFFSET_CR(x)		FIELD_PREP(GENMASK(9, 0), (x))
+#define W6_CMD_ENC_PIC_TIMESTAMP_LOW			(W6_REG_BASE + 0x448)
+#define W6_CMD_ENC_PIC_TIMESTAMP_HIGH			(W6_REG_BASE + 0x44C)
+
+/* ENCODER - QUERY : GET_RESULT */
+#define W6_RET_ENC_RD_PTR				(W6_REG_BASE + 0x300)
+#define W6_RET_ENC_WR_PTR				(W6_REG_BASE + 0x304)
+#define W6_RET_ENC_NUM_REQUIRED_FBC_FB			(W6_REG_BASE + 0x308)
+#define W6_RET_ENC_MIN_SRC_BUF_NUM			(W6_REG_BASE + 0x30C)
+#define W6_RET_ENC_PIC_TYPE				(W6_REG_BASE + 0x310)
+#define W6_RET_ENC_PIC_POC				(W6_REG_BASE + 0x314)
+#define W6_RET_ENC_PIC_IDX				(W6_REG_BASE + 0x318)
+#define W6_RET_ENC_PIC_SLICE_NUM			(W6_REG_BASE + 0x31C)
+#define W6_RET_ENC_PIC_SKIP				(W6_REG_BASE + 0x320)
+#define W6_RET_ENC_PIC_NUM_INTRA			(W6_REG_BASE + 0x324)
+#define W6_RET_ENC_PIC_NUM_MERGE			(W6_REG_BASE + 0x328)
+#define W6_RET_ENC_PIC_NON_REF_PIC_FLAG			(W6_REG_BASE + 0x32C)
+#define W6_RET_ENC_PIC_NUM_SKIP				(W6_REG_BASE + 0x330)
+#define W6_RET_ENC_PIC_AVG_CTU_QP			(W6_REG_BASE + 0x334)
+#define W6_RET_ENC_PIC_BYTE				(W6_REG_BASE + 0x338)
+#define W6_RET_ENC_GOP_PIC_IDX				(W6_REG_BASE + 0x33C)
+#define W6_RET_ENC_USED_SRC_IDX				(W6_REG_BASE + 0x340)
+#define W6_RET_ENC_PIC_NUM				(W6_REG_BASE + 0x344)
+#define W6_RET_ENC_VCL_NUT				(W6_REG_BASE + 0x348)
+#define W6_RET_ENC_PIC_DIST_LOW				(W6_REG_BASE + 0x350)
+#define W6_RET_ENC_PIC_DIST_HIGH			(W6_REG_BASE + 0x354)
+#define W6_RET_ENC_PIC_MAX_LATENCY_PICTURES		(W6_REG_BASE + 0x358)
+#define W6_RET_ENC_HISTO_CNT0				(W6_REG_BASE + 0x360)
+#define W6_RET_ENC_HISTO_CNT1				(W6_REG_BASE + 0x364)
+#define W6_RET_ENC_HISTO_CNT2				(W6_REG_BASE + 0x368)
+#define W6_RET_ENC_HISTO_CNT3				(W6_REG_BASE + 0x36C)
+#define W6_RET_ENC_HISTO_CNT4				(W6_REG_BASE + 0x370)
+#define W6_RET_ENC_WARN_INFO				(W6_REG_BASE + 0x3AC)
+#define W6_RET_ENC_ERR_INFO				(W6_REG_BASE + 0x3B0)
+#define W6_RET_ENC_ENCODING_SUCCESS			(W6_REG_BASE + 0x3B4)
+#define W6_RET_ENC_SUM_ME0_X_DIR_LOWER			(W6_REG_BASE + 0x3B8)
+#define W6_RET_ENC_SUM_ME0_X_DIR_HIGHER			(W6_REG_BASE + 0x3BC)
+#define W6_RET_ENC_SUM_ME0_Y_DIR_LOWER			(W6_REG_BASE + 0x3C0)
+#define W6_RET_ENC_SUM_ME0_Y_DIR_HIGHER			(W6_REG_BASE + 0x3C4)
+#define W6_RET_ENC_SUM_ME1_X_DIR_LOWER			(W6_REG_BASE + 0x3C8)
+#define W6_RET_ENC_SUM_ME1_X_DIR_HIGHER			(W6_REG_BASE + 0x3CC)
+#define W6_RET_ENC_SUM_ME1_Y_DIR_LOWER			(W6_REG_BASE + 0x3D0)
+#define W6_RET_ENC_SUM_ME1_Y_DIR_HIGHER			(W6_REG_BASE + 0x3D4)
+#define W6_RET_ENC_SRC_Y_ADDR				(W6_REG_BASE + 0x3E8)
+#define W6_RET_ENC_CUSTOM_MAP_ADDR			(W6_REG_BASE + 0x3EC)
+#define W6_RET_ENC_PREFIX_SEI_NAL_ADDR			(W6_REG_BASE + 0x3F0)
+#define W6_RET_ENC_SUFFIX_SEI_NAL_ADDR			(W6_REG_BASE + 0x3F4)
+#define W6_RET_ENC_NUM_REQUIRED_COL_BUF			(W6_REG_BASE + 0x404)
+#define W6_RET_ENC_TIMESTAMP_LOW			(W6_REG_BASE + 0x44C)
+#define W6_RET_ENC_TIMESTAMP_HIGH			(W6_REG_BASE + 0x450)
+
+#endif /* __WAVE6_REGDEFINE_H__ */
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vdi.h b/drivers/media/platform/chips-media/wave6/wave6-vdi.h
new file mode 100644
index 000000000000..746a4e128ba8
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vdi.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - low level access interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VDI_H__
+#define __WAVE6_VDI_H__
+
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <media/videobuf2-dma-contig.h>
+#include "wave6-vpuconfig.h"
+
+enum endian_mode {
+	VDI_128BIT_BIG_ENDIAN = 0,
+	VDI_128BIT_BE_BYTE_SWAP,
+	VDI_128BIT_BE_WORD_SWAP,
+	VDI_128BIT_BE_WORD_BYTE_SWAP,
+	VDI_128BIT_BE_DWORD_SWAP,
+	VDI_128BIT_BE_DWORD_BYTE_SWAP,
+	VDI_128BIT_BE_DWORD_WORD_SWAP,
+	VDI_128BIT_BE_DWORD_WORD_BYTE_SWAP,
+	VDI_128BIT_LE_DWORD_WORD_BYTE_SWAP,
+	VDI_128BIT_LE_DWORD_WORD_SWAP,
+	VDI_128BIT_LE_DWORD_BYTE_SWAP,
+	VDI_128BIT_LE_DWORD_SWAP,
+	VDI_128BIT_LE_WORD_BYTE_SWAP,
+	VDI_128BIT_LE_WORD_SWAP,
+	VDI_128BIT_LE_BYTE_SWAP,
+	VDI_128BIT_LITTLE_ENDIAN = 15,
+	VDI_ENDIAN_MAX
+};
+
+/**
+ * struct vpu_buf - VPU buffer for a coherent DMA buffer
+ * @size:	Buffer size
+ * @daddr:	Mapped address for device access
+ * @vaddr:	Kernel virtual address
+ * @dev:	Device pointer for DMA API
+ *
+ * Represents a buffer allocated via dma_alloc_coherent().
+ */
+struct vpu_buf {
+	size_t size;
+	dma_addr_t daddr;
+	void *vaddr;
+	struct device *dev;
+};
+
+static inline void wave6_vdi_writel(void __iomem *base, u32 addr, u32 data)
+{
+	writel(data, base + addr);
+}
+
+static inline unsigned int wave6_vdi_readl(void __iomem *base, u32 addr)
+{
+	return readl(base + addr);
+}
+
+static inline int wave6_vdi_alloc_dma(struct device *dev, struct vpu_buf *vb)
+{
+	void *vaddr;
+	dma_addr_t daddr;
+
+	if (!vb || !vb->size)
+		return -EINVAL;
+
+	vaddr = dma_alloc_coherent(dev, vb->size, &daddr, GFP_KERNEL);
+	if (!vaddr)
+		return -ENOMEM;
+
+	vb->vaddr = vaddr;
+	vb->daddr = daddr;
+	vb->dev = dev;
+
+	return 0;
+}
+
+static inline void wave6_vdi_free_dma(struct vpu_buf *vb)
+{
+	if (!vb || !vb->size || !vb->vaddr)
+		return;
+
+	dma_free_coherent(vb->dev, vb->size, vb->vaddr, vb->daddr);
+
+	memset(vb, 0, sizeof(*vb));
+}
+
+#endif /* __WAVE6_VDI_H__ */
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpuapi.c b/drivers/media/platform/chips-media/wave6/wave6-vpuapi.c
new file mode 100644
index 000000000000..266885de92af
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpuapi.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 helper interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/bug.h>
+#include "wave6-vpuapi.h"
+#include "wave6-regdefine.h"
+#include "wave6-hw.h"
+#include "wave6-vpu-dbg.h"
+#include "wave6-trace.h"
+
+int wave6_vpu_dec_open(struct vpu_instance *inst, struct dec_open_param *pop)
+{
+	struct dec_info *p_dec_info;
+	int ret;
+	struct vpu_core_device *core = inst->dev;
+
+	guard(mutex)(&core->hw_lock);
+
+	if (!wave6_vpu_is_init(core))
+		return -ENODEV;
+
+	inst->codec_info = kzalloc_obj(*inst->codec_info);
+	if (!inst->codec_info)
+		return -ENOMEM;
+
+	p_dec_info = &inst->codec_info->dec_info;
+	memcpy(&p_dec_info->open_param, pop, sizeof(struct dec_open_param));
+
+	ret = wave6_vpu_build_up_dec_param(inst, pop);
+	if (ret) {
+		kfree(inst->codec_info);
+		inst->codec_info = NULL;
+	}
+
+	return ret;
+}
+
+int wave6_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res)
+{
+	int ret;
+
+	if (WARN_ON(!inst->codec_info))
+		return -EINVAL;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	ret = wave6_vpu_dec_fini_seq(inst, fail_res);
+	if (ret) {
+		dev_warn(inst->dev->dev, "dec seq end timed out\n");
+
+		return ret;
+	}
+
+	dev_dbg(inst->dev->dev, "dec seq end complete\n");
+
+	kfree(inst->codec_info);
+
+	return 0;
+}
+
+int wave6_vpu_dec_complete_seq_init(struct vpu_instance *inst,
+				    struct dec_seq_info *info)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	ret = wave6_vpu_dec_get_seq_info(inst, info);
+	if (!ret)
+		p_dec_info->seq_info_obtained = true;
+
+	info->rd_ptr = wave6_vpu_dec_get_rd_ptr(inst);
+	info->wr_ptr = p_dec_info->stream_wr_ptr;
+
+	p_dec_info->seq_info = *info;
+
+	return ret;
+}
+
+int wave6_vpu_dec_get_aux_buffer_size(struct vpu_instance *inst,
+				      struct aux_buffer_size_info info, u32 *size)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	int width = info.width;
+	int height = info.height;
+	int buf_size, twice;
+
+	if (info.type == AUX_BUF_FBC_Y_TBL) {
+		switch (inst->std) {
+		case W_HEVC_DEC:
+		case W_AVC_DEC:
+			buf_size = WAVE6_FBC_LUMA_TABLE_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+		buf_size = ALIGN(buf_size, 16);
+	} else if (info.type == AUX_BUF_FBC_C_TBL) {
+		if (p_dec_info->seq_info.c_fmt_idc == C_FMT_IDC_YUV422)
+			twice = 2;
+		else if (p_dec_info->seq_info.c_fmt_idc == C_FMT_IDC_YUV444)
+			twice = 4;
+		else
+			twice = 1;
+
+		switch (inst->std) {
+		case W_HEVC_DEC:
+		case W_AVC_DEC:
+			buf_size = WAVE6_FBC_CHROMA_TABLE_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+		buf_size = buf_size * twice;
+		buf_size = ALIGN(buf_size, 16);
+	} else if (info.type == AUX_BUF_MV_COL) {
+		switch (inst->std) {
+		case W_HEVC_DEC:
+			buf_size = WAVE6_DEC_HEVC_MVCOL_BUF_SIZE(width, height);
+			break;
+		case W_AVC_DEC:
+			buf_size = WAVE6_DEC_AVC_MVCOL_BUF_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+		buf_size = ALIGN(buf_size, 16);
+	} else {
+		return -EINVAL;
+	}
+
+	*size = buf_size;
+
+	return 0;
+}
+
+void wave6_vpu_dec_get_bitstream_buffer(struct vpu_instance *inst,
+					dma_addr_t *p_rd_ptr,
+					dma_addr_t *p_wr_ptr)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	dma_addr_t rd_ptr;
+	dma_addr_t wr_ptr;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	rd_ptr = wave6_vpu_dec_get_rd_ptr(inst);
+	wr_ptr = p_dec_info->stream_wr_ptr;
+
+	if (p_rd_ptr)
+		*p_rd_ptr = rd_ptr;
+	if (p_wr_ptr)
+		*p_wr_ptr = wr_ptr;
+}
+
+int wave6_vpu_dec_update_bitstream_buffer(struct vpu_instance *inst, int size)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	dma_addr_t wr_ptr;
+	dma_addr_t rd_ptr;
+
+	wr_ptr = p_dec_info->stream_wr_ptr;
+	rd_ptr = p_dec_info->stream_rd_ptr;
+
+	if (size > 0) {
+		if (wr_ptr < rd_ptr && rd_ptr <= wr_ptr + size)
+			return -EINVAL;
+
+		wr_ptr += size;
+
+		p_dec_info->stream_wr_ptr = wr_ptr;
+		p_dec_info->stream_rd_ptr = rd_ptr;
+	}
+
+	p_dec_info->stream_end = size == 0;
+
+	return 0;
+}
+
+void wave6_vpu_dec_set_rd_ptr(struct vpu_instance *inst, dma_addr_t addr,
+			      bool update_wr_ptr)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+
+	p_dec_info->stream_rd_ptr = addr;
+	if (update_wr_ptr)
+		p_dec_info->stream_wr_ptr = addr;
+}
+
+void wave6_vpu_dec_reset_frame_buffer_info(struct vpu_instance *inst)
+{
+	struct dec_info *p_dec_info = &inst->codec_info->dec_info;
+	int i;
+
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		wave6_vdi_free_dma(&inst->frame_vbuf[i]);
+		memset(&inst->frame_buf[i], 0, sizeof(struct frame_buffer));
+		memset(&p_dec_info->disp_buf[i], 0, sizeof(struct frame_buffer));
+
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_MV_COL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i]);
+	}
+}
+
+int wave6_vpu_enc_open(struct vpu_instance *inst, struct enc_open_param *pop)
+{
+	struct enc_info *p_enc_info;
+	int ret;
+	struct vpu_core_device *core = inst->dev;
+
+	guard(mutex)(&core->hw_lock);
+
+	if (!wave6_vpu_is_init(core))
+		return -ENODEV;
+
+	inst->codec_info = kzalloc_obj(*inst->codec_info);
+	if (!inst->codec_info)
+		return -ENOMEM;
+
+	p_enc_info = &inst->codec_info->enc_info;
+	p_enc_info->open_param = *pop;
+
+	ret = wave6_vpu_build_up_enc_param(core->dev, inst, pop);
+	if (ret) {
+		kfree(inst->codec_info);
+		inst->codec_info = NULL;
+	}
+
+	return ret;
+}
+
+int wave6_vpu_enc_close(struct vpu_instance *inst, u32 *fail_res)
+{
+	int ret;
+
+	if (WARN_ON(!inst->codec_info))
+		return -EINVAL;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	ret = wave6_vpu_enc_fini_seq(inst, fail_res);
+	if (ret) {
+		dev_warn(inst->dev->dev, "enc seq end timed out\n");
+
+		return ret;
+	}
+
+	dev_dbg(inst->dev->dev, "enc seq end complete\n");
+	kfree(inst->codec_info);
+
+	return 0;
+}
+
+int wave6_vpu_enc_get_aux_buffer_size(struct vpu_instance *inst,
+				      struct aux_buffer_size_info info, u32 *size)
+{
+	struct enc_info *p_enc_info = &inst->codec_info->enc_info;
+	int width = info.width;
+	int height = info.height;
+	int buf_size, twice;
+
+	if (info.type == AUX_BUF_FBC_Y_TBL) {
+		switch (inst->std) {
+		case W_HEVC_ENC:
+		case W_AVC_ENC:
+			buf_size = WAVE6_FBC_LUMA_TABLE_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else if (info.type == AUX_BUF_FBC_C_TBL) {
+		if (p_enc_info->c_fmt_idc == C_FMT_IDC_YUV422)
+			twice = 2;
+		else if (p_enc_info->c_fmt_idc == C_FMT_IDC_YUV444)
+			twice = 4;
+		else
+			twice = 1;
+
+		switch (inst->std) {
+		case W_HEVC_ENC:
+		case W_AVC_ENC:
+			buf_size = WAVE6_FBC_CHROMA_TABLE_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+		buf_size = buf_size * twice;
+	} else if (info.type == AUX_BUF_MV_COL) {
+		switch (inst->std) {
+		case W_HEVC_ENC:
+			buf_size = WAVE6_ENC_HEVC_MVCOL_BUF_SIZE(width, height);
+			break;
+		case W_AVC_ENC:
+			buf_size = WAVE6_ENC_AVC_MVCOL_BUF_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else if (info.type == AUX_BUF_SUB_SAMPLE) {
+		switch (inst->std) {
+		case W_HEVC_ENC:
+		case W_AVC_ENC:
+			buf_size = WAVE6_ENC_SUBSAMPLED_SIZE(width, height);
+			break;
+		default:
+			return -EINVAL;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	*size = buf_size;
+
+	return 0;
+}
+
+int wave6_vpu_enc_complete_seq_init(struct vpu_instance *inst,
+				    struct enc_seq_info *info)
+{
+	struct enc_info *p_enc_info = &inst->codec_info->enc_info;
+	int ret;
+
+	guard(mutex)(&inst->dev->hw_lock);
+
+	ret = wave6_vpu_enc_get_seq_info(inst, info);
+	if (ret) {
+		p_enc_info->seq_info_obtained = false;
+		return ret;
+	}
+
+	if (!p_enc_info->seq_info_obtained) {
+		p_enc_info->seq_info_obtained = true;
+		p_enc_info->seq_info = *info;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpuapi.h b/drivers/media/platform/chips-media/wave6/wave6-vpuapi.h
new file mode 100644
index 000000000000..da51bd376a4e
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpuapi.h
@@ -0,0 +1,1005 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 helper interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPUAPI_H__
+#define __WAVE6_VPUAPI_H__
+
+#include <linux/kfifo.h>
+#include <linux/idr.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-ctrls.h>
+#include "wave6-vpuerror.h"
+#include "wave6-vpuconfig.h"
+#include "wave6-vdi.h"
+#include "wave6-vpu.h"
+
+struct vpu_attr;
+
+enum vpu_instance_type {
+	VPU_INST_TYPE_DEC,
+	VPU_INST_TYPE_ENC
+};
+
+/**
+ * enum vpu_instance_state - VPU instance states
+ * @VPU_INST_STATE_NONE:	Instance is not created or has been destroyed
+ * @VPU_INST_STATE_OPEN:	Instance is created
+ * @VPU_INST_STATE_INIT_SEQ:	For decoder, sequence header is parsed
+ *				For encoder, parameter configuration is complete
+ * @VPU_INST_STATE_PIC_RUN:	Instance is decoding or encoding
+ * @VPU_INST_STATE_SEEK:	Ready to seek a decoding resume point
+ *				Valid only for decoder
+ * @VPU_INST_STATE_STOP:	Decoding or encoding process is stopped
+ */
+enum vpu_instance_state {
+	VPU_INST_STATE_NONE,
+	VPU_INST_STATE_OPEN,
+	VPU_INST_STATE_INIT_SEQ,
+	VPU_INST_STATE_PIC_RUN,
+	VPU_INST_STATE_SEEK,
+	VPU_INST_STATE_STOP
+};
+
+#define WAVE6_MAX_FBS 31
+
+#define WAVE6_DEC_HEVC_MVCOL_BUF_SIZE(_w, _h) \
+	((ALIGN((_w), 256) / 16) * (ALIGN((_h), 64) / 16) * 1 * 16)
+#define WAVE6_DEC_AVC_MVCOL_BUF_SIZE(_w, _h) \
+	((ALIGN((_w), 64) / 16) * (ALIGN((_h), 16) / 16) * 5 * 16)
+#define WAVE6_FBC_LUMA_TABLE_SIZE(_w, _h) \
+	(ALIGN((_w), 256) * ALIGN((_h), 64) / 32)
+#define WAVE6_FBC_CHROMA_TABLE_SIZE(_w, _h) \
+	(ALIGN(((_w) / 2), 256) * ALIGN((_h), 64) / 32)
+#define WAVE6_ENC_AVC_MVCOL_BUF_SIZE(_w, _h) \
+	((ALIGN((_w), 512) / 512) * (ALIGN((_h), 16) / 16) * 16)
+#define WAVE6_ENC_HEVC_MVCOL_BUF_SIZE(_w, _h) \
+	((ALIGN((_w), 64) / 64) * (ALIGN((_h), 64) / 64) * 128)
+#define WAVE6_ENC_SUBSAMPLED_SIZE(_w, _h) \
+	(ALIGN(((_w) / 4), 16) * ALIGN(((_h) / 4), 32))
+
+enum codec_std {
+	W_HEVC_DEC	= 0x00,
+	W_HEVC_ENC	= 0x01,
+	W_AVC_DEC	= 0x02,
+	W_AVC_ENC	= 0x03,
+	STD_UNKNOWN	= 0xFF
+};
+
+#define HEVC_PROFILE_MAIN 1
+#define HEVC_PROFILE_MAIN10 2
+#define HEVC_PROFILE_STILLPICTURE 3
+#define HEVC_PROFILE_MAIN10_STILLPICTURE 2
+
+#define H264_PROFILE_BP 1
+#define H264_PROFILE_MP 2
+#define H264_PROFILE_EXTENDED 3
+#define H264_PROFILE_HP 4
+#define H264_PROFILE_HIGH10 5
+
+#define H264_VUI_SAR_IDC_EXTENDED 255
+
+#define DEC_REFRESH_TYPE_NON_IRAP 0
+#define DEC_REFRESH_TYPE_IDR 2
+
+#define DEFAULT_TEMP_LAYER_CNT 1
+#define DEFAULT_RC_INITIAL_LEVEL 8
+#define DEFAULT_RC_INITIAL_QP -1
+#define DEFAULT_PIC_RC_MAX_DQP 3
+#define DEFAULT_QROUND_INTER 85
+#define DEFAULT_QROUND_INTRA 171
+#define DEFAULT_INTRA_4X4 3
+#define DEFAULT_NUM_TICKS_POC_DIFF 100
+#define DEFAULT_RC_UPDATE_SPEED_CBR 64
+#define DEFAULT_RC_UPDATE_SPEED_VBR 16
+#define MAX_INTRA_INTER_PIC_BIT 1500000000
+#define DEFAULT_FRAME_RATE 30
+#define MIN_FRAME_RATE 1
+#define MAX_FRAME_RATE 960
+#define DEFAULT_CPB_SIZE 10000
+#define MIN_CPB_SIZE 10
+#define MAX_CPB_SIZE 100000
+#define AVC_MIN_SLICE_MAX_MB 4
+
+#define SEQ_CHANGE_ENABLE_PROFILE BIT(5)
+#define SEQ_CHANGE_ENABLE_SIZE BIT(16)
+#define SEQ_CHANGE_ENABLE_BIT_DEPTH BIT(18)
+#define SEQ_CHANGE_ENABLE_DPB_COUNT BIT(19)
+
+#define SEQ_CHANGE_ENABLE_ALL_HEVC (SEQ_CHANGE_ENABLE_PROFILE | \
+		SEQ_CHANGE_ENABLE_SIZE | \
+		SEQ_CHANGE_ENABLE_BIT_DEPTH | \
+		SEQ_CHANGE_ENABLE_DPB_COUNT)
+
+#define SEQ_CHANGE_ENABLE_ALL_AVC (SEQ_CHANGE_ENABLE_SIZE | \
+		SEQ_CHANGE_ENABLE_BIT_DEPTH | \
+		SEQ_CHANGE_ENABLE_DPB_COUNT)
+
+#define DEC_NOTI_FLAG_NO_FB		BIT(1)
+#define DEC_NOTI_FLAG_SEQ_CHANGE	BIT(0)
+
+#define RECON_IDX_FLAG_ENC_END -1
+#define RECON_IDX_FLAG_ENC_DELAY -2
+#define RECON_IDX_FLAG_HEADER_ONLY -3
+#define RECON_IDX_FLAG_CHANGE_PARAM -4
+
+#define FW_VERSION_MAJOR_MASK	0xFF000000
+#define FW_VERSION_MINOR_MASK	0x00FF0000
+#define FW_VERSION_REL_MASK	0x0000FFFF
+#define FW_VERSION_MAJOR(x)	FIELD_GET(FW_VERSION_MAJOR_MASK, (x))
+#define FW_VERSION_MINOR(x)	FIELD_GET(FW_VERSION_MINOR_MASK, (x))
+#define FW_VERSION_REL(x)	FIELD_GET(FW_VERSION_REL_MASK, (x))
+
+enum mirror_direction {
+	MIR_NONE,
+	MIR_VER,
+	MIR_HOR,
+	MIR_HOR_VER
+};
+
+enum rotation_angle {
+	ROT_0 = 0,
+	ROT_90 = 90,
+	ROT_180 = 180,
+	ROT_270 = 270
+};
+
+enum chroma_format_idc {
+	C_FMT_IDC_YUV400,
+	C_FMT_IDC_YUV420,
+	C_FMT_IDC_YUV422,
+	C_FMT_IDC_YUV444,
+	C_FMT_IDC_RGB
+};
+
+enum csc_format_order {
+	CSC_FMT_ORDER_RGB	= 0,
+	CSC_FMT_ORDER_RBG	= 1,
+	CSC_FMT_ORDER_GRB	= 2,
+	CSC_FMT_ORDER_GBR	= 3,
+	CSC_FMT_ORDER_BGR	= 4,
+	CSC_FMT_ORDER_BRG	= 5,
+
+	CSC_FMT_ORDER_ARGB	= 0,
+	CSC_FMT_ORDER_ARBG	= 1,
+	CSC_FMT_ORDER_AGRB	= 2,
+	CSC_FMT_ORDER_AGBR	= 3,
+	CSC_FMT_ORDER_ABGR	= 4,
+	CSC_FMT_ORDER_ABRG	= 5,
+	CSC_FMT_ORDER_RGBA	= 8,
+	CSC_FMT_ORDER_RBGA	= 9,
+	CSC_FMT_ORDER_GRBA	= 10,
+	CSC_FMT_ORDER_GBRA	= 11,
+	CSC_FMT_ORDER_BGRA	= 12,
+	CSC_FMT_ORDER_BRGA	= 13,
+};
+
+enum frame_buffer_format {
+	FORMAT_ERR = -1,
+
+	FORMAT_420 = 0,
+	FORMAT_422,
+	FORMAT_224,
+	FORMAT_444,
+	FORMAT_400,
+
+	FORMAT_420_P10_16BIT_MSB = 5,
+	FORMAT_420_P10_16BIT_LSB,
+	FORMAT_420_P10_32BIT_MSB,
+	FORMAT_420_P10_32BIT_LSB,
+
+	FORMAT_422_P10_16BIT_MSB,
+	FORMAT_422_P10_16BIT_LSB,
+	FORMAT_422_P10_32BIT_MSB,
+	FORMAT_422_P10_32BIT_LSB,
+
+	FORMAT_444_P10_16BIT_MSB,
+	FORMAT_444_P10_16BIT_LSB,
+	FORMAT_444_P10_32BIT_MSB,
+	FORMAT_444_P10_32BIT_LSB,
+
+	FORMAT_400_P10_16BIT_MSB,
+	FORMAT_400_P10_16BIT_LSB,
+	FORMAT_400_P10_32BIT_MSB,
+	FORMAT_400_P10_32BIT_LSB,
+
+	FORMAT_YUYV,
+	FORMAT_YUYV_P10_16BIT_MSB,
+	FORMAT_YUYV_P10_16BIT_LSB,
+	FORMAT_YUYV_P10_32BIT_MSB,
+	FORMAT_YUYV_P10_32BIT_LSB,
+
+	FORMAT_YVYU,
+	FORMAT_YVYU_P10_16BIT_MSB,
+	FORMAT_YVYU_P10_16BIT_LSB,
+	FORMAT_YVYU_P10_32BIT_MSB,
+	FORMAT_YVYU_P10_32BIT_LSB,
+
+	FORMAT_UYVY,
+	FORMAT_UYVY_P10_16BIT_MSB,
+	FORMAT_UYVY_P10_16BIT_LSB,
+	FORMAT_UYVY_P10_32BIT_MSB,
+	FORMAT_UYVY_P10_32BIT_LSB,
+
+	FORMAT_VYUY,
+	FORMAT_VYUY_P10_16BIT_MSB,
+	FORMAT_VYUY_P10_16BIT_LSB,
+	FORMAT_VYUY_P10_32BIT_MSB,
+	FORMAT_VYUY_P10_32BIT_LSB,
+
+	FORMAT_RGB_32BIT_PACKED = 90,
+	FORMAT_YUV444_32BIT_PACKED,
+	FORMAT_RGB_P10_32BIT_PACKED,
+	FORMAT_YUV444_P10_32BIT_PACKED,
+
+	FORMAT_RGB_24BIT_PACKED = 95,
+	FORMAT_YUV444_24BIT_PACKED,
+	FORMAT_YUV444_24BIT,
+
+	FORMAT_MAX,
+};
+
+enum pic_type {
+	PIC_TYPE_I = 0,
+	PIC_TYPE_P = 1,
+	PIC_TYPE_B = 2,
+	PIC_TYPE_IDR = 5,
+	PIC_TYPE_MAX
+};
+
+enum enc_force_pic_type {
+	ENC_FORCE_PIC_TYPE_I = 0,
+	ENC_FORCE_PIC_TYPE_P = 1,
+	ENC_FORCE_PIC_TYPE_B = 2,
+	ENC_FORCE_PIC_TYPE_IDR = 3,
+	ENC_FORCE_PIC_TYPE_DISABLED = 4,
+};
+
+enum display_mode {
+	DISP_MODE_DISP_ORDER,
+	DISP_MODE_DEC_ORDER,
+};
+
+enum sw_reset_mode {
+	SW_RESET_SAFETY,
+	SW_RESET_FORCE,
+	SW_RESET_ON_BOOT
+};
+
+enum tiled_map_type {
+	LINEAR_FRAME_MAP = 0,
+	COMPRESSED_FRAME_MAP = 17,
+};
+
+enum aux_buffer_type {
+	AUX_BUF_FBC_Y_TBL,
+	AUX_BUF_FBC_C_TBL,
+	AUX_BUF_MV_COL,
+	AUX_BUF_SUB_SAMPLE,
+	AUX_BUF_TYPE_MAX,
+};
+
+enum intra_refresh_mode {
+	INTRA_REFRESH_NONE = 0,
+	INTRA_REFRESH_ROW = 1,
+	INTRA_REFRESH_COLUMN = 2,
+};
+
+struct vpu_attr {
+	u32 product_id;
+	char product_name[8];
+	u32 product_code;
+	u32 product_version;
+	u32 fw_version;
+	u32 fw_revision;
+	u32 hw_version;
+	u32 support_decoders;
+	u32 support_encoders;
+	bool support_avc10bit_dec;
+	bool support_hevc10bit_dec;
+	bool support_avc10bit_enc;
+	bool support_hevc10bit_enc;
+	bool support_dual_core;
+};
+
+struct frame_buffer {
+	dma_addr_t buf_y;
+	dma_addr_t buf_cb;
+	dma_addr_t buf_cr;
+	enum tiled_map_type map_type;
+	unsigned int stride;
+	unsigned int stride_c;
+	unsigned int height;
+	int index;
+	u32 luma_bit_depth: 4;
+	u32 chroma_bit_depth: 4;
+	enum chroma_format_idc c_fmt_idc;
+};
+
+struct vpu_rect {
+	u32 left;
+	u32 top;
+	u32 right;
+	u32 bottom;
+};
+
+struct sar_info {
+	u32 enable;
+	u32 idc;
+	u32 width;
+	u32 height;
+};
+
+struct instance_buffer {
+	dma_addr_t temp_base;
+	u32 temp_size;
+	dma_addr_t ar_base;
+};
+
+struct report_cycle {
+	u32 host_cmd_s;
+	u32 host_cmd_e;
+	u32 proc_s;
+	u32 proc_e;
+	u32 vpu_s;
+	u32 vpu_e;
+	u32 frame_cycle;
+	u32 proc_cycle;
+	u32 vpu_cycle;
+};
+
+struct color_param {
+	u32 color_range;
+	u32 matrix_coefficients;
+	u32 transfer_characteristics;
+	u32 color_primaries;
+	bool color_description_present;
+	bool video_signal_type_present;
+};
+
+struct sec_axi_info {
+	bool use_dec_ip;
+	bool use_dec_lf_row;
+	bool use_enc_rdo;
+	bool use_enc_lf;
+};
+
+struct aux_buffer_size_info {
+	int width;
+	int height;
+	enum aux_buffer_type type;
+};
+
+struct dec_scaler_info {
+	bool enable;
+	int width;
+	int height;
+};
+
+struct dec_open_param {
+	enum display_mode disp_mode;
+	u32 ext_addr_vcpu: 8;
+	bool is_secure_inst;
+	u32 inst_priority: 5;
+	struct instance_buffer inst_buffer;
+};
+
+struct dec_seq_info {
+	u32 pic_width;
+	u32 pic_height;
+	u32 f_rate_numerator;
+	u32 f_rate_denominator;
+	struct vpu_rect pic_crop_rect;
+	u32 min_frame_buffer_count;
+	u32 req_mv_buffer_count;
+	u32 frame_buf_delay;
+	u32 profile;
+	u32 level;
+	u32 tier;
+	bool is_ext_sar;
+	u32 aspect_rate_info;
+	u32 bitrate;
+	enum chroma_format_idc c_fmt_idc;
+	u32 luma_bit_depth;
+	u32 chroma_bit_depth;
+	u32 err_reason;
+	int warn_info;
+	dma_addr_t rd_ptr;
+	dma_addr_t wr_ptr;
+	unsigned int sequence_no;
+	struct color_param color;
+};
+
+struct dec_param {
+	u64 timestamp;
+};
+
+struct dec_output_info {
+	int nal_type;
+	int pic_type;
+	int disp_pic_width;
+	int disp_pic_height;
+	int dec_pic_width;
+	int dec_pic_height;
+	int decoded_poc;
+	int display_poc;
+	dma_addr_t rd_ptr;
+	dma_addr_t wr_ptr;
+	dma_addr_t byte_pos_frame_start;
+	dma_addr_t byte_pos_frame_end;
+	dma_addr_t frame_decoded_addr;
+	dma_addr_t frame_display_addr;
+	int error_reason;
+	int warn_info;
+	unsigned int sequence_no;
+	struct report_cycle cycle;
+	dma_addr_t release_disp_frame_addr[WAVE6_MAX_FBS];
+	dma_addr_t disp_frame_addr[WAVE6_MAX_FBS];
+	u64 timestamp;
+	u32 notification_flags;
+	u32 release_disp_frame_num: 5;
+	u32 disp_frame_num: 5;
+	u32 ctu_size: 2;
+	bool frame_display;
+	bool frame_decoded;
+	bool stream_end;
+	bool last_frame_in_au;
+	bool decoding_success;
+};
+
+struct dec_info {
+	struct dec_open_param open_param;
+	struct dec_seq_info seq_info;
+	dma_addr_t stream_wr_ptr;
+	dma_addr_t stream_rd_ptr;
+	bool stream_end;
+	struct frame_buffer disp_buf[WAVE6_MAX_FBS];
+	bool seq_info_obtained;
+	struct sec_axi_info sec_axi;
+	struct dec_output_info dec_out_info[WAVE6_MAX_FBS];
+	int seq_change_mask;
+	enum frame_buffer_format wtl_format;
+};
+
+#define MAX_CUSTOM_LAMBDA_NUM 52
+#define MAX_NUM_TEMP_LAYER 7
+#define MAX_CUSTOM_GOP_NUM 8
+#define MAX_NUM_CHANGEABLE_TEMP_LAYER 4
+
+struct custom_gop_pic_param {
+	int pic_type;
+	int poc_offset;
+	int pic_qp;
+	bool multi_ref_p;
+	int ref_poc_l0;
+	int ref_poc_l1;
+	int temporal_id;
+};
+
+struct custom_gop_param {
+	int size;
+	struct custom_gop_pic_param pic[MAX_CUSTOM_GOP_NUM];
+};
+
+struct temporal_layer_param {
+	bool change_qp;
+	u32 qp_i;
+	u32 qp_p;
+	u32 qp_b;
+};
+
+struct enc_scaler_info {
+	bool enable;
+	int width;
+	int height;
+	int coef_mode;
+};
+
+struct enc_codec_param {
+	u32 internal_bit_depth;
+	u32 decoding_refresh_type;
+	u32 idr_period;
+	u32 intra_period;
+	u32 gop_preset_idx;
+	u32 frame_rate;
+	u32 bitrate;
+	u32 cpb_size;
+	u32 hvs_qp_scale_div2;
+	u32 max_delta_qp;
+	int rc_initial_qp;
+	u32 rc_update_speed;
+	u32 max_bitrate;
+	u32 rc_mode;
+	u32 rc_initial_level;
+	u32 pic_rc_max_dqp;
+	u32 bg_th_diff;
+	u32 bg_th_mean_diff;
+	int bg_delta_qp;
+	u32 intra_refresh_mode;
+	u32 intra_refresh_arg;
+	int beta_offset_div2;
+	int tc_offset_div2;
+	u32 qp;
+	u32 min_qp_i;
+	u32 max_qp_i;
+	u32 min_qp_p;
+	u32 max_qp_p;
+	u32 min_qp_b;
+	u32 max_qp_b;
+	int cb_qp_offset;
+	int cr_qp_offset;
+	u32 qround_intra;
+	u32 qround_inter;
+	int lambda_dqp_intra;
+	int lambda_dqp_inter;
+	u32 slice_mode;
+	u32 slice_arg;
+	u32 level;
+	u32 tier;
+	u32 profile;
+	struct vpu_rect conf_win;
+	u32 forced_idr_header;
+	u16 lambda_ssd[MAX_CUSTOM_LAMBDA_NUM];
+	u16 lambda_sad[MAX_CUSTOM_LAMBDA_NUM];
+	struct custom_gop_param gop_param;
+	struct temporal_layer_param temp_layer[MAX_NUM_CHANGEABLE_TEMP_LAYER];
+	u32 temp_layer_cnt;
+	u32 mv_histo_th0;
+	u32 mv_histo_th1;
+	u32 mv_histo_th2;
+	u32 mv_histo_th3;
+	u32 num_units_in_tick;
+	u32 time_scale;
+	u32 num_ticks_poc_diff_one;
+	struct color_param color;
+	struct sar_info sar;
+	u32 max_intra_pic_bit;
+	u32 max_inter_pic_bit;
+	u32 intra_4x4;
+
+	u32 en_const_intra_pred: 1;
+	u32 en_longterm: 1;
+	u32 en_rate_control: 1;
+	u32 en_transform8x8: 1;
+	u32 en_hvs_qp: 1;
+	u32 en_bg_detect: 1;
+	u32 en_temporal_mvp: 1;
+	u32 en_cabac: 1;
+	u32 en_dbk: 1;
+	u32 en_sao: 1;
+	u32 en_lf_slice_boundary: 1;
+	u32 en_scaling_list: 1;
+	u32 en_qround_offset: 1;
+	u32 en_still_picture: 1;
+	u32 en_intra_smooth: 1;
+	u32 en_custom_lambda: 1;
+	u32 en_report_mv_histo: 1;
+	u32 en_cu_level_rate_control: 1;
+	u32 en_skip_frame: 1;
+};
+
+struct enc_open_param {
+	int pic_width;
+	int pic_height;
+	struct enc_codec_param codec_param;
+	enum endian_mode source_endian;
+	enum frame_buffer_format src_format;
+	enum frame_buffer_format output_format;
+	u32 ext_addr_vcpu: 8;
+	bool is_secure_inst;
+	u32 inst_priority: 5;
+	struct instance_buffer inst_buffer;
+	enum mirror_direction mir_dir;
+	enum rotation_angle rot_angle;
+};
+
+struct enc_seq_info {
+	u32 min_frame_buffer_count;
+	u32 min_src_frame_count;
+	u32 req_mv_buffer_count;
+	int max_latency_pictures;
+	int err_reason;
+	int warn_info;
+};
+
+struct enc_csc_param {
+	u32 fmt_order;
+	s32 coef_ry;
+	s32 coef_gy;
+	s32 coef_by;
+	s32 coef_rcb;
+	s32 coef_gcb;
+	s32 coef_bcb;
+	s32 coef_rcr;
+	s32 coef_gcr;
+	s32 coef_bcr;
+	u32 offset_y;
+	u32 offset_cb;
+	u32 offset_cr;
+};
+
+struct enc_param {
+	struct frame_buffer *source_frame;
+	bool skip_picture;
+	dma_addr_t pic_stream_buffer_addr;
+	int pic_stream_buffer_size;
+	bool force_pic_qp;
+	int force_pic_qp_i;
+	int force_pic_qp_p;
+	int force_pic_qp_b;
+	bool force_pic;
+	int force_pic_type;
+	int src_idx;
+	bool src_end;
+	u32 bitrate;
+	struct enc_csc_param csc;
+	u64 timestamp;
+};
+
+struct enc_report_fme_sum {
+	u32 lower_x0;
+	u32 higher_x0;
+	u32 lower_y0;
+	u32 higher_y0;
+	u32 lower_x1;
+	u32 higher_x1;
+	u32 lower_y1;
+	u32 higher_y1;
+};
+
+struct enc_report_mv_histo {
+	u32 cnt0;
+	u32 cnt1;
+	u32 cnt2;
+	u32 cnt3;
+	u32 cnt4;
+};
+
+struct enc_output_info {
+	dma_addr_t bitstream_buffer;
+	u32 bitstream_size;
+	int pic_type;
+	int num_of_slices;
+	int recon_frame_index;
+	dma_addr_t rd_ptr;
+	dma_addr_t wr_ptr;
+	int pic_skipped;
+	int num_of_intra;
+	int num_of_merge;
+	int num_of_skip_block;
+	int avg_ctu_qp;
+	int enc_pic_byte;
+	int enc_gop_pic_idx;
+	int enc_pic_poc;
+	int enc_src_idx;
+	int enc_vcl_nut;
+	int enc_pic_cnt;
+	int error_reason;
+	int warn_info;
+	u32 pic_distortion_low;
+	u32 pic_distortion_high;
+	bool non_ref_pic;
+	bool encoding_success;
+	struct enc_report_fme_sum fme_sum;
+	struct enc_report_mv_histo mv_histo;
+	struct report_cycle cycle;
+	u64 timestamp;
+	dma_addr_t src_y_addr;
+	dma_addr_t custom_map_addr;
+	dma_addr_t prefix_sei_nal_addr;
+	dma_addr_t suffix_sei_nal_addr;
+};
+
+enum gop_preset_idx {
+	PRESET_IDX_CUSTOM_GOP = 0,
+	PRESET_IDX_ALL_I = 1,
+	PRESET_IDX_IPP = 2,
+	PRESET_IDX_IBBB = 3,
+	PRESET_IDX_IBPBP = 4,
+	PRESET_IDX_IBBBP = 5,
+	PRESET_IDX_IPPPP = 6,
+	PRESET_IDX_IBBBB = 7,
+	PRESET_IDX_RA_IB = 8,
+	PRESET_IDX_IPP_SINGLE = 9,
+	PRESET_IDX_MAX,
+};
+
+struct enc_info {
+	struct enc_open_param open_param;
+	struct enc_seq_info seq_info;
+	enum mirror_direction mir_dir;
+	enum rotation_angle rot_angle;
+	bool seq_info_obtained;
+	struct sec_axi_info sec_axi;
+	u32 width;
+	u32 height;
+	struct enc_scaler_info scaler;
+	enum chroma_format_idc c_fmt_idc;
+};
+
+struct h264_enc_controls {
+	u32 profile;
+	u32 level;
+	u32 min_qp;
+	u32 max_qp;
+	u32 i_frame_qp;
+	u32 p_frame_qp;
+	u32 b_frame_qp;
+	u32 loop_filter_mode;
+	u32 loop_filter_beta;
+	u32 loop_filter_alpha;
+	u32 _8x8_transform;
+	u32 constrained_intra_prediction;
+	u32 chroma_qp_index_offset;
+	u32 entropy_mode;
+	u32 i_period;
+	u32 vui_sar_enable;
+	u32 vui_sar_idc;
+	u32 vui_ext_sar_width;
+	u32 vui_ext_sar_height;
+	u32 cpb_size;
+};
+
+struct hevc_enc_controls {
+	u32 profile;
+	u32 level;
+	u32 min_qp;
+	u32 max_qp;
+	u32 i_frame_qp;
+	u32 p_frame_qp;
+	u32 b_frame_qp;
+	u32 loop_filter_mode;
+	u32 lf_beta_offset_div2;
+	u32 lf_tc_offset_div2;
+	u32 refresh_type;
+	u32 refresh_period;
+	u32 const_intra_pred;
+	u32 strong_smoothing;
+	u32 tmv_prediction;
+};
+
+struct enc_controls {
+	u32 frame_rate;
+	u32 rotation_angle;
+	u32 mirror_direction;
+	u32 bitrate;
+	u32 bitrate_mode;
+	u32 gop_size;
+	u32 frame_rc_enable;
+	u32 mb_rc_enable;
+	u32 slice_mode;
+	u32 slice_max_mb;
+	u32 prepend_spspps_to_idr;
+	u32 intra_refresh_period;
+	struct h264_enc_controls h264;
+	struct hevc_enc_controls hevc;
+	u32 force_key_frame;
+	u32 frame_skip_mode;
+};
+
+/**
+ * struct vpu_irq - VPU interrupt information
+ * @status:	Value of the VPU interrupt status
+ * @inst_idc:	Bitmask of instance IDs for this interrupt
+ */
+struct vpu_irq {
+	u32 status;
+	u32 inst_idc;
+};
+
+/**
+ * struct vpu_core_device - VPU core driver structure
+ * @dev:		Platform device pointer
+ * @vpu:		Parent VPU driver structure
+ * @v4l2_dev:		V4L2 device
+ * @m2m_dev:		V4L2 mem2mem device
+ * @instances:		List of VPU instances
+ * @inst_lock:		Spinlock protecting instance list
+ * @video_dev_dec:	Video device node for decoder
+ * @video_dev_enc:	Video device node for encoder
+ * @dev_lock:		Mutex protecting video_device, src, dst vb2_queue
+ * @hw_lock:		Mutex protecting register access
+ * @attr:		Hardware attributes retrieved after boot
+ * @reg_base:		Base address of MMIO registers
+ * @temp_vbuf:		TEMP buffer
+ * @clks:		Array of clock handles
+ * @num_clks:		Number of entries in @clks
+ * @irq_fifo:		kfifo storing interrupt information
+ * @task_timer:		Workqueue to detect hangs during frame processing
+ * @res:		Device compatible data
+ * @debugfs:		Debugfs entry
+ * @active:		Decoder/encoder active flag
+ */
+struct vpu_core_device {
+	struct device *dev;
+	struct wave6_vpu_device *vpu;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_m2m_dev *m2m_dev;
+	struct list_head instances;
+	spinlock_t inst_lock; /* Protects instance list */
+	struct video_device *video_dev_dec;
+	struct video_device *video_dev_enc;
+	struct mutex dev_lock; /* Protects video_device, src, dst vb2_queue */
+	struct mutex hw_lock; /* Protects register access */
+	struct vpu_attr	attr;
+	void __iomem *reg_base;
+
+	/* Allocates per-core, used during decode/encode process. */
+	struct vpu_buf temp_vbuf;
+
+	struct clk_bulk_data *clks;
+	int num_clks;
+
+	/* Stores interrupt information quickly, consumed in irq_thread. */
+	struct kfifo irq_fifo;
+
+	/* Detects hangs during decode/encode and aborts if needed. */
+	struct delayed_work task_timer;
+
+	const struct wave6_vpu_core_resource *res;
+	struct dentry *debugfs;
+	bool active;
+};
+
+struct vpu_instance;
+
+struct vpu_instance_ops {
+	void (*prepare_process)(struct vpu_instance *inst);
+	int (*start_process)(struct vpu_instance *inst);
+	void (*finish_process)(struct vpu_instance *inst, bool error);
+};
+
+struct vpu_performance_info {
+	ktime_t ts_start;
+	ktime_t ts_first;
+	ktime_t ts_last;
+	s64 latency_first;
+	s64 latency_max;
+	s64 min_process_time;
+	s64 max_process_time;
+	u64 total_sw_time;
+	u64 total_hw_time;
+};
+
+/**
+ * struct vpu_instance - VPU instance structure
+ * @list:		List head for VPU core device instance list
+ * @v4l2_fh:		V4L2 file handler
+ * @v4l2_ctrl_hdl:	V4L2 control handler
+ * @dev:		VPU core driver structure
+ * @irq_done:		Completion for command finish
+ * @refcount:		Reference count for instance in irq_thread
+ * @enable:		Instance enable flag
+ * @src_fmt:		V4L2 pixel format for source
+ * @dst_fmt:		V4L2 pixel format for destination
+ * @crop:		Crop rectangle
+ * @codec_rect:		Actual encoding rectangle
+ * @scaler_info:	Decoder scaler information
+ * @colorspace:		V4L2 colorspace
+ * @xfer_func:		V4L2 transfer function
+ * @ycbcr_enc:		V4L2 YCbCr encoding
+ * @quantization:	V4L2 quantization
+ * @state:		Instance state
+ * @state_in_seek:	Previous state before seek
+ * @type:		Instance type (decoder or encoder)
+ * @ops:		Instance operations
+ * @std:		Codec standard
+ * @id:			Instance id
+ * @enc_info:		Encoder-specific information
+ * @dec_info:		Decoder-specific information
+ * @frame_buf:		Metadata for reference frame buffers
+ * @frame_vbuf:		Reference frame buffers
+ * @aux_vbuf:		Auxiliary buffers
+ * @ar_vbuf:		Auxiliary AR buffer for encoder
+ * @fbc_buf_registered:	Reference frame buffers registration flag
+ * @queued_src_buf_num:	number of queued source buffers
+ * @queued_dst_buf_num:	number of queued destination buffers
+ * @processed_buf_num:	number of processed buffers
+ * @error_buf_num:	number of error buffers
+ * @sequence:		V4L2 buffers sequence number
+ * @next_buf_last:	Next queued buffer last flag
+ * @cbcr_interleave:	CbCr interleave format flag
+ * @nv21:		NV21 format flag
+ * @eos:		End of stream flag
+ * @error_recovery:	Error recovery flag
+ * @disp_mode:		Display mode for decoder V4L2 control
+ * @enc_ctrls:		Encoder V4L2 control and streaming parameters
+ * @performance:	Performance information
+ * @debugfs:		Debugfs entry
+ */
+struct vpu_instance {
+	struct list_head list;
+	struct v4l2_fh v4l2_fh;
+	struct v4l2_ctrl_handler v4l2_ctrl_hdl;
+	struct vpu_core_device *dev;
+
+	/* Signals when INIT_SEQ, SET_PARAM command is done. */
+	struct completion irq_done;
+
+	atomic_t refcount;
+	bool enable;
+
+	struct v4l2_pix_format_mplane src_fmt;
+	struct v4l2_pix_format_mplane dst_fmt;
+	struct v4l2_rect crop;
+	struct v4l2_rect codec_rect;
+	struct dec_scaler_info scaler_info;
+	enum v4l2_colorspace colorspace;
+	enum v4l2_xfer_func xfer_func;
+	enum v4l2_ycbcr_encoding ycbcr_enc;
+	enum v4l2_quantization quantization;
+
+	enum vpu_instance_state state;
+	enum vpu_instance_state state_in_seek;
+	enum vpu_instance_type type;
+	const struct vpu_instance_ops *ops;
+
+	enum codec_std std;
+	u32 id;
+	union {
+		struct enc_info enc_info;
+		struct dec_info dec_info;
+	} *codec_info;
+	struct frame_buffer frame_buf[WAVE6_MAX_FBS];
+	struct vpu_buf frame_vbuf[WAVE6_MAX_FBS];
+	struct vpu_buf aux_vbuf[AUX_BUF_TYPE_MAX][WAVE6_MAX_FBS];
+	struct vpu_buf ar_vbuf;
+	bool fbc_buf_registered;
+	u32 queued_src_buf_num;
+	u32 queued_dst_buf_num;
+	u32 processed_buf_num;
+	u32 error_buf_num;
+	u32 sequence;
+	bool next_buf_last;
+	bool cbcr_interleave;
+	bool nv21;
+	bool eos;
+	bool error_recovery;
+
+	enum display_mode disp_mode;
+	struct enc_controls enc_ctrls;
+
+	struct vpu_performance_info performance;
+	struct dentry *debugfs;
+};
+
+void wave6_vpu_writel(struct vpu_core_device *core, u32 addr, u32 data);
+u32 wave6_vpu_readl(struct vpu_core_device *core, u32 addr);
+
+int wave6_vpu_dec_open(struct vpu_instance *inst, struct dec_open_param *pop);
+int wave6_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res);
+int wave6_vpu_dec_issue_seq_init(struct vpu_instance *inst);
+int wave6_vpu_dec_complete_seq_init(struct vpu_instance *inst, struct dec_seq_info *info);
+int wave6_vpu_dec_get_aux_buffer_size(struct vpu_instance *inst,
+				      struct aux_buffer_size_info info, u32 *size);
+int wave6_vpu_dec_register_frame_buffer(struct vpu_instance *inst);
+int wave6_vpu_dec_register_display_buffer(struct vpu_instance *inst, struct frame_buffer fb);
+int wave6_vpu_dec_start_one_frame(struct vpu_instance *inst, struct dec_param *param,
+				  u32 *fail_res);
+int wave6_vpu_dec_get_output_info(struct vpu_instance *inst, struct dec_output_info *info);
+void wave6_vpu_dec_set_rd_ptr(struct vpu_instance *inst, dma_addr_t addr,
+			      bool update_wr_ptr);
+void wave6_vpu_dec_reset_frame_buffer_info(struct vpu_instance *inst);
+void wave6_vpu_dec_get_bitstream_buffer(struct vpu_instance *inst,
+					dma_addr_t *p_rd_ptr,
+					dma_addr_t *p_wr_ptr);
+int wave6_vpu_dec_update_bitstream_buffer(struct vpu_instance *inst, int size);
+int wave6_vpu_dec_flush_instance(struct vpu_instance *inst);
+
+int wave6_vpu_enc_open(struct vpu_instance *inst, struct enc_open_param *enc_op_param);
+int wave6_vpu_enc_close(struct vpu_instance *inst, u32 *fail_res);
+int wave6_vpu_enc_issue_seq_init(struct vpu_instance *inst);
+int wave6_vpu_enc_issue_seq_change(struct vpu_instance *inst, bool *changed);
+int wave6_vpu_enc_complete_seq_init(struct vpu_instance *inst, struct enc_seq_info *info);
+int wave6_vpu_enc_get_aux_buffer_size(struct vpu_instance *inst,
+				      struct aux_buffer_size_info info, u32 *size);
+int wave6_vpu_enc_register_frame_buffer(struct vpu_instance *inst);
+int wave6_vpu_enc_start_one_frame(struct vpu_instance *inst, struct enc_param *param,
+				  u32 *fail_res);
+int wave6_vpu_enc_get_output_info(struct vpu_instance *inst, struct enc_output_info *info);
+
+#endif /* __WAVE6_VPUAPI_H__ */
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpuconfig.h b/drivers/media/platform/chips-media/wave6/wave6-vpuconfig.h
new file mode 100644
index 000000000000..962c99112178
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpuconfig.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - product config definitions
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPUCONFIG_H__
+#define __WAVE6_VPUCONFIG_H__
+
+#define WAVE617_CODE	0x6170
+#define WAVE627_CODE	0x6270
+#define WAVE633_CODE	0x6330
+#define WAVE637_CODE	0x6370
+#define WAVE663_CODE	0x6630
+#define WAVE677_CODE	0x6670
+
+#define W6_VPU_MAX_NUM_CORE		4
+#define MAX_NUM_INSTANCE		32
+
+#define W6_MAX_PIC_STRIDE		(4096U * 4)
+#define W6_PIC_STRIDE_ALIGNMENT		32
+#define W6_FBC_BUF_ALIGNMENT		32
+#define W6_DEC_BUF_ALIGNMENT		32
+#define W6_DEF_DEC_PIC_WIDTH		720U
+#define W6_DEF_DEC_PIC_HEIGHT		480U
+#define W6_MIN_DEC_PIC_WIDTH		64U
+#define W6_MIN_DEC_PIC_HEIGHT		64U
+#define W6_MAX_DEC_PIC_WIDTH		4096U
+#define W6_MAX_DEC_PIC_HEIGHT		4096U
+#define W6_DEC_PIC_SIZE_STEP		1
+
+#define W6_DEF_ENC_PIC_WIDTH		416U
+#define W6_DEF_ENC_PIC_HEIGHT		240U
+#define W6_MIN_ENC_PIC_WIDTH		256U
+#define W6_MIN_ENC_PIC_HEIGHT		128U
+#define W6_MAX_ENC_PIC_WIDTH		4096U
+#define W6_MAX_ENC_PIC_HEIGHT		4096U
+#define W6_ENC_PIC_SIZE_STEP		8
+#define W6_ENC_CROP_X_POS_STEP		32
+#define W6_ENC_CROP_Y_POS_STEP		2
+#define W6_ENC_CROP_STEP		2
+
+#define W6_VPU_POLL_DELAY_US		10
+#define W6_VPU_POLL_TIMEOUT		300000
+#define W6_BOOT_WAIT_TIMEOUT		10000
+#define W6_VPU_TIMEOUT			6000
+#define W6_VPU_TIMEOUT_CYCLE_COUNT	(8000000 * 4 * 4)
+
+#define HOST_ENDIAN			VDI_128BIT_LITTLE_ENDIAN
+#define VPU_STREAM_ENDIAN		HOST_ENDIAN
+#define VPU_USER_DATA_ENDIAN		HOST_ENDIAN
+#define VPU_SOURCE_ENDIAN		HOST_ENDIAN
+
+#define W6_MAX_REMAP_PAGE_SIZE		(1024 * 1024)
+#define W6_ARBUF_SIZE			(1024)
+#define W6_MAX_CODE_BUF_SIZE		(4 * 1024 * 1024)
+#define W6_CODE_BUF_SIZE		(1 * 1024 * 1024)
+#define W6_EXTRA_CODE_BUF_SIZE		(256 * 1024)
+#define W6_TEMPBUF_SIZE			(3 * 1024 * 1024)
+#define W627ENC_WORKBUF_SIZE		(512 * 1024)
+#define W637DEC_WORKBUF_SIZE		(2 * 512 * 1024)
+#define W637DEC_WORKBUF_SIZE_FOR_CQ	(3 * 512 * 1024)
+
+static inline bool wave6_is_product_w_series(u32 code)
+{
+	return code == WAVE617_CODE || code == WAVE627_CODE ||
+	       code == WAVE633_CODE || code == WAVE637_CODE ||
+	       code == WAVE663_CODE || code == WAVE677_CODE;
+}
+
+#endif /* __WAVE6_VPUCONFIG_H__ */
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpuerror.h b/drivers/media/platform/chips-media/wave6/wave6-vpuerror.h
new file mode 100644
index 000000000000..5bcc7ecf62d3
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpuerror.h
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 vpu error values
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPUERROR_H__
+#define __WAVE6_VPUERROR_H__
+
+/* WAVE6 COMMON SYSTEM ERROR (FAIL_REASON) */
+#define WAVE6_SYSERR_QUEUEING_FAIL			0x00000001
+#define WAVE6_SYSERR_DECODER_FUSE			0x00000002
+#define WAVE6_SYSERR_INSTRUCTION_ACCESS_VIOLATION	0x00000004
+#define WAVE6_SYSERR_PRIVILEGE_VIOLATION		0x00000008
+#define WAVE6_SYSERR_DATA_ADDR_ALIGNMENT		0x00000010
+#define WAVE6_SYSERR_DATA_ACCESS_VIOLATION		0x00000020
+#define WAVE6_SYSERR_ACCESS_VIOLATION_HW		0x00000040
+#define WAVE6_SYSERR_INSTRUCTION_ADDR_ALIGNMENT		0x00000080
+#define WAVE6_SYSERR_UNKNOWN				0x00000100
+#define WAVE6_SYSERR_BUS_ERROR				0x00000200
+#define WAVE6_SYSERR_DOUBLE_FAULT			0x00000400
+#define WAVE6_SYSERR_RESULT_NOT_READY			0x00000800
+#define WAVE6_SYSERR_VPU_STILL_RUNNING			0x00001000
+#define WAVE6_SYSERR_UNKNOWN_CMD			0x00002000
+#define WAVE6_SYSERR_UNKNOWN_CODEC_STD			0x00004000
+#define WAVE6_SYSERR_UNKNOWN_QUERY_OPTION		0x00008000
+#define WAVE6_SYSERR_WATCHDOG_TIMEOUT			0x00020000
+#define WAVE6_SYSERR_NOT_SUPPORT			0x00100000
+#define WAVE6_SYSERR_TEMP_SEC_BUF_OVERFLOW		0x00200000
+#define WAVE6_SYSERR_NOT_SUPPORT_PROFILE		0x00400000
+#define WAVE6_SYSERR_TIMEOUT_CODEC_FW			0x40000000
+#define WAVE6_SYSERR_FATAL_VPU_HANGUP			0xf0000000
+
+/* WAVE6 COMMAND QUEUE ERROR (FAIL_REASON) */
+#define WAVE6_CMDQ_ERR_NOT_QUEABLE_CMD		0x00000001
+#define WAVE6_CMDQ_ERR_SKIP_MODE_ENABLE		0x00000002
+#define WAVE6_CMDQ_ERR_INST_FLUSHING		0x00000003
+#define WAVE6_CMDQ_ERR_INST_INACTIVE		0x00000004
+#define WAVE6_CMDQ_ERR_QUEUE_FAIL		0x00000005
+#define WAVE6_CMDQ_ERR_CMD_BUF_FULL		0x00000006
+
+/* WAVE6 ERROR ON DECODER (ERR_INFO) */
+#define HEVC_SPSERR_SEQ_PARAMETER_SET_ID				0x00001000
+#define HEVC_SPSERR_CHROMA_FORMAT_IDC					0x00001001
+#define HEVC_SPSERR_PIC_WIDTH_IN_LUMA_SAMPLES				0x00001002
+#define HEVC_SPSERR_PIC_HEIGHT_IN_LUMA_SAMPLES				0x00001003
+#define HEVC_SPSERR_CONF_WIN_LEFT_OFFSET				0x00001004
+#define HEVC_SPSERR_CONF_WIN_RIGHT_OFFSET				0x00001005
+#define HEVC_SPSERR_CONF_WIN_TOP_OFFSET					0x00001006
+#define HEVC_SPSERR_CONF_WIN_BOTTOM_OFFSET				0x00001007
+#define HEVC_SPSERR_BIT_DEPTH_LUMA_MINUS8				0x00001008
+#define HEVC_SPSERR_BIT_DEPTH_CHROMA_MINUS8				0x00001009
+#define HEVC_SPSERR_LOG2_MAX_PIC_ORDER_CNT_LSB_MINUS4			0x0000100A
+#define HEVC_SPSERR_SPS_MAX_DEC_PIC_BUFFERING				0x0000100B
+#define HEVC_SPSERR_SPS_MAX_NUM_REORDER_PICS				0x0000100C
+#define HEVC_SPSERR_SPS_MAX_LATENCY_INCREASE				0x0000100D
+#define HEVC_SPSERR_LOG2_MIN_LUMA_CODING_BLOCK_SIZE_MINUS3		0x0000100E
+#define HEVC_SPSERR_LOG2_DIFF_MAX_MIN_LUMA_CODING_BLOCK_SIZE		0x0000100F
+#define HEVC_SPSERR_LOG2_MIN_TRANSFORM_BLOCK_SIZE_MINUS2		0x00001010
+#define HEVC_SPSERR_LOG2_DIFF_MAX_MIN_TRANSFORM_BLOCK_SIZE		0x00001011
+#define HEVC_SPSERR_MAX_TRANSFORM_HIERARCHY_DEPTH_INTER			0x00001012
+#define HEVC_SPSERR_MAX_TRANSFORM_HIERARCHY_DEPTH_INTRA			0x00001013
+#define HEVC_SPSERR_SCALING_LIST					0x00001014
+#define HEVC_SPSERR_LOG2_DIFF_MIN_PCM_LUMA_CODING_BLOCK_SIZE_MINUS3	0x00001015
+#define HEVC_SPSERR_LOG2_DIFF_MAX_MIN_PCM_LUMA_CODING_BLOCK_SIZE	0x00001016
+#define HEVC_SPSERR_NUM_SHORTTERM_REF_PIC_SETS				0x00001017
+#define HEVC_SPSERR_NUM_LONGTERM_REF_PICS_SPS				0x00001018
+#define HEVC_SPSERR_GBU_PARSING_ERROR					0x00001019
+#define HEVC_SPSERR_EXTENSION_FLAG					0x0000101A
+#define HEVC_SPSERR_VUI_ERROR						0x0000101B
+#define HEVC_SPSERR_ACTIVATE_SPS					0x0000101C
+#define HEVC_SPSERR_PROFILE_SPACE					0x0000101D
+#define HEVC_PPSERR_PPS_PIC_PARAMETER_SET_ID				0x00002000
+#define HEVC_PPSERR_PPS_SEQ_PARAMETER_SET_ID				0x00002001
+#define HEVC_PPSERR_NUM_REF_IDX_L0_DEFAULT_ACTIVE_MINUS1		0x00002002
+#define HEVC_PPSERR_NUM_REF_IDX_L1_DEFAULT_ACTIVE_MINUS1		0x00002003
+#define HEVC_PPSERR_INIT_QP_MINUS26					0x00002004
+#define HEVC_PPSERR_DIFF_CU_QP_DELTA_DEPTH				0x00002005
+#define HEVC_PPSERR_PPS_CB_QP_OFFSET					0x00002006
+#define HEVC_PPSERR_PPS_CR_QP_OFFSET					0x00002007
+#define HEVC_PPSERR_NUM_TILE_COLUMNS_MINUS1				0x00002008
+#define HEVC_PPSERR_NUM_TILE_ROWS_MINUS1				0x00002009
+#define HEVC_PPSERR_COLUMN_WIDTH_MINUS1					0x0000200A
+#define HEVC_PPSERR_ROW_HEIGHT_MINUS1					0x0000200B
+#define HEVC_PPSERR_PPS_BETA_OFFSET_DIV2				0x0000200C
+#define HEVC_PPSERR_PPS_TC_OFFSET_DIV2					0x0000200D
+#define HEVC_PPSERR_SCALING_LIST					0x0000200E
+#define HEVC_PPSERR_LOG2_PARALLEL_MERGE_LEVEL_MINUS2			0x0000200F
+#define HEVC_PPSERR_NUM_TILE_COLUMNS_RANGE_OUT				0x00002010
+#define HEVC_PPSERR_NUM_TILE_ROWS_RANGE_OUT				0x00002011
+#define HEVC_PPSERR_MORE_RBSP_DATA_ERROR				0x00002012
+#define HEVC_PPSERR_PPS_PIC_PARAMETER_SET_ID_RANGE_OUT			0x00002013
+#define HEVC_PPSERR_PPS_SEQ_PARAMETER_SET_ID_RANGE_OUT			0x00002014
+#define HEVC_PPSERR_NUM_REF_IDX_L0_DEFAULT_ACTIVE_MINUS1_RANGE_OUT	0x00002015
+#define HEVC_PPSERR_NUM_REF_IDX_L1_DEFAULT_ACTIVE_MINUS1_RANGE_OUT	0x00002016
+#define HEVC_PPSERR_PPS_CB_QP_OFFSET_RANGE_OUT				0x00002017
+#define HEVC_PPSERR_PPS_CR_QP_OFFSET_RANGE_OUT				0x00002018
+#define HEVC_PPSERR_COLUMN_WIDTH_MINUS1_RANGE_OUT			0x00002019
+#define HEVC_PPSERR_ROW_HEIGHT_MINUS1_RANGE_OUT				0x00002020
+#define HEVC_PPSERR_PPS_BETA_OFFSET_DIV2_RANGE_OUT			0x00002021
+#define HEVC_PPSERR_PPS_TC_OFFSET_DIV2_RANGE_OUT			0x00002022
+#define HEVC_SHERR_SLICE_PIC_PARAMETER_SET_ID				0x00003000
+#define HEVC_SHERR_ACTIVATE_PPS						0x00003001
+#define HEVC_SHERR_ACTIVATE_SPS						0x00003002
+#define HEVC_SHERR_SLICE_TYPE						0x00003003
+#define HEVC_SHERR_FIRST_SLICE_IS_DEPENDENT_SLICE			0x00003004
+#define HEVC_SHERR_SHORTTERM_REF_PIC_SET_SPS_FLAG			0x00003005
+#define HEVC_SHERR_SHORTTERM_REF_PIC_SET				0x00003006
+#define HEVC_SHERR_SHORTTERM_REF_PIC_SET_IDX				0x00003007
+#define HEVC_SHERR_NUM_LONGTERM_SPS					0x00003008
+#define HEVC_SHERR_NUM_LONGTERM_PICS					0x00003009
+#define HEVC_SHERR_LT_IDX_SPS_IS_OUT_OF_RANGE				0x0000300A
+#define HEVC_SHERR_DELTA_POC_MSB_CYCLE_LT				0x0000300B
+#define HEVC_SHERR_NUM_REF_IDX_L0_ACTIVE_MINUS1				0x0000300C
+#define HEVC_SHERR_NUM_REF_IDX_L1_ACTIVE_MINUS1				0x0000300D
+#define HEVC_SHERR_COLLOCATED_REF_IDX					0x0000300E
+#define HEVC_SHERR_PRED_WEIGHT_TABLE					0x0000300F
+#define HEVC_SHERR_FIVE_MINUS_MAX_NUM_MERGE_CAND			0x00003010
+#define HEVC_SHERR_SLICE_QP_DELTA					0x00003011
+#define HEVC_SHERR_SLICE_QP_DELTA_IS_OUT_OF_RANGE			0x00003012
+#define HEVC_SHERR_SLICE_CB_QP_OFFSET					0x00003013
+#define HEVC_SHERR_SLICE_CR_QP_OFFSET					0x00003014
+#define HEVC_SHERR_SLICE_BETA_OFFSET_DIV2				0x00003015
+#define HEVC_SHERR_SLICE_TC_OFFSET_DIV2					0x00003016
+#define HEVC_SHERR_NUM_ENTRY_POINT_OFFSETS				0x00003017
+#define HEVC_SHERR_OFFSET_LEN_MINUS1					0x00003018
+#define HEVC_SHERR_SLICE_SEGMENT_HEADER_EXTENSION_LENGTH		0x00003019
+#define HEVC_SHERR_WRONG_POC_IN_STILL_PICTURE_PROFILE			0x0000301A
+#define HEVC_SHERR_SLICE_TYPE_ERROR_IN_STILL_PICTURE_PROFILE		0x0000301B
+#define HEVC_SHERR_PPS_ID_NOT_EQUAL_PREV_VALUE				0x0000301C
+#define HEVC_SPECERR_OVER_PICTURE_WIDTH_SIZE				0x00004000
+#define HEVC_SPECERR_OVER_PICTURE_HEIGHT_SIZE				0x00004001
+#define HEVC_SPECERR_OVER_CHROMA_FORMAT					0x00004002
+#define HEVC_SPECERR_OVER_BIT_DEPTH					0x00004003
+#define HEVC_SPECERR_OVER_BUFFER_OVER_FLOW				0x00004004
+#define HEVC_SPECERR_OVER_WRONG_BUFFER_ACCESS				0x00004005
+#define HEVC_ETCERR_INIT_SEQ_SPS_NOT_FOUND				0x00005000
+#define HEVC_ETCERR_DEC_PIC_VCL_NOT_FOUND				0x00005001
+#define HEVC_ETCERR_NO_VALID_SLICE_IN_AU				0x00005002
+#define HEVC_ETCERR_INPLACE_V						0x0000500F
+
+#define AVC_SPSERR_SEQ_PARAMETER_SET_ID					0x00001000
+#define AVC_SPSERR_CHROMA_FORMAT_IDC					0x00001001
+#define AVC_SPSERR_PIC_WIDTH_IN_LUMA_SAMPLES				0x00001002
+#define AVC_SPSERR_PIC_HEIGHT_IN_LUMA_SAMPLES				0x00001003
+#define AVC_SPSERR_CONF_WIN_LEFT_OFFSET					0x00001004
+#define AVC_SPSERR_CONF_WIN_RIGHT_OFFSET				0x00001005
+#define AVC_SPSERR_CONF_WIN_TOP_OFFSET					0x00001006
+#define AVC_SPSERR_CONF_WIN_BOTTOM_OFFSET				0x00001007
+#define AVC_SPSERR_BIT_DEPTH_LUMA_MINUS8				0x00001008
+#define AVC_SPSERR_BIT_DEPTH_CHROMA_MINUS8				0x00001009
+#define AVC_SPSERR_SPS_MAX_DEC_PIC_BUFFERING				0x0000100B
+#define AVC_SPSERR_SPS_MAX_NUM_REORDER_PICS				0x0000100C
+#define AVC_SPSERR_SCALING_LIST						0x00001014
+#define AVC_SPSERR_GBU_PARSING_ERROR					0x00001019
+#define AVC_SPSERR_VUI_ERROR						0x0000101B
+#define AVC_SPSERR_ACTIVATE_SPS						0x0000101C
+#define AVC_PPSERR_PPS_PIC_PARAMETER_SET_ID				0x00002000
+#define AVC_PPSERR_PPS_SEQ_PARAMETER_SET_ID				0x00002001
+#define AVC_PPSERR_NUM_REF_IDX_L0_DEFAULT_ACTIVE_MINUS1			0x00002002
+#define AVC_PPSERR_NUM_REF_IDX_L1_DEFAULT_ACTIVE_MINUS1			0x00002003
+#define AVC_PPSERR_INIT_QP_MINUS26					0x00002004
+#define AVC_PPSERR_PPS_CB_QP_OFFSET					0x00002006
+#define AVC_PPSERR_PPS_CR_QP_OFFSET					0x00002007
+#define AVC_PPSERR_SCALING_LIST						0x0000200E
+#define AVC_PPSERR_MORE_RBSP_DATA_ERROR					0x00002012
+#define AVC_PPSERR_PPS_PIC_PARAMETER_SET_ID_RANGE_OUT			0x00002013
+#define AVC_PPSERR_PPS_SEQ_PARAMETER_SET_ID_RANGE_OUT			0x00002014
+#define AVC_PPSERR_NUM_REF_IDX_L0_DEFAULT_ACTIVE_MINUS1_RANGE_OUT	0x00002015
+#define AVC_PPSERR_NUM_REF_IDX_L1_DEFAULT_ACTIVE_MINUS1_RANGE_OUT	0x00002016
+#define AVC_PPSERR_PPS_CB_QP_OFFSET_RANGE_OUT				0x00002017
+#define AVC_PPSERR_PPS_CR_QP_OFFSET_RANGE_OUT				0x00002018
+#define AVC_SHERR_SLICE_PIC_PARAMETER_SET_ID				0x00003000
+#define AVC_SHERR_ACTIVATE_PPS						0x00003001
+#define AVC_SHERR_ACTIVATE_SPS						0x00003002
+#define AVC_SHERR_SLICE_TYPE						0x00003003
+#define AVC_SHERR_FIRST_MB_IN_SLICE					0x00003004
+#define AVC_SHERR_RPLM							0x00003006
+#define AVC_SHERR_LT_IDX_SPS_IS_OUT_OF_RANGE				0x0000300A
+#define AVC_SHERR_NUM_REF_IDX_L0_ACTIVE_MINUS1				0x0000300C
+#define AVC_SHERR_NUM_REF_IDX_L1_ACTIVE_MINUS1				0x0000300D
+#define AVC_SHERR_PRED_WEIGHT_TABLE					0x0000300F
+#define AVC_SHERR_SLICE_QP_DELTA					0x00003011
+#define AVC_SHERR_SLICE_BETA_OFFSET_DIV2				0x00003015
+#define AVC_SHERR_SLICE_TC_OFFSET_DIV2					0x00003016
+#define AVC_SHERR_DISABLE_DEBLOCK_FILTER_IDC				0x00003017
+#define AVC_SPECERR_OVER_PICTURE_WIDTH_SIZE				0x00004000
+#define AVC_SPECERR_OVER_PICTURE_HEIGHT_SIZE				0x00004001
+#define AVC_SPECERR_OVER_CHROMA_FORMAT					0x00004002
+#define AVC_SPECERR_OVER_BIT_DEPTH					0x00004003
+#define AVC_SPECERR_OVER_BUFFER_OVER_FLOW				0x00004004
+#define AVC_SPECERR_OVER_WRONG_BUFFER_ACCESS				0x00004005
+#define AVC_ETCERR_INIT_SEQ_SPS_NOT_FOUND				0x00005000
+#define AVC_ETCERR_DEC_PIC_VCL_NOT_FOUND				0x00005001
+#define AVC_ETCERR_NO_VALID_SLICE_IN_AU					0x00005002
+#define AVC_ETCERR_ASO							0x00005004
+#define AVC_ETCERR_FMO							0x00005005
+#define AVC_ETCERR_INPLACE_V						0x0000500F
+
+/* WAVE6 WARNING ON DECODER (WARN_INFO) */
+#define HEVC_SPSWARN_MAX_SUB_LAYERS_MINUS1			0x00000001
+#define HEVC_SPSWARN_GENERAL_RESERVED_ZERO_44BITS		0x00000002
+#define HEVC_SPSWARN_RESERVED_ZERO_2BITS			0x00000004
+#define HEVC_SPSWARN_SUB_LAYER_RESERVED_ZERO_44BITS		0x00000008
+#define HEVC_SPSWARN_GENERAL_LEVEL_IDC				0x00000010
+#define HEVC_SPSWARN_SPS_MAX_DEC_PIC_BUFFERING_VALUE_OVER	0x00000020
+#define HEVC_SPSWARN_RBSP_TRAILING_BITS				0x00000040
+#define HEVC_SPSWARN_ST_RPS_UE_ERROR				0x00000080
+#define HEVC_SPSWARN_EXTENSION_FLAG				0x01000000
+#define HEVC_SPSWARN_REPLACED_WITH_PREV_SPS			0x02000000
+#define HEVC_PPSWARN_RBSP_TRAILING_BITS				0x00000100
+#define HEVC_PPSWARN_REPLACED_WITH_PREV_PPS			0x00000200
+#define HEVC_SHWARN_FIRST_SLICE_SEGMENT_IN_PIC_FLAG		0x00001000
+#define HEVC_SHWARN_NO_OUTPUT_OF_PRIOR_PICS_FLAG		0x00002000
+#define HEVC_SHWARN_PIC_OUTPUT_FLAG				0x00004000
+#define HEVC_SHWARN_DUPLICATED_SLICE_SEGMENT			0x00008000
+#define HEVC_ETCWARN_INIT_SEQ_VCL_NOT_FOUND			0x00010000
+#define HEVC_ETCWARN_MISSING_REFERENCE_PICTURE			0x00020000
+#define HEVC_ETCWARN_WRONG_TEMPORAL_ID				0x00040000
+#define HEVC_ETCWARN_ERROR_PICTURE_IS_REFERENCED		0x00080000
+#define HEVC_SPECWARN_OVER_PROFILE				0x00100000
+#define HEVC_SPECWARN_OVER_LEVEL				0x00200000
+#define HEVC_PRESWARN_PARSING_ERR				0x04000000
+#define HEVC_PRESWARN_MVD_OUT_OF_RANGE				0x08000000
+#define HEVC_PRESWARN_CU_QP_DELTA_VAL_OUT_OF_RANGE		0x09000000
+#define HEVC_PRESWARN_COEFF_LEVEL_REMAINING_OUT_OF_RANGE	0x0A000000
+#define HEVC_PRESWARN_PCM_ERR					0x0B000000
+#define HEVC_PRESWARN_OVERCONSUME				0x0C000000
+#define HEVC_PRESWARN_END_OF_SUBSET_ONE_BIT_ERR			0x10000000
+#define HEVC_PRESWARN_END_OF_SLICE_SEGMENT_FLAG			0x20000000
+
+#define AVC_SPSWARN_RESERVED_ZERO_2BITS				0x00000004
+#define AVC_SPSWARN_GENERAL_LEVEL_IDC				0x00000010
+#define AVC_SPSWARN_RBSP_TRAILING_BITS				0x00000040
+#define AVC_PPSWARN_RBSP_TRAILING_BITS				0x00000100
+#define AVC_SHWARN_NO_OUTPUT_OF_PRIOR_PICS_FLAG			0x00002000
+#define AVC_ETCWARN_INIT_SEQ_VCL_NOT_FOUND			0x00010000
+#define AVC_ETCWARN_MISSING_REFERENCE_PICTURE			0x00020000
+#define AVC_ETCWARN_ERROR_PICTURE_IS_REFERENCED			0x00080000
+#define AVC_SPECWARN_OVER_PROFILE				0x00100000
+#define AVC_SPECWARN_OVER_LEVEL					0x00200000
+#define AVC_PRESWARN_MVD_RANGE_OUT				0x00400000
+#define AVC_PRESWARN_MB_QPD_RANGE_OUT				0x00500000
+#define AVC_PRESWARN_COEFF_RANGE_OUT				0x00600000
+#define AVC_PRESWARN_MV_RANGE_OUT				0x00700000
+#define AVC_PRESWARN_MB_SKIP_RUN_RANGE_OUT			0x00800000
+#define AVC_PRESWARN_MB_TYPE_RANGE_OUT				0x00900000
+#define AVC_PRESWARN_SUB_MB_TYPE_RANGE_OUT			0x00A00000
+#define AVC_PRESWARN_CBP_RANGE_OUT				0x00B00000
+#define AVC_PRESWARN_INTRA_CHROMA_PRED_MODE_RANGE_OUT		0x00C00000
+#define AVC_PRESWARN_REF_IDX_RANGE_OUT				0x00D00000
+#define AVC_PRESWARN_COEFF_TOKEN_RANGE_OUT			0x00E00000
+#define AVC_PRESWARN_TOTAL_ZERO_RANGE_OUT			0x00F00000
+#define AVC_PRESWARN_RUN_BEFORE_RANGE_OUT			0x01000000
+#define AVC_PRESWARN_OVERCONSUME				0x01100000
+#define AVC_PRESWARN_MISSING_SLICE				0x01200000
+
+/* WAVE6 WARNING ON ENCODER (WARN_INFO) */
+#define WAVE6_ETCWARN_FORCED_SPLIT_BY_CU8X8	0x00000001
+
+#endif /* __WAVE6_VPUERROR_H__ */
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 4/9] media: chips-media: wave6: Add v4l2 m2m driver support
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
                   ` (2 preceding siblings ...)
  2026-06-24  7:20 ` [PATCH v6 3/9] media: chips-media: wave6: Add Wave6 VPU interface Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:40   ` sashiko-bot
  2026-06-24  7:20 ` [PATCH v6 5/9] media: chips-media: wave6: Add Wave6 core driver Nas Chung
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung,
	Ming Qian

Add v4l2 m2m stateful driver support for the
Chips&Media Wave6 video codec IP, including both encoder and decoder.

The following features and formats are supported:
Encoder (8bit only):
- Supported codecs: AVC, HEVC
- Supported input formats:
  * V4L2_PIX_FMT_YUV420/NV12/NV21
  * V4L2_PIX_FMT_YUV422P/NV16/NV61
  * V4L2_PIX_FMT_YUYV
  * V4L2_PIX_FMT_YUV24/NV24/NV42
  * V4L2_PIX_FMT_RGB24
  * V4L2_PIX_FMT_P010
  * V4L2_PIX_FMT_ARGB32
  * V4L2_PIX_FMT_RGBA32
  * V4L2_PIX_FMT_XRGB32
  * V4L2_PIX_FMT_ARGB2101010
- Supports multiplanar format
- Although 10-bit input formats are supported (e.g., P010, ARGB2101010),
  encoded output is limited to 8-bit streams
- Supports crop
- Supports extensive v4l2 encoder control

Decoder (8bit only):
- Supported codecs: AVC, HEVC (8bit only)
- Supported output formats: V4L2_PIX_FMT_YUV420/NV12/NV21
- Supports multiplanar format
- Supports scale down
- Supports dynamic resolution change

Note: While other versions of the Wave6 IP may support VP9 decoding and
AV1 decoding and encoding those formats are not implemented or validated
in this driver at this time.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
 .../chips-media/wave6/wave6-vpu-dec.c         | 1831 +++++++++++
 .../chips-media/wave6/wave6-vpu-enc.c         | 2758 +++++++++++++++++
 .../chips-media/wave6/wave6-vpu-v4l2.c        |  516 +++
 3 files changed, 5105 insertions(+)
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c

diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c
new file mode 100644
index 000000000000..9545264c19b7
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c
@@ -0,0 +1,1831 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - v4l2 stateful decoder interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+#include "wave6-vpu-core.h"
+#include "wave6-vpu-dbg.h"
+#include "wave6-trace.h"
+
+#define VPU_DEC_DEV_NAME "C&M Wave6 VPU decoder"
+#define VPU_DEC_DRV_NAME "wave6-dec"
+
+static const struct vpu_format wave6_vpu_dec_fmt_list[2][6] = {
+	[VPU_FMT_TYPE_CODEC] = {
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_HEVC,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 1,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_H264,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 1,
+		},
+	},
+	[VPU_FMT_TYPE_RAW] = {
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV420,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV12,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV21,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV420M,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 3,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV12M,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 2,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV21M,
+			.max_width = W6_MAX_DEC_PIC_WIDTH,
+			.min_width = W6_MIN_DEC_PIC_WIDTH,
+			.max_height = W6_MAX_DEC_PIC_HEIGHT,
+			.min_height = W6_MIN_DEC_PIC_HEIGHT,
+			.num_planes = 2,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER,
+		},
+	}
+};
+
+static const struct vpu_format *wave6_find_vpu_fmt(unsigned int v4l2_pix_fmt,
+						   enum vpu_fmt_type type)
+{
+	unsigned int index;
+
+	for (index = 0; index < ARRAY_SIZE(wave6_vpu_dec_fmt_list[type]); index++) {
+		if (wave6_vpu_dec_fmt_list[type][index].v4l2_pix_fmt == v4l2_pix_fmt)
+			return &wave6_vpu_dec_fmt_list[type][index];
+	}
+
+	return NULL;
+}
+
+static const struct vpu_format *wave6_find_vpu_fmt_by_idx(unsigned int idx,
+							  enum vpu_fmt_type type)
+{
+	if (idx >= ARRAY_SIZE(wave6_vpu_dec_fmt_list[type]))
+		return NULL;
+
+	if (!wave6_vpu_dec_fmt_list[type][idx].v4l2_pix_fmt)
+		return NULL;
+
+	return &wave6_vpu_dec_fmt_list[type][idx];
+}
+
+static void wave6_vpu_dec_release_fb(struct vpu_instance *inst)
+{
+	int i;
+
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		wave6_vdi_free_dma(&inst->frame_vbuf[i]);
+		memset(&inst->frame_buf[i], 0, sizeof(struct frame_buffer));
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_MV_COL][i]);
+	}
+}
+
+static void wave6_vpu_dec_destroy_instance(struct vpu_instance *inst, bool remove_dbgfs)
+{
+	u32 fail_res = 0;
+	int ret;
+
+	wave6_vpu_disable_instance(inst);
+
+	if (remove_dbgfs)
+		wave6_vpu_remove_dbgfs_file(inst);
+
+	ret = wave6_vpu_dec_close(inst, &fail_res);
+	if (ret) {
+		dev_err(inst->dev->dev, "failed destroy instance: %d (%d)\n",
+			ret, fail_res);
+	}
+
+	scoped_guard(spinlock, &inst->dev->inst_lock)
+		list_del_init(&inst->list);
+	wave6_vpu_dec_release_fb(inst);
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_NONE);
+
+	if (!pm_runtime_suspended(inst->dev->dev))
+		pm_runtime_put_sync(inst->dev->dev);
+}
+
+static void wave6_handle_bitstream_buffer(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *src_buf;
+	u32 src_size = 0;
+	int ret;
+
+	src_buf = v4l2_m2m_next_src_buf(inst->v4l2_fh.m2m_ctx);
+	if (src_buf) {
+		struct vpu_buffer *vpu_buf = wave6_to_vpu_buf(src_buf);
+		dma_addr_t rd_ptr = wave6_get_dma_addr(src_buf, 0);
+
+		if (vpu_buf->consumed) {
+			dev_dbg(inst->dev->dev, "%s: Already consumed buffer\n",
+				__func__);
+			return;
+		}
+
+		vpu_buf->ts_start = ktime_get_raw();
+		vpu_buf->consumed = true;
+		wave6_vpu_dec_set_rd_ptr(inst, rd_ptr, true);
+
+		src_size = vb2_get_plane_payload(&src_buf->vb2_buf, 0);
+	}
+
+	if (!src_size) {
+		dma_addr_t rd = 0, wr = 0;
+
+		wave6_vpu_dec_get_bitstream_buffer(inst, &rd, &wr);
+		wave6_vpu_dec_set_rd_ptr(inst, wr, true);
+	}
+
+	trace_wave6_vpu_dec_pic(inst, src_buf ? src_buf->vb2_buf.index : -1, src_size);
+
+	ret = wave6_vpu_dec_update_bitstream_buffer(inst, src_size);
+	if (ret) {
+		dev_dbg(inst->dev->dev, "%s: Update bitstream buffer fail %d\n",
+			__func__, ret);
+		return;
+	}
+}
+
+static void wave6_update_pix_fmt_cap(struct v4l2_pix_format_mplane *pix_mp,
+				     unsigned int width,
+				     unsigned int height,
+				     bool new_resolution)
+{
+	unsigned int aligned_width;
+
+	if (new_resolution)
+		pix_mp->plane_fmt[0].bytesperline = 0;
+
+	aligned_width = round_up(width, W6_DEC_BUF_ALIGNMENT);
+	wave6_update_pix_fmt(pix_mp, aligned_width, height);
+}
+
+static int wave6_allocate_aux_buffer(struct vpu_instance *inst,
+				     enum aux_buffer_type type,
+				     int num)
+{
+	struct aux_buffer_size_info size_info;
+	unsigned int size;
+	int i, ret;
+
+	size_info.width = inst->src_fmt.width;
+	size_info.height = inst->src_fmt.height;
+	size_info.type = type;
+
+	ret = wave6_vpu_dec_get_aux_buffer_size(inst, size_info, &size);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: Get size fail (type %d)\n", __func__, type);
+		return ret;
+	}
+
+	num = min_t(u32, num, WAVE6_MAX_FBS);
+	for (i = 0; i < num; i++) {
+		inst->aux_vbuf[type][i].size = size;
+		ret = wave6_vdi_alloc_dma(inst->dev->dev, &inst->aux_vbuf[type][i]);
+		if (ret) {
+			dev_err(inst->dev->dev, "%s: Alloc fail (type %d)\n", __func__, type);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void wave6_vpu_dec_handle_dst_buffer(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *dst_buf;
+	struct v4l2_m2m_buffer *v4l2_m2m_buf;
+	struct vpu_buffer *vpu_buf;
+	u32 fb_stride = inst->dst_fmt.plane_fmt[0].bytesperline;
+	u32 luma_size = fb_stride * inst->dst_fmt.height;
+	u32 chroma_size = (fb_stride / 2) * (inst->dst_fmt.height / 2);
+	struct frame_buffer disp_buffer = { };
+	struct dec_seq_info *seq_info = &inst->codec_info->dec_info.seq_info;
+	int consumed_num = wave6_vpu_get_consumed_fb_num(inst);
+	int ret;
+
+	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
+		dst_buf = &v4l2_m2m_buf->vb;
+		vpu_buf = wave6_to_vpu_buf(dst_buf);
+
+		if (vpu_buf->consumed)
+			continue;
+
+		if (consumed_num >= WAVE6_MAX_FBS)
+			break;
+
+		if (inst->dst_fmt.num_planes == 1) {
+			disp_buffer.buf_y = wave6_get_dma_addr(dst_buf, 0);
+			disp_buffer.buf_cb = disp_buffer.buf_y + luma_size;
+			disp_buffer.buf_cr = disp_buffer.buf_cb + chroma_size;
+		} else if (inst->dst_fmt.num_planes == 2) {
+			disp_buffer.buf_y = wave6_get_dma_addr(dst_buf, 0);
+			disp_buffer.buf_cb = wave6_get_dma_addr(dst_buf, 1);
+			disp_buffer.buf_cr = disp_buffer.buf_cb + chroma_size;
+		} else if (inst->dst_fmt.num_planes == 3) {
+			disp_buffer.buf_y = wave6_get_dma_addr(dst_buf, 0);
+			disp_buffer.buf_cb = wave6_get_dma_addr(dst_buf, 1);
+			disp_buffer.buf_cr = wave6_get_dma_addr(dst_buf, 2);
+		}
+		disp_buffer.height = inst->src_fmt.height;
+		disp_buffer.stride = fb_stride;
+		disp_buffer.map_type = LINEAR_FRAME_MAP;
+		disp_buffer.luma_bit_depth = seq_info->luma_bit_depth;
+		disp_buffer.chroma_bit_depth = seq_info->chroma_bit_depth;
+		disp_buffer.c_fmt_idc = seq_info->c_fmt_idc;
+
+		ret = wave6_vpu_dec_register_display_buffer(inst, disp_buffer);
+		if (ret) {
+			dev_err(inst->dev->dev, "fail register display buffer %d", ret);
+			break;
+		}
+
+		vpu_buf->consumed = true;
+		consumed_num++;
+	}
+
+	if (!inst->fbc_buf_registered) {
+		ret = wave6_vpu_dec_register_frame_buffer(inst);
+		if (ret)
+			dev_err(inst->dev->dev, "register frame buffer fail %d\n", ret);
+		else
+			inst->fbc_buf_registered = true;
+	}
+}
+
+static enum v4l2_quantization to_v4l2_quantization(u32 video_full_range_flag)
+{
+	switch (video_full_range_flag) {
+	case 0:
+		return V4L2_QUANTIZATION_LIM_RANGE;
+	case 1:
+		return V4L2_QUANTIZATION_FULL_RANGE;
+	default:
+		return V4L2_QUANTIZATION_DEFAULT;
+	}
+}
+
+static enum v4l2_colorspace to_v4l2_colorspace(u32 colour_primaries)
+{
+	switch (colour_primaries) {
+	case 1:
+		return V4L2_COLORSPACE_REC709;
+	case 4:
+		return V4L2_COLORSPACE_470_SYSTEM_M;
+	case 5:
+		return V4L2_COLORSPACE_470_SYSTEM_BG;
+	case 6:
+		return V4L2_COLORSPACE_SMPTE170M;
+	case 7:
+		return V4L2_COLORSPACE_SMPTE240M;
+	case 9:
+		return V4L2_COLORSPACE_BT2020;
+	case 11:
+		return V4L2_COLORSPACE_DCI_P3;
+	default:
+		return V4L2_COLORSPACE_DEFAULT;
+	}
+}
+
+static enum v4l2_xfer_func to_v4l2_xfer_func(u32 transfer_characteristics)
+{
+	switch (transfer_characteristics) {
+	case 1:
+		return V4L2_XFER_FUNC_709;
+	case 6:
+		return V4L2_XFER_FUNC_709;
+	case 7:
+		return V4L2_XFER_FUNC_SMPTE240M;
+	case 8:
+		return V4L2_XFER_FUNC_NONE;
+	case 13:
+		return V4L2_XFER_FUNC_SRGB;
+	case 14:
+		return V4L2_XFER_FUNC_709;
+	case 16:
+		return V4L2_XFER_FUNC_SMPTE2084;
+	default:
+		return V4L2_XFER_FUNC_DEFAULT;
+	}
+}
+
+static enum v4l2_ycbcr_encoding to_v4l2_ycbcr_encoding(u32 matrix_coeffs)
+{
+	switch (matrix_coeffs) {
+	case 1:
+		return V4L2_YCBCR_ENC_709;
+	case 5:
+		return V4L2_YCBCR_ENC_601;
+	case 6:
+		return V4L2_YCBCR_ENC_601;
+	case 7:
+		return V4L2_YCBCR_ENC_SMPTE240M;
+	case 9:
+		return V4L2_YCBCR_ENC_BT2020;
+	case 10:
+		return V4L2_YCBCR_ENC_BT2020_CONST_LUM;
+	default:
+		return V4L2_YCBCR_ENC_DEFAULT;
+	}
+}
+
+static void wave6_update_color_info(struct vpu_instance *inst,
+				    struct dec_seq_info *seq_info)
+{
+	struct color_param *color = &seq_info->color;
+
+	if (!color->video_signal_type_present)
+		goto set_default_all;
+
+	inst->quantization = to_v4l2_quantization(color->color_range);
+
+	if (!color->color_description_present)
+		goto set_default_color;
+
+	inst->colorspace = to_v4l2_colorspace(color->color_primaries);
+	inst->xfer_func = to_v4l2_xfer_func(color->transfer_characteristics);
+	inst->ycbcr_enc = to_v4l2_ycbcr_encoding(color->matrix_coefficients);
+
+	return;
+
+set_default_all:
+	inst->quantization = V4L2_QUANTIZATION_DEFAULT;
+set_default_color:
+	inst->colorspace = V4L2_COLORSPACE_DEFAULT;
+	inst->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	inst->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+}
+
+static enum v4l2_mpeg_video_hevc_profile to_v4l2_hevc_profile(u32 profile)
+{
+	switch (profile) {
+	case HEVC_PROFILE_MAIN:
+		return V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN;
+	case HEVC_PROFILE_STILLPICTURE:
+		return V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE;
+	default:
+		return V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN;
+	}
+}
+
+static enum v4l2_mpeg_video_h264_profile to_v4l2_h264_profile(u32 profile)
+{
+	switch (profile) {
+	case H264_PROFILE_BP:
+		return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
+	case H264_PROFILE_MP:
+		return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
+	case H264_PROFILE_EXTENDED:
+		return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED;
+	case H264_PROFILE_HP:
+		return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
+	default:
+		return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
+	}
+}
+
+static void wave6_update_v4l2_ctrls(struct vpu_instance *inst,
+				    struct dec_seq_info *info)
+{
+	struct v4l2_ctrl *ctrl;
+	u32 min_disp_cnt;
+
+	min_disp_cnt = info->frame_buf_delay + 1;
+	ctrl = v4l2_ctrl_find(&inst->v4l2_ctrl_hdl,
+			      V4L2_CID_MIN_BUFFERS_FOR_CAPTURE);
+	if (ctrl)
+		v4l2_ctrl_s_ctrl(ctrl, min_disp_cnt);
+
+	if (inst->src_fmt.pixelformat == V4L2_PIX_FMT_HEVC) {
+		ctrl = v4l2_ctrl_find(&inst->v4l2_ctrl_hdl,
+				      V4L2_CID_MPEG_VIDEO_HEVC_PROFILE);
+		if (ctrl)
+			v4l2_ctrl_s_ctrl(ctrl, to_v4l2_hevc_profile(info->profile));
+	} else if (inst->src_fmt.pixelformat == V4L2_PIX_FMT_H264) {
+		ctrl = v4l2_ctrl_find(&inst->v4l2_ctrl_hdl,
+				      V4L2_CID_MPEG_VIDEO_H264_PROFILE);
+		if (ctrl)
+			v4l2_ctrl_s_ctrl(ctrl, to_v4l2_h264_profile(info->profile));
+	}
+}
+
+static int wave6_vpu_dec_start_decode(struct vpu_instance *inst)
+{
+	struct dec_param pic_param;
+	int ret;
+	u32 fail_res = 0;
+
+	memset(&pic_param, 0, sizeof(struct dec_param));
+
+	wave6_handle_bitstream_buffer(inst);
+	wave6_vpu_dec_handle_dst_buffer(inst);
+
+	ret = wave6_vpu_dec_start_one_frame(inst, &pic_param, &fail_res);
+	if (ret) {
+		struct vb2_v4l2_buffer *src_buf = NULL;
+
+		dev_err(inst->dev->dev, "[%d] %s: fail %d\n", inst->id, __func__, ret);
+		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_STOP);
+
+		src_buf = v4l2_m2m_src_buf_remove(inst->v4l2_fh.m2m_ctx);
+		if (src_buf) {
+			v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+			inst->sequence++;
+			inst->processed_buf_num++;
+			inst->error_buf_num++;
+		}
+	}
+
+	return ret;
+}
+
+static void wave6_handle_decoded_frame(struct vpu_instance *inst,
+				       struct dec_output_info *info)
+{
+	struct vb2_v4l2_buffer *src_buf;
+	struct vb2_v4l2_buffer *dst_buf;
+	struct vpu_buffer *vpu_buf;
+	enum vb2_buffer_state state;
+
+	state = info->decoding_success ? VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR;
+
+	src_buf = v4l2_m2m_next_src_buf(inst->v4l2_fh.m2m_ctx);
+	if (!src_buf) {
+		dev_err(inst->dev->dev, "[%d] decoder can't find src buffer\n", inst->id);
+		return;
+	}
+
+	vpu_buf = wave6_to_vpu_buf(src_buf);
+	if (!vpu_buf || !vpu_buf->consumed) {
+		dev_err(inst->dev->dev, "[%d] src buffer is not consumed\n", inst->id);
+		return;
+	}
+
+	dst_buf = wave6_get_dst_buf_by_addr(inst, info->frame_decoded_addr);
+	if (dst_buf) {
+		struct vpu_buffer *dst_vpu_buf = wave6_to_vpu_buf(dst_buf);
+
+		if (wave6_to_vpu_buf(dst_buf)->used) {
+			dev_warn(inst->dev->dev, "[%d] duplication frame buffer\n", inst->id);
+			inst->sequence++;
+		}
+		v4l2_m2m_buf_copy_metadata(src_buf, dst_buf);
+		dst_vpu_buf->used = true;
+		if (state == VB2_BUF_STATE_ERROR)
+			dst_vpu_buf->error = true;
+		dst_vpu_buf->ts_input = vpu_buf->ts_input;
+		dst_vpu_buf->ts_start = vpu_buf->ts_start;
+		dst_vpu_buf->ts_finish = ktime_get_raw();
+		dst_vpu_buf->hw_time = wave6_vpu_cycle_to_ns(inst->dev, info->cycle.frame_cycle);
+	}
+
+	v4l2_m2m_src_buf_remove_by_buf(inst->v4l2_fh.m2m_ctx, src_buf);
+	if (state == VB2_BUF_STATE_ERROR) {
+		dev_dbg(inst->dev->dev, "[%d] error frame %d\n", inst->id, inst->sequence);
+		inst->error_buf_num++;
+	}
+	v4l2_m2m_buf_done(src_buf, state);
+	inst->processed_buf_num++;
+}
+
+static void wave6_handle_skipped_frame(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *src_buf;
+	struct vpu_buffer *vpu_buf;
+
+	src_buf = v4l2_m2m_next_src_buf(inst->v4l2_fh.m2m_ctx);
+	if (!src_buf)
+		return;
+
+	vpu_buf = wave6_to_vpu_buf(src_buf);
+	if (!vpu_buf || !vpu_buf->consumed)
+		return;
+
+	dev_dbg(inst->dev->dev, "[%d] skip frame %d\n", inst->id, inst->sequence);
+
+	inst->sequence++;
+	inst->processed_buf_num++;
+	inst->error_buf_num++;
+	v4l2_m2m_src_buf_remove_by_buf(inst->v4l2_fh.m2m_ctx, src_buf);
+	v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+}
+
+static void wave6_handle_display_frame(struct vpu_instance *inst,
+				       dma_addr_t addr, enum vb2_buffer_state state)
+{
+	struct vb2_v4l2_buffer *dst_buf;
+	struct vpu_buffer *vpu_buf;
+	int i;
+
+	dst_buf = wave6_get_dst_buf_by_addr(inst, addr);
+	if (!dst_buf)
+		return;
+
+	vpu_buf = wave6_to_vpu_buf(dst_buf);
+	if (!vpu_buf->used) {
+		dev_dbg(inst->dev->dev, "[%d] recycle display buffer\n", inst->id);
+		vpu_buf->consumed = false;
+		return;
+	}
+
+	for (i = 0; i < inst->dst_fmt.num_planes; i++)
+		vb2_set_plane_payload(&dst_buf->vb2_buf, i,
+				      inst->dst_fmt.plane_fmt[i].sizeimage);
+
+	vpu_buf->ts_output = ktime_get_raw();
+	wave6_vpu_handle_performance(inst, vpu_buf);
+
+	if (vpu_buf->error)
+		state = VB2_BUF_STATE_ERROR;
+	dst_buf->sequence = inst->sequence++;
+	dst_buf->field = V4L2_FIELD_NONE;
+	if (state == VB2_BUF_STATE_ERROR)
+		dev_dbg(inst->dev->dev, "[%d] discard frame %d\n", inst->id, dst_buf->sequence);
+	v4l2_m2m_dst_buf_remove_by_buf(inst->v4l2_fh.m2m_ctx, dst_buf);
+	v4l2_m2m_buf_done(dst_buf, state);
+}
+
+static void wave6_handle_display_frames(struct vpu_instance *inst,
+					struct dec_output_info *info)
+{
+	int i;
+
+	for (i = 0; i < info->disp_frame_num; i++)
+		wave6_handle_display_frame(inst,
+					   info->disp_frame_addr[i],
+					   VB2_BUF_STATE_DONE);
+}
+
+static void wave6_handle_discard_frames(struct vpu_instance *inst,
+					struct dec_output_info *info)
+{
+	int i;
+
+	for (i = 0; i < info->release_disp_frame_num; i++)
+		wave6_handle_display_frame(inst,
+					   info->release_disp_frame_addr[i],
+					   VB2_BUF_STATE_ERROR);
+}
+
+static void wave6_handle_last_frame(struct vpu_instance *inst,
+				    struct vb2_v4l2_buffer *dst_buf)
+{
+	int i;
+
+	if (!dst_buf) {
+		dst_buf = v4l2_m2m_dst_buf_remove(inst->v4l2_fh.m2m_ctx);
+		if (!dst_buf) {
+			inst->next_buf_last = true;
+			return;
+		}
+	}
+
+	for (i = 0; i < inst->dst_fmt.num_planes; i++)
+		vb2_set_plane_payload(&dst_buf->vb2_buf, i, 0);
+
+	dst_buf->flags |= V4L2_BUF_FLAG_LAST;
+	dst_buf->field = V4L2_FIELD_NONE;
+	v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
+
+	if (inst->state != VPU_INST_STATE_INIT_SEQ) {
+		dev_dbg(inst->dev->dev, "[%d] eos\n", inst->id);
+		inst->eos = true;
+		v4l2_m2m_set_src_buffered(inst->v4l2_fh.m2m_ctx, false);
+	}
+}
+
+static void wave6_vpu_dec_retry_one_frame(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *src_buf;
+	struct vpu_buffer *vpu_buf;
+
+	src_buf = v4l2_m2m_next_src_buf(inst->v4l2_fh.m2m_ctx);
+	if (!src_buf)
+		return;
+
+	vpu_buf = wave6_to_vpu_buf(src_buf);
+	vpu_buf->consumed = false;
+}
+
+static void wave6_vpu_dec_handle_source_change(struct vpu_instance *inst)
+{
+	static const struct v4l2_event vpu_event_src_ch = {
+		.type = V4L2_EVENT_SOURCE_CHANGE,
+		.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+	};
+	struct dec_seq_info *info = &inst->codec_info->dec_info.seq_info;
+
+	dev_dbg(inst->dev->dev, "pic size %dx%d profile %d, min_fb_cnt : %d | min_disp_cnt : %d\n",
+		info->pic_width, info->pic_height,
+		info->profile, info->min_frame_buffer_count, info->frame_buf_delay);
+
+	wave6_vpu_dec_retry_one_frame(inst);
+	wave6_vpu_dec_reset_frame_buffer_info(inst);
+	inst->fbc_buf_registered = false;
+
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_INIT_SEQ);
+
+	inst->crop.left = info->pic_crop_rect.left;
+	inst->crop.top = info->pic_crop_rect.top;
+	inst->crop.width = info->pic_crop_rect.right - inst->crop.left;
+	inst->crop.height = info->pic_crop_rect.bottom - inst->crop.top;
+
+	wave6_update_v4l2_ctrls(inst, info);
+	wave6_update_color_info(inst, info);
+	wave6_update_pix_fmt(&inst->src_fmt, info->pic_width, info->pic_height);
+	wave6_update_pix_fmt_cap(&inst->dst_fmt,
+				 info->pic_width, info->pic_height,
+				 true);
+
+	trace_wave6_vpu_source_change(inst, info);
+
+	v4l2_event_queue_fh(&inst->v4l2_fh, &vpu_event_src_ch);
+}
+
+static void wave6_vpu_dec_handle_decoding_warn_error(struct vpu_instance *inst,
+						     struct dec_output_info *info)
+{
+	if (info->warn_info)
+		dev_dbg(inst->dev->dev, "[%d] decoding %d warning 0x%x\n",
+			inst->id, inst->processed_buf_num, info->warn_info);
+
+	if (info->error_reason)
+		dev_dbg(inst->dev->dev, "[%d] decoding %d error 0x%x\n",
+			inst->id, inst->processed_buf_num, info->error_reason);
+}
+
+static void wave6_vpu_dec_finish_decode(struct vpu_instance *inst, bool error)
+{
+	struct dec_output_info info;
+	struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx;
+	int ret;
+
+	memset(&info, 0, sizeof(info));
+
+	ret = wave6_vpu_dec_get_output_info(inst, &info);
+	if (ret)
+		goto finish_decode;
+
+	trace_wave6_vpu_dec_done(inst, &info);
+
+	dev_dbg(inst->dev->dev, "dec %d dis %d noti_flag %d stream_end %d\n",
+		info.frame_decoded, info.frame_display,
+		info.notification_flags, info.stream_end);
+
+	if (info.notification_flags & DEC_NOTI_FLAG_NO_FB) {
+		wave6_vpu_dec_retry_one_frame(inst);
+		goto finish_decode;
+	}
+
+	if (info.notification_flags & DEC_NOTI_FLAG_SEQ_CHANGE) {
+		v4l2_m2m_mark_stopped(m2m_ctx);
+
+		if (info.frame_display)
+			wave6_handle_display_frames(inst, &info);
+
+		if (info.release_disp_frame_num)
+			wave6_handle_discard_frames(inst, &info);
+
+		wave6_vpu_dec_handle_source_change(inst);
+
+		wave6_handle_last_frame(inst, NULL);
+
+		goto finish_decode;
+	}
+
+	wave6_vpu_dec_handle_decoding_warn_error(inst, &info);
+
+	if (info.frame_decoded)
+		wave6_handle_decoded_frame(inst, &info);
+	else
+		wave6_handle_skipped_frame(inst);
+
+	if (info.frame_display)
+		wave6_handle_display_frames(inst, &info);
+
+	if (info.release_disp_frame_num)
+		wave6_handle_discard_frames(inst, &info);
+
+	if (info.stream_end && !inst->eos)
+		wave6_handle_last_frame(inst, NULL);
+
+finish_decode:
+	wave6_vpu_finish_job(inst);
+}
+
+static int wave6_vpu_dec_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, VPU_DEC_DRV_NAME, sizeof(cap->driver));
+	strscpy(cap->card, VPU_DEC_DRV_NAME, sizeof(cap->card));
+	strscpy(cap->bus_info, "platform:" VPU_DEC_DRV_NAME, sizeof(cap->bus_info));
+
+	return 0;
+}
+
+static int wave6_vpu_dec_enum_framesizes(struct file *f, void *fh, struct v4l2_frmsizeenum *fsize)
+{
+	const struct vpu_format *vpu_fmt;
+
+	if (fsize->index)
+		return -EINVAL;
+
+	vpu_fmt = wave6_find_vpu_fmt(fsize->pixel_format, VPU_FMT_TYPE_CODEC);
+	if (!vpu_fmt) {
+		vpu_fmt = wave6_find_vpu_fmt(fsize->pixel_format, VPU_FMT_TYPE_RAW);
+		if (!vpu_fmt)
+			return -EINVAL;
+	}
+
+	fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
+	fsize->stepwise.min_width = vpu_fmt->min_width;
+	fsize->stepwise.max_width = vpu_fmt->max_width;
+	fsize->stepwise.step_width = W6_DEC_PIC_SIZE_STEP;
+	fsize->stepwise.min_height = vpu_fmt->min_height;
+	fsize->stepwise.max_height = vpu_fmt->max_height;
+	fsize->stepwise.step_height = W6_DEC_PIC_SIZE_STEP;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_enum_fmt_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+	const struct vpu_format *vpu_fmt;
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(f->index, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt)
+		return -EINVAL;
+
+	f->pixelformat = vpu_fmt->v4l2_pix_fmt;
+	f->flags = 0;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_try_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vpu_format *vpu_fmt;
+	int width, height;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	if (!V4L2_TYPE_IS_CAPTURE(f->type))
+		return -EINVAL;
+
+	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt) {
+		width = inst->dst_fmt.width;
+		height = inst->dst_fmt.height;
+		pix_mp->pixelformat = inst->dst_fmt.pixelformat;
+		pix_mp->num_planes = inst->dst_fmt.num_planes;
+	} else {
+		width = clamp(pix_mp->width,
+			      vpu_fmt->min_width,
+			      round_up(inst->src_fmt.width, W6_DEC_BUF_ALIGNMENT));
+		height = clamp(pix_mp->height,
+			       vpu_fmt->min_height, inst->src_fmt.height);
+		pix_mp->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		pix_mp->num_planes = vpu_fmt->num_planes;
+	}
+
+	if (inst->state >= VPU_INST_STATE_INIT_SEQ) {
+		width = inst->dst_fmt.width;
+		height = inst->dst_fmt.height;
+	}
+
+	wave6_update_pix_fmt_cap(pix_mp, width, height, false);
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_s_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vpu_format *vpu_fmt;
+	int i, ret;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	ret = wave6_vpu_dec_try_fmt_cap(file, fh, f);
+	if (ret)
+		return ret;
+
+	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt)
+		return -EINVAL;
+
+	inst->dst_fmt.width = pix_mp->width;
+	inst->dst_fmt.height = pix_mp->height;
+	inst->dst_fmt.pixelformat = pix_mp->pixelformat;
+	inst->dst_fmt.field = pix_mp->field;
+	inst->dst_fmt.flags = pix_mp->flags;
+	inst->dst_fmt.num_planes = pix_mp->num_planes;
+	for (i = 0; i < inst->dst_fmt.num_planes; i++) {
+		inst->dst_fmt.plane_fmt[i].bytesperline = pix_mp->plane_fmt[i].bytesperline;
+		inst->dst_fmt.plane_fmt[i].sizeimage = pix_mp->plane_fmt[i].sizeimage;
+	}
+
+	inst->cbcr_interleave = !!(vpu_fmt->flags & VPU_FMT_FLAG_CBCR_INTERLEAVED);
+	inst->nv21 = !!(vpu_fmt->flags & VPU_FMT_FLAG_CRCB_ORDER);
+
+	return 0;
+}
+
+static int wave6_vpu_dec_g_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	int i;
+
+	pix_mp->width = inst->dst_fmt.width;
+	pix_mp->height = inst->dst_fmt.height;
+	pix_mp->pixelformat = inst->dst_fmt.pixelformat;
+	pix_mp->field = inst->dst_fmt.field;
+	pix_mp->flags = inst->dst_fmt.flags;
+	pix_mp->num_planes = inst->dst_fmt.num_planes;
+	for (i = 0; i < pix_mp->num_planes; i++) {
+		pix_mp->plane_fmt[i].bytesperline = inst->dst_fmt.plane_fmt[i].bytesperline;
+		pix_mp->plane_fmt[i].sizeimage = inst->dst_fmt.plane_fmt[i].sizeimage;
+	}
+
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_enum_fmt_out(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	const struct vpu_format *vpu_fmt;
+
+	dev_dbg(inst->dev->dev, "%s: index %d\n", __func__, f->index);
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(f->index, VPU_FMT_TYPE_CODEC);
+	if (!vpu_fmt)
+		return -EINVAL;
+
+	f->pixelformat = vpu_fmt->v4l2_pix_fmt;
+	f->flags = V4L2_FMT_FLAG_DYN_RESOLUTION | V4L2_FMT_FLAG_COMPRESSED;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_try_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vpu_format *vpu_fmt;
+	int width, height;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	if (!V4L2_TYPE_IS_OUTPUT(f->type))
+		return -EINVAL;
+
+	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_CODEC);
+	if (!vpu_fmt) {
+		width = inst->src_fmt.width;
+		height = inst->src_fmt.height;
+		pix_mp->pixelformat = inst->src_fmt.pixelformat;
+		pix_mp->num_planes = inst->src_fmt.num_planes;
+	} else {
+		width = pix_mp->width;
+		height = pix_mp->height;
+		pix_mp->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		pix_mp->num_planes = vpu_fmt->num_planes;
+	}
+
+	wave6_update_pix_fmt(pix_mp, width, height);
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_s_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane in_pix_mp = f->fmt.pix_mp;
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	int i, ret;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	ret = wave6_vpu_dec_try_fmt_out(file, fh, f);
+	if (ret)
+		return ret;
+
+	pix_mp->colorspace = in_pix_mp.colorspace;
+	pix_mp->ycbcr_enc = in_pix_mp.ycbcr_enc;
+	pix_mp->quantization = in_pix_mp.quantization;
+	pix_mp->xfer_func = in_pix_mp.xfer_func;
+
+	inst->src_fmt.width = pix_mp->width;
+	inst->src_fmt.height = pix_mp->height;
+	inst->src_fmt.pixelformat = pix_mp->pixelformat;
+	inst->src_fmt.field = pix_mp->field;
+	inst->src_fmt.flags = pix_mp->flags;
+	inst->src_fmt.num_planes = pix_mp->num_planes;
+	for (i = 0; i < inst->src_fmt.num_planes; i++) {
+		inst->src_fmt.plane_fmt[i].bytesperline = pix_mp->plane_fmt[i].bytesperline;
+		inst->src_fmt.plane_fmt[i].sizeimage = pix_mp->plane_fmt[i].sizeimage;
+	}
+
+	inst->colorspace = pix_mp->colorspace;
+	inst->ycbcr_enc = pix_mp->ycbcr_enc;
+	inst->quantization = pix_mp->quantization;
+	inst->xfer_func = pix_mp->xfer_func;
+
+	wave6_update_pix_fmt_cap(&inst->dst_fmt,
+				 pix_mp->width, pix_mp->height,
+				 true);
+
+	return 0;
+}
+
+static int wave6_vpu_dec_g_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	int i;
+
+	pix_mp->width = inst->src_fmt.width;
+	pix_mp->height = inst->src_fmt.height;
+	pix_mp->pixelformat = inst->src_fmt.pixelformat;
+	pix_mp->field = inst->src_fmt.field;
+	pix_mp->flags = inst->src_fmt.flags;
+	pix_mp->num_planes = inst->src_fmt.num_planes;
+	for (i = 0; i < pix_mp->num_planes; i++) {
+		pix_mp->plane_fmt[i].bytesperline = inst->src_fmt.plane_fmt[i].bytesperline;
+		pix_mp->plane_fmt[i].sizeimage = inst->src_fmt.plane_fmt[i].sizeimage;
+	}
+
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_g_selection(struct file *file, void *fh, struct v4l2_selection *s)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+
+	dev_dbg(inst->dev->dev, "%s: type %d target %d\n",
+		__func__, s->type, s->target);
+
+	if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		return -EINVAL;
+
+	switch (s->target) {
+	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+		s->r.left = 0;
+		s->r.top = 0;
+		s->r.width = inst->dst_fmt.width;
+		s->r.height = inst->dst_fmt.height;
+		break;
+	case V4L2_SEL_TGT_COMPOSE_PADDED:
+	case V4L2_SEL_TGT_COMPOSE:
+		s->r.left = 0;
+		s->r.top = 0;
+		if (inst->scaler_info.enable) {
+			s->r.width = inst->scaler_info.width;
+			s->r.height = inst->scaler_info.height;
+		} else if (inst->crop.width && inst->crop.height) {
+			s->r = inst->crop;
+		} else {
+			s->r.width = inst->src_fmt.width;
+			s->r.height = inst->src_fmt.height;
+		}
+		break;
+	case V4L2_SEL_TGT_CROP:
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+		s->r.left = 0;
+		s->r.top = 0;
+		s->r.width = inst->src_fmt.width;
+		s->r.height = inst->src_fmt.height;
+		if (inst->crop.width && inst->crop.height)
+			s->r = inst->crop;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_dec_s_selection(struct file *file, void *fh, struct v4l2_selection *s)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	int step = 4;
+	int scale_width, scale_height;
+	int min_scale_width, min_scale_height;
+
+	if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		return -EINVAL;
+
+	if (s->target != V4L2_SEL_TGT_COMPOSE)
+		return -EINVAL;
+
+	if (!(s->flags & (V4L2_SEL_FLAG_GE | V4L2_SEL_FLAG_LE)))
+		s->flags |= V4L2_SEL_FLAG_LE;
+
+	scale_width = clamp(s->r.width, W6_MIN_DEC_PIC_WIDTH,
+			    round_up(inst->src_fmt.width, W6_DEC_BUF_ALIGNMENT));
+	scale_height = clamp(s->r.height, W6_MIN_DEC_PIC_HEIGHT,
+			     inst->src_fmt.height);
+	if (s->flags & V4L2_SEL_FLAG_GE) {
+		scale_width = round_up(scale_width, step);
+		scale_height = round_up(scale_height, step);
+	}
+	if (s->flags & V4L2_SEL_FLAG_LE) {
+		scale_width = round_down(scale_width, step);
+		scale_height = round_down(scale_height, step);
+	}
+
+	if (scale_width < inst->src_fmt.width ||
+	    scale_height < inst->src_fmt.height)
+		inst->scaler_info.enable = true;
+
+	if (inst->scaler_info.enable) {
+		min_scale_width = ALIGN((inst->src_fmt.width / 8), step);
+		min_scale_height = ALIGN((inst->src_fmt.height / 8), step);
+
+		if (scale_width < W6_MIN_DEC_PIC_WIDTH)
+			scale_width = W6_MIN_DEC_PIC_WIDTH;
+		if (scale_width < min_scale_width)
+			scale_width = min_scale_width;
+		if (scale_height < W6_MIN_DEC_PIC_HEIGHT)
+			scale_height = W6_MIN_DEC_PIC_HEIGHT;
+		if (scale_height < min_scale_height)
+			scale_height = min_scale_height;
+
+		inst->scaler_info.width = scale_width;
+		inst->scaler_info.height = scale_height;
+	}
+
+	s->r.left = 0;
+	s->r.top = 0;
+	s->r.width = scale_width;
+	s->r.height = scale_height;
+
+	return 0;
+}
+
+static int wave6_vpu_dec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *dc)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	int ret;
+
+	dev_dbg(inst->dev->dev, "%s: cmd %d\n", __func__, dc->cmd);
+
+	ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, dc);
+	if (ret)
+		return ret;
+
+	switch (dc->cmd) {
+	case V4L2_DEC_CMD_STOP:
+		v4l2_m2m_set_src_buffered(inst->v4l2_fh.m2m_ctx, true);
+		v4l2_m2m_try_schedule(inst->v4l2_fh.m2m_ctx);
+		break;
+	case V4L2_DEC_CMD_START:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops wave6_vpu_dec_ioctl_ops = {
+	.vidioc_querycap = wave6_vpu_dec_querycap,
+	.vidioc_enum_framesizes = wave6_vpu_dec_enum_framesizes,
+
+	.vidioc_enum_fmt_vid_cap = wave6_vpu_dec_enum_fmt_cap,
+	.vidioc_s_fmt_vid_cap_mplane = wave6_vpu_dec_s_fmt_cap,
+	.vidioc_g_fmt_vid_cap_mplane = wave6_vpu_dec_g_fmt_cap,
+	.vidioc_try_fmt_vid_cap_mplane = wave6_vpu_dec_try_fmt_cap,
+
+	.vidioc_enum_fmt_vid_out = wave6_vpu_dec_enum_fmt_out,
+	.vidioc_s_fmt_vid_out_mplane = wave6_vpu_dec_s_fmt_out,
+	.vidioc_g_fmt_vid_out_mplane = wave6_vpu_dec_g_fmt_out,
+	.vidioc_try_fmt_vid_out_mplane = wave6_vpu_dec_try_fmt_out,
+
+	.vidioc_g_selection = wave6_vpu_dec_g_selection,
+	.vidioc_s_selection = wave6_vpu_dec_s_selection,
+
+	.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+	.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+	.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+	.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+	.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+	.vidioc_streamon = v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+
+	.vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd,
+	.vidioc_decoder_cmd = wave6_vpu_dec_decoder_cmd,
+
+	.vidioc_subscribe_event = wave6_vpu_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int wave6_vpu_dec_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct vpu_instance *inst = wave6_ctrl_to_vpu_inst(ctrl);
+
+	trace_wave6_vpu_s_ctrl(inst, ctrl);
+
+	dev_dbg(inst->dev->dev, "%s: name %s value %d\n",
+		__func__, ctrl->name, ctrl->val);
+
+	switch (ctrl->id) {
+	case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE:
+		inst->disp_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY:
+	case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
+	case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE:
+	case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops wave6_vpu_dec_ctrl_ops = {
+	.s_ctrl = wave6_vpu_dec_s_ctrl,
+};
+
+static void wave6_set_dec_openparam(struct dec_open_param *open_param,
+				    struct vpu_instance *inst)
+{
+	open_param->inst_buffer.temp_base = inst->dev->temp_vbuf.daddr;
+	open_param->inst_buffer.temp_size = inst->dev->temp_vbuf.size;
+	open_param->disp_mode = inst->disp_mode;
+}
+
+static int wave6_vpu_dec_create_instance(struct vpu_instance *inst)
+{
+	int ret;
+	struct dec_open_param open_param;
+
+	memset(&open_param, 0, sizeof(struct dec_open_param));
+
+	wave6_vpu_record_performance_timestamps(inst);
+
+	wave6_vpu_core_activate(inst->dev);
+	ret = pm_runtime_resume_and_get(inst->dev->dev);
+	if (ret) {
+		dev_err(inst->dev->dev, "runtime_resume failed %d\n", ret);
+		return ret;
+	}
+
+	inst->std = wave6_to_codec_std(inst->type, inst->src_fmt.pixelformat);
+	if (inst->std == STD_UNKNOWN) {
+		dev_err(inst->dev->dev, "unsupported pixelformat: %.4s\n",
+			(char *)&inst->src_fmt.pixelformat);
+		ret = -EINVAL;
+		goto error_pm;
+	}
+
+	wave6_set_dec_openparam(&open_param, inst);
+
+	ret = wave6_vpu_dec_open(inst, &open_param);
+	if (ret) {
+		dev_err(inst->dev->dev, "failed create instance : %d\n", ret);
+		goto error_pm;
+	}
+
+	dev_dbg(inst->dev->dev, "[%d] decoder\n", inst->id);
+
+	scoped_guard(spinlock, &inst->dev->inst_lock)
+		list_add_tail(&inst->list, &inst->dev->instances);
+	wave6_vpu_enable_instance(inst);
+	wave6_vpu_create_dbgfs_file(inst);
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_OPEN);
+
+	return 0;
+
+error_pm:
+	pm_runtime_put_sync(inst->dev->dev);
+
+	return ret;
+}
+
+static int wave6_vpu_dec_prepare_fb(struct vpu_instance *inst)
+{
+	int ret;
+	unsigned int i;
+	unsigned int fb_num;
+	unsigned int mv_num;
+	unsigned int fb_stride;
+	unsigned int fb_height;
+	unsigned int luma_size;
+	unsigned int chroma_size;
+	struct dec_seq_info *seq_info = &inst->codec_info->dec_info.seq_info;
+
+	fb_num = seq_info->min_frame_buffer_count;
+	mv_num = seq_info->req_mv_buffer_count;
+
+	fb_stride = ALIGN(inst->src_fmt.width, W6_FBC_BUF_ALIGNMENT);
+	fb_height = ALIGN(inst->src_fmt.height, W6_FBC_BUF_ALIGNMENT);
+
+	luma_size = fb_stride * fb_height;
+	chroma_size = ALIGN(fb_stride / 2, W6_FBC_BUF_ALIGNMENT) * fb_height;
+
+	for (i = 0; i < fb_num; i++) {
+		struct frame_buffer *frame = &inst->frame_buf[i];
+		struct vpu_buf *vframe = &inst->frame_vbuf[i];
+
+		vframe->size = luma_size + chroma_size;
+		ret = wave6_vdi_alloc_dma(inst->dev->dev, vframe);
+		if (ret) {
+			dev_err(inst->dev->dev, "alloc FBC buffer fail : %zu\n",
+				vframe->size);
+			goto error;
+		}
+
+		frame->buf_y = vframe->daddr;
+		frame->buf_cb = vframe->daddr + luma_size;
+		frame->buf_cr = (dma_addr_t)-1;
+		frame->stride = fb_stride;
+		frame->height = fb_height;
+		frame->map_type = COMPRESSED_FRAME_MAP;
+	}
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_FBC_Y_TBL, fb_num);
+	if (ret)
+		goto error;
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_FBC_C_TBL, fb_num);
+	if (ret)
+		goto error;
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_MV_COL, mv_num);
+	if (ret)
+		goto error;
+
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_PIC_RUN);
+
+	wave6_vpu_record_performance_timestamps(inst);
+
+	return 0;
+
+error:
+	wave6_vpu_dec_release_fb(inst);
+
+	return ret;
+}
+
+static int wave6_vpu_dec_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
+				     unsigned int *num_planes, unsigned int sizes[],
+				     struct device *alloc_devs[])
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(q);
+	struct v4l2_pix_format_mplane inst_format =
+		(V4L2_TYPE_IS_OUTPUT(q->type)) ? inst->src_fmt : inst->dst_fmt;
+	unsigned int i;
+
+	dev_dbg(inst->dev->dev, "%s: num_buffers %d num_planes %d type %d\n",
+		__func__, *num_buffers, *num_planes, q->type);
+
+	if (*num_planes) {
+		if (inst_format.num_planes != *num_planes)
+			return -EINVAL;
+
+		for (i = 0; i < *num_planes; i++) {
+			if (sizes[i] < inst_format.plane_fmt[i].sizeimage)
+				return -EINVAL;
+		}
+	} else {
+		*num_planes = inst_format.num_planes;
+		for (i = 0; i < *num_planes; i++) {
+			sizes[i] = inst_format.plane_fmt[i].sizeimage;
+			dev_dbg(inst->dev->dev, "size[%d] : %d\n", i, sizes[i]);
+		}
+
+		if (V4L2_TYPE_IS_CAPTURE(q->type)) {
+			struct v4l2_ctrl *ctrl;
+			unsigned int min_disp_cnt = 0;
+
+			ctrl = v4l2_ctrl_find(&inst->v4l2_ctrl_hdl,
+					      V4L2_CID_MIN_BUFFERS_FOR_CAPTURE);
+			if (ctrl)
+				min_disp_cnt = v4l2_ctrl_g_ctrl(ctrl);
+
+			*num_buffers = max(*num_buffers, min_disp_cnt);
+
+			if (*num_buffers > WAVE6_MAX_FBS)
+				*num_buffers = min_disp_cnt;
+		}
+	}
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type) && inst->state == VPU_INST_STATE_SEEK)
+		wave6_vpu_dec_destroy_instance(inst, true);
+
+	return 0;
+}
+
+static int wave6_vpu_dec_seek_header(struct vpu_instance *inst)
+{
+	struct dec_seq_info seq_info;
+	int ret;
+
+	memset(&seq_info, 0, sizeof(struct dec_seq_info));
+
+	ret = wave6_vpu_dec_issue_seq_init(inst);
+	if (ret) {
+		dev_err(inst->dev->dev, "failed wave6_vpu_dec_issue_seq_init %d\n", ret);
+		return ret;
+	}
+
+	ret = wave6_vpu_wait_interrupt(inst, W6_VPU_TIMEOUT);
+	if (ret) {
+		dev_err(inst->dev->dev, "failed to call vpu_wait_interrupt()\n");
+		return ret;
+	}
+
+	ret = wave6_vpu_dec_complete_seq_init(inst, &seq_info);
+	if (ret) {
+		dev_err(inst->dev->dev, "vpu_dec_complete_seq_init: %d, reason : 0x%x\n",
+			ret, seq_info.err_reason);
+		if ((seq_info.err_reason & WAVE6_SYSERR_NOT_SUPPORT) ||
+		    (seq_info.err_reason & WAVE6_SYSERR_NOT_SUPPORT_PROFILE)) {
+			ret = -EINVAL;
+		} else if ((seq_info.err_reason & HEVC_ETCERR_INIT_SEQ_SPS_NOT_FOUND) ||
+			   (seq_info.err_reason & AVC_ETCERR_INIT_SEQ_SPS_NOT_FOUND)) {
+			wave6_handle_skipped_frame(inst);
+			ret = 0;
+		}
+	} else {
+		wave6_vpu_dec_handle_source_change(inst);
+		inst->v4l2_fh.m2m_ctx->ignore_cap_streaming = false;
+		v4l2_m2m_set_dst_buffered(inst->v4l2_fh.m2m_ctx, false);
+		if (vb2_is_streaming(v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx)))
+			wave6_handle_last_frame(inst, NULL);
+	}
+
+	return ret;
+}
+
+static void wave6_vpu_dec_prepare_decode(struct vpu_instance *inst)
+{
+	int ret = 0;
+
+	wave6_handle_bitstream_buffer(inst);
+	if (inst->state == VPU_INST_STATE_OPEN) {
+		ret = wave6_vpu_dec_seek_header(inst);
+		if (ret) {
+			vb2_queue_error(v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx));
+			vb2_queue_error(v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx));
+		}
+	}
+}
+
+static void wave6_vpu_dec_buf_queue_src(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vpu_buffer *vpu_buf = wave6_to_vpu_buf(vbuf);
+	struct vpu_instance *inst = vb2_get_drv_priv(vb->vb2_queue);
+
+	dev_dbg(inst->dev->dev, "type %4d index %4d size[0] %4ld size[1] : %4ld | size[2] : %4ld\n",
+		vb->type, vb->index, vb2_plane_size(&vbuf->vb2_buf, 0),
+		vb2_plane_size(&vbuf->vb2_buf, 1), vb2_plane_size(&vbuf->vb2_buf, 2));
+
+	vbuf->sequence = inst->queued_src_buf_num++;
+	vpu_buf->ts_input = ktime_get_raw();
+
+	v4l2_m2m_buf_queue(inst->v4l2_fh.m2m_ctx, vbuf);
+}
+
+static void wave6_vpu_dec_buf_queue_dst(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vpu_instance *inst = vb2_get_drv_priv(vb->vb2_queue);
+
+	dev_dbg(inst->dev->dev, "type %4d index %4d size[0] %4ld size[1] : %4ld | size[2] : %4ld\n",
+		vb->type, vb->index, vb2_plane_size(&vbuf->vb2_buf, 0),
+		vb2_plane_size(&vbuf->vb2_buf, 1), vb2_plane_size(&vbuf->vb2_buf, 2));
+
+	inst->queued_dst_buf_num++;
+	if (inst->next_buf_last) {
+		wave6_handle_last_frame(inst, vbuf);
+		inst->next_buf_last = false;
+	} else {
+		v4l2_m2m_buf_queue(inst->v4l2_fh.m2m_ctx, vbuf);
+	}
+}
+
+static void wave6_vpu_dec_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vpu_buffer *vpu_buf = wave6_to_vpu_buf(vbuf);
+
+	vpu_buf->consumed = false;
+	vpu_buf->used = false;
+	vpu_buf->error = false;
+	if (V4L2_TYPE_IS_OUTPUT(vb->type))
+		wave6_vpu_dec_buf_queue_src(vb);
+	else
+		wave6_vpu_dec_buf_queue_dst(vb);
+}
+
+static int wave6_vpu_dec_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(q);
+	struct v4l2_pix_format_mplane *fmt;
+	int ret = 0;
+
+	trace_wave6_vpu_start_streaming(inst, V4L2_TYPE_IS_OUTPUT(q->type));
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+		fmt = &inst->src_fmt;
+		if (inst->state == VPU_INST_STATE_NONE) {
+			ret = wave6_vpu_dec_create_instance(inst);
+			if (!ret) {
+				inst->v4l2_fh.m2m_ctx->ignore_cap_streaming = true;
+				v4l2_m2m_set_dst_buffered(inst->v4l2_fh.m2m_ctx, true);
+			}
+		}
+		if (inst->state == VPU_INST_STATE_SEEK)
+			wave6_vpu_set_instance_state(inst, inst->state_in_seek);
+	} else {
+		fmt = &inst->dst_fmt;
+		if (inst->state == VPU_INST_STATE_INIT_SEQ) {
+			ret = wave6_vpu_dec_prepare_fb(inst);
+			if (ret)
+				goto exit;
+		}
+	}
+
+exit:
+	if (ret)
+		wave6_vpu_return_buffers(inst, q->type, VB2_BUF_STATE_QUEUED);
+
+	dev_dbg(inst->dev->dev, "[%d] %s %c%c%c%c %dx%d, %d buffers, ret = %d\n",
+		inst->id, V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture",
+		fmt->pixelformat,
+		fmt->pixelformat >> 8,
+		fmt->pixelformat >> 16,
+		fmt->pixelformat >> 24,
+		fmt->width, fmt->height, vb2_get_num_buffers(q), ret);
+
+	return ret;
+}
+
+static void wave6_vpu_dec_stop_streaming(struct vb2_queue *q)
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(q);
+	struct v4l2_m2m_ctx *m2m_ctx = inst->v4l2_fh.m2m_ctx;
+
+	trace_wave6_vpu_stop_streaming(inst, V4L2_TYPE_IS_OUTPUT(q->type));
+
+	dev_dbg(inst->dev->dev, "[%d] %s, input %d, decode %d error %d\n",
+		inst->id, V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture",
+		inst->queued_src_buf_num, inst->processed_buf_num, inst->error_buf_num);
+
+	if (inst->state == VPU_INST_STATE_NONE)
+		goto exit;
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+		wave6_vpu_reset_performance(inst);
+		inst->queued_src_buf_num = 0;
+		inst->processed_buf_num = 0;
+		inst->error_buf_num = 0;
+		inst->state_in_seek = inst->state;
+		v4l2_m2m_set_src_buffered(inst->v4l2_fh.m2m_ctx, false);
+		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_SEEK);
+		inst->sequence = 0;
+	} else {
+		if (v4l2_m2m_has_stopped(m2m_ctx))
+			v4l2_m2m_clear_state(m2m_ctx);
+
+		inst->eos = false;
+		inst->queued_dst_buf_num = 0;
+		inst->sequence = 0;
+		wave6_vpu_dec_flush_instance(inst);
+	}
+
+exit:
+	wave6_vpu_return_buffers(inst, q->type, VB2_BUF_STATE_ERROR);
+}
+
+static int wave6_vpu_dec_buf_init(struct vb2_buffer *vb)
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(vb->vb2_queue);
+	struct dec_seq_info *seq_info;
+	int i;
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->type))
+		return 0;
+
+	if (inst->state == VPU_INST_STATE_NONE)
+		return 0;
+
+	seq_info = &inst->codec_info->dec_info.seq_info;
+	if (seq_info->c_fmt_idc != C_FMT_IDC_YUV400)
+		return 0;
+
+	for (i = 0; i < inst->dst_fmt.num_planes; i++) {
+		void *vaddr = vb2_plane_vaddr(vb, i);
+
+		if (vaddr)
+			memset(vaddr, 0x80, vb2_plane_size(vb, i));
+	}
+
+	return 0;
+}
+
+static const struct vb2_ops wave6_vpu_dec_vb2_ops = {
+	.queue_setup = wave6_vpu_dec_queue_setup,
+	.buf_queue = wave6_vpu_dec_buf_queue,
+	.start_streaming = wave6_vpu_dec_start_streaming,
+	.stop_streaming = wave6_vpu_dec_stop_streaming,
+	.buf_init = wave6_vpu_dec_buf_init,
+};
+
+static void wave6_set_default_format(struct v4l2_pix_format_mplane *src_fmt,
+				     struct v4l2_pix_format_mplane *dst_fmt)
+{
+	const struct vpu_format *vpu_fmt;
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(0, VPU_FMT_TYPE_CODEC);
+	if (vpu_fmt) {
+		src_fmt->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		src_fmt->num_planes = vpu_fmt->num_planes;
+		wave6_update_pix_fmt(src_fmt,
+				     W6_DEF_DEC_PIC_WIDTH, W6_DEF_DEC_PIC_HEIGHT);
+	}
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(0, VPU_FMT_TYPE_RAW);
+	if (vpu_fmt) {
+		dst_fmt->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		dst_fmt->num_planes = vpu_fmt->num_planes;
+		wave6_update_pix_fmt_cap(dst_fmt,
+					 W6_DEF_DEC_PIC_WIDTH, W6_DEF_DEC_PIC_HEIGHT,
+					 true);
+	}
+}
+
+static int wave6_vpu_dec_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
+{
+	struct vpu_instance *inst = priv;
+	int ret;
+
+	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->ops = &wave6_vpu_dec_vb2_ops;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->buf_struct_size = sizeof(struct vpu_buffer);
+	src_vq->drv_priv = inst;
+	src_vq->lock = &inst->dev->dev_lock;
+	src_vq->dev = inst->dev->v4l2_dev.dev;
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->ops = &wave6_vpu_dec_vb2_ops;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->buf_struct_size = sizeof(struct vpu_buffer);
+	dst_vq->drv_priv = inst;
+	dst_vq->lock = &inst->dev->dev_lock;
+	dst_vq->dev = inst->dev->v4l2_dev.dev;
+	ret = vb2_queue_init(dst_vq);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct vpu_instance_ops wave6_vpu_dec_inst_ops = {
+	.prepare_process = wave6_vpu_dec_prepare_decode,
+	.start_process = wave6_vpu_dec_start_decode,
+	.finish_process = wave6_vpu_dec_finish_decode,
+};
+
+static int wave6_vpu_open_dec(struct file *filp)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct vpu_core_device *core = video_drvdata(filp);
+	struct vpu_instance *inst = NULL;
+	int ret;
+
+	inst = kzalloc_obj(*inst);
+	if (!inst)
+		return -ENOMEM;
+
+	init_completion(&inst->irq_done);
+	inst->dev = core;
+	inst->type = VPU_INST_TYPE_DEC;
+	inst->ops = &wave6_vpu_dec_inst_ops;
+
+	v4l2_fh_init(&inst->v4l2_fh, vdev);
+	filp->private_data = &inst->v4l2_fh;
+	v4l2_fh_add(&inst->v4l2_fh, filp);
+
+	inst->v4l2_fh.m2m_ctx =
+		v4l2_m2m_ctx_init(core->m2m_dev, inst, wave6_vpu_dec_queue_init);
+	if (IS_ERR(inst->v4l2_fh.m2m_ctx)) {
+		ret = PTR_ERR(inst->v4l2_fh.m2m_ctx);
+		goto free_inst;
+	}
+
+	v4l2_ctrl_handler_init(&inst->v4l2_ctrl_hdl, 10);
+	v4l2_ctrl_new_std(&inst->v4l2_ctrl_hdl, &wave6_vpu_dec_ctrl_ops,
+			  V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 1);
+	v4l2_ctrl_new_std(&inst->v4l2_ctrl_hdl, &wave6_vpu_dec_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY,
+			  0, 0, 1, 0);
+	v4l2_ctrl_new_std(&inst->v4l2_ctrl_hdl, &wave6_vpu_dec_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE,
+			  0, 1, 1, 0);
+	v4l2_ctrl_new_std_menu(&inst->v4l2_ctrl_hdl, &wave6_vpu_dec_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
+			       V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE,
+			       0, V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN);
+	v4l2_ctrl_new_std_menu(&inst->v4l2_ctrl_hdl, &wave6_vpu_dec_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+			       V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, 0,
+			       V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE);
+
+	if (inst->v4l2_ctrl_hdl.error) {
+		ret = -ENODEV;
+		goto err_m2m_release;
+	}
+
+	inst->v4l2_fh.ctrl_handler = &inst->v4l2_ctrl_hdl;
+	ret = v4l2_ctrl_handler_setup(&inst->v4l2_ctrl_hdl);
+	if (ret)
+		goto err_m2m_release;
+
+	wave6_set_default_format(&inst->src_fmt, &inst->dst_fmt);
+	inst->colorspace = V4L2_COLORSPACE_DEFAULT;
+	inst->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	inst->quantization = V4L2_QUANTIZATION_DEFAULT;
+	inst->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+	return 0;
+
+err_m2m_release:
+	v4l2_m2m_ctx_release(inst->v4l2_fh.m2m_ctx);
+	v4l2_ctrl_handler_free(&inst->v4l2_ctrl_hdl);
+free_inst:
+	v4l2_fh_del(&inst->v4l2_fh, filp);
+	v4l2_fh_exit(&inst->v4l2_fh);
+	kfree(inst);
+
+	return ret;
+}
+
+static int wave6_vpu_dec_release(struct file *filp)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(filp);
+
+	wave6_vpu_remove_dbgfs_file(inst);
+	v4l2_m2m_ctx_release(inst->v4l2_fh.m2m_ctx);
+
+	if (inst->state != VPU_INST_STATE_NONE)
+		wave6_vpu_dec_destroy_instance(inst, false);
+
+	v4l2_ctrl_handler_free(&inst->v4l2_ctrl_hdl);
+	v4l2_fh_del(&inst->v4l2_fh, filp);
+	v4l2_fh_exit(&inst->v4l2_fh);
+	kfree(inst);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations wave6_vpu_dec_fops = {
+	.owner = THIS_MODULE,
+	.open = wave6_vpu_open_dec,
+	.release = wave6_vpu_dec_release,
+	.unlocked_ioctl = video_ioctl2,
+	.poll = v4l2_m2m_fop_poll,
+	.mmap = v4l2_m2m_fop_mmap,
+};
+
+int wave6_vpu_dec_register_device(struct vpu_core_device *core)
+{
+	struct video_device *vdev_dec;
+	int ret;
+
+	vdev_dec = devm_kzalloc(core->v4l2_dev.dev, sizeof(*vdev_dec), GFP_KERNEL);
+	if (!vdev_dec)
+		return -ENOMEM;
+
+	core->video_dev_dec = vdev_dec;
+
+	strscpy(vdev_dec->name, VPU_DEC_DEV_NAME, sizeof(vdev_dec->name));
+	vdev_dec->fops = &wave6_vpu_dec_fops;
+	vdev_dec->ioctl_ops = &wave6_vpu_dec_ioctl_ops;
+	vdev_dec->release = video_device_release_empty;
+	vdev_dec->v4l2_dev = &core->v4l2_dev;
+	vdev_dec->vfl_dir = VFL_DIR_M2M;
+	vdev_dec->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+	vdev_dec->lock = &core->dev_lock;
+	video_set_drvdata(vdev_dec, core);
+
+	ret = video_register_device(vdev_dec, VFL_TYPE_VIDEO, -1);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+void wave6_vpu_dec_unregister_device(struct vpu_core_device *core)
+{
+	video_unregister_device(core->video_dev_dec);
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c
new file mode 100644
index 000000000000..825229f6fb05
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c
@@ -0,0 +1,2758 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - v4l2 stateful encoder interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/pm_runtime.h>
+#include "wave6-vpu-core.h"
+#include "wave6-vpu-dbg.h"
+#include "wave6-trace.h"
+
+#define VPU_ENC_DEV_NAME "C&M Wave6 VPU encoder"
+#define VPU_ENC_DRV_NAME "wave6-enc"
+
+static const struct vpu_format wave6_vpu_enc_fmt_list[2][23] = {
+	[VPU_FMT_TYPE_CODEC] = {
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_HEVC,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_H264,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+		},
+	},
+	[VPU_FMT_TYPE_RAW] = {
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV420,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV12,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV21,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV422P,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_422,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV16,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_422,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV61,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_422,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUYV,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_YUYV,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV24,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_YUV444_24BIT_PACKED,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV24,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_YUV444_24BIT,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV42,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_YUV444_24BIT,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV420M,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 3,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV12M,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 2,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV21M,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 2,
+			.fb_fmt = FORMAT_420,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_YUV422M,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 3,
+			.fb_fmt = FORMAT_422,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV16M,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 2,
+			.fb_fmt = FORMAT_422,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_NV61M,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 2,
+			.fb_fmt = FORMAT_422,
+			.endian = VPU_SOURCE_ENDIAN,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_CRCB_ORDER |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_P010,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_420_P10_16BIT_MSB,
+			.endian = VDI_128BIT_LE_BYTE_SWAP,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_10BIT |
+				 VPU_FMT_FLAG_SUPPORT_ROT_MIR,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_RGB24,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_RGB_24BIT_PACKED,
+			.endian = VPU_SOURCE_ENDIAN,
+			.csc_fmt_order = CSC_FMT_ORDER_RGB,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_RGB,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_ARGB32,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_RGB_32BIT_PACKED,
+			.endian = VPU_SOURCE_ENDIAN,
+			.csc_fmt_order = CSC_FMT_ORDER_ARGB,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_RGB,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_XRGB32,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_RGB_32BIT_PACKED,
+			.endian = VPU_SOURCE_ENDIAN,
+			.csc_fmt_order = CSC_FMT_ORDER_ARGB,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_RGB,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_RGBA32,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_RGB_32BIT_PACKED,
+			.endian = VPU_SOURCE_ENDIAN,
+			.csc_fmt_order = CSC_FMT_ORDER_RGBA,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_RGB,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_RGBX32,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_RGB_32BIT_PACKED,
+			.endian = VPU_SOURCE_ENDIAN,
+			.csc_fmt_order = CSC_FMT_ORDER_RGBA,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_RGB,
+		},
+		{
+			.v4l2_pix_fmt = V4L2_PIX_FMT_ARGB2101010,
+			.max_width = W6_MAX_ENC_PIC_WIDTH,
+			.min_width = W6_MIN_ENC_PIC_WIDTH,
+			.max_height = W6_MAX_ENC_PIC_HEIGHT,
+			.min_height = W6_MIN_ENC_PIC_HEIGHT,
+			.num_planes = 1,
+			.fb_fmt = FORMAT_RGB_P10_32BIT_PACKED,
+			.endian = VDI_128BIT_LE_WORD_BYTE_SWAP,
+			.csc_fmt_order = CSC_FMT_ORDER_ARGB,
+			.flags = VPU_FMT_FLAG_CBCR_INTERLEAVED |
+				 VPU_FMT_FLAG_RGB |
+				 VPU_FMT_FLAG_10BIT,
+		},
+	}
+};
+
+static const struct vpu_format *wave6_find_vpu_fmt(unsigned int v4l2_pix_fmt,
+						   enum vpu_fmt_type type)
+{
+	unsigned int index;
+
+	for (index = 0; index < ARRAY_SIZE(wave6_vpu_enc_fmt_list[type]); index++) {
+		if (wave6_vpu_enc_fmt_list[type][index].v4l2_pix_fmt == v4l2_pix_fmt)
+			return &wave6_vpu_enc_fmt_list[type][index];
+	}
+
+	return NULL;
+}
+
+static const struct vpu_format *wave6_find_vpu_fmt_by_idx(unsigned int idx,
+							  enum vpu_fmt_type type)
+{
+	if (idx >= ARRAY_SIZE(wave6_vpu_enc_fmt_list[type]))
+		return NULL;
+
+	if (!wave6_vpu_enc_fmt_list[type][idx].v4l2_pix_fmt)
+		return NULL;
+
+	return &wave6_vpu_enc_fmt_list[type][idx];
+}
+
+static u32 wave6_cpb_size_msec(u32 cpb_size_kb, u32 bitrate)
+{
+	u64 cpb_size_bit;
+	u64 cpb_size_msec;
+
+	cpb_size_bit = (u64)cpb_size_kb * 1000 * BITS_PER_BYTE;
+	cpb_size_msec = (cpb_size_bit * 1000) / bitrate;
+
+	if (cpb_size_msec < MIN_CPB_SIZE || cpb_size_msec > MAX_CPB_SIZE)
+		cpb_size_msec = DEFAULT_CPB_SIZE;
+
+	return cpb_size_msec;
+}
+
+static void wave6_vpu_enc_release_fb(struct vpu_instance *inst)
+{
+	int i;
+
+	for (i = 0; i < WAVE6_MAX_FBS; i++) {
+		wave6_vdi_free_dma(&inst->frame_vbuf[i]);
+		memset(&inst->frame_buf[i], 0, sizeof(struct frame_buffer));
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_MV_COL][i]);
+		wave6_vdi_free_dma(&inst->aux_vbuf[AUX_BUF_SUB_SAMPLE][i]);
+	}
+}
+
+static void wave6_vpu_enc_destroy_instance(struct vpu_instance *inst, bool remove_dbgfs)
+{
+	u32 fail_res = 0;
+	int ret;
+
+	wave6_vpu_disable_instance(inst);
+
+	if (remove_dbgfs)
+		wave6_vpu_remove_dbgfs_file(inst);
+
+	ret = wave6_vpu_enc_close(inst, &fail_res);
+	if (ret) {
+		dev_err(inst->dev->dev, "failed destroy instance: %d (%d)\n",
+			ret, fail_res);
+	}
+
+	scoped_guard(spinlock, &inst->dev->inst_lock)
+		list_del_init(&inst->list);
+	wave6_vpu_enc_release_fb(inst);
+	wave6_vdi_free_dma(&inst->ar_vbuf);
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_NONE);
+
+	if (!pm_runtime_suspended(inst->dev->dev))
+		pm_runtime_put_sync(inst->dev->dev);
+}
+
+static struct vb2_v4l2_buffer *wave6_get_valid_src_buf(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *vb2_v4l2_buf;
+	struct v4l2_m2m_buffer *v4l2_m2m_buf;
+	struct vpu_buffer *vpu_buf = NULL;
+
+	v4l2_m2m_for_each_src_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
+		vb2_v4l2_buf = &v4l2_m2m_buf->vb;
+		vpu_buf = wave6_to_vpu_buf(vb2_v4l2_buf);
+
+		if (!vpu_buf->consumed) {
+			dev_dbg(inst->dev->dev, "no consumed src idx : %d\n",
+				vb2_v4l2_buf->vb2_buf.index);
+			return vb2_v4l2_buf;
+		}
+	}
+
+	return NULL;
+}
+
+static struct vb2_v4l2_buffer *wave6_get_valid_dst_buf(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *vb2_v4l2_buf;
+	struct v4l2_m2m_buffer *v4l2_m2m_buf;
+	struct vpu_buffer *vpu_buf;
+
+	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
+		vb2_v4l2_buf = &v4l2_m2m_buf->vb;
+		vpu_buf = wave6_to_vpu_buf(vb2_v4l2_buf);
+
+		if (!vpu_buf->consumed) {
+			dev_dbg(inst->dev->dev, "no consumed dst idx : %d\n",
+				vb2_v4l2_buf->vb2_buf.index);
+			return vb2_v4l2_buf;
+		}
+	}
+
+	return NULL;
+}
+
+static void wave6_set_csc(struct vpu_instance *inst, struct enc_param *pic_param)
+{
+	const struct vpu_format *vpu_fmt;
+	bool is_10bit;
+
+	vpu_fmt = wave6_find_vpu_fmt(inst->src_fmt.pixelformat, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt)
+		return;
+
+	if (!(vpu_fmt->flags & VPU_FMT_FLAG_RGB))
+		return;
+
+	is_10bit = !!(vpu_fmt->flags & VPU_FMT_FLAG_10BIT);
+	pic_param->csc.fmt_order = vpu_fmt->csc_fmt_order;
+
+	if (inst->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT ||
+	    inst->ycbcr_enc == V4L2_YCBCR_ENC_601) {
+		if (inst->quantization == V4L2_QUANTIZATION_FULL_RANGE) {
+			/*
+			 * Y   0.299(R)    0.587(G)    0.114(B)
+			 * Cb -0.16874(R) -0.33126(G)  0.5(B)
+			 * Cr  0.5(R)     -0.41869(G) -0.08131(B)
+			 */
+			pic_param->csc.coef_ry = 0x099;
+			pic_param->csc.coef_gy = 0x12d;
+			pic_param->csc.coef_by = 0x03a;
+			pic_param->csc.coef_rcb = 0xffffffaa;
+			pic_param->csc.coef_gcb = 0xffffff56;
+			pic_param->csc.coef_bcb = 0x100;
+			pic_param->csc.coef_rcr = 0x100;
+			pic_param->csc.coef_gcr = 0xffffff2a;
+			pic_param->csc.coef_bcr = 0xffffffd6;
+			pic_param->csc.offset_y = 0x0;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		} else {
+			/*
+			 * Y   0.258(R)   0.504(G)   0.098(B)
+			 * Cb -0.1484(R) -0.2891(G)  0.4375(B)
+			 * Cr  0.4375(R) -0.3672(G) -0.0703(B)
+			 */
+			pic_param->csc.coef_ry = 0x084;
+			pic_param->csc.coef_gy = 0x102;
+			pic_param->csc.coef_by = 0x032;
+			pic_param->csc.coef_rcb = 0xffffffb4;
+			pic_param->csc.coef_gcb = 0xffffff6c;
+			pic_param->csc.coef_bcb = 0x0e0;
+			pic_param->csc.coef_rcr = 0x0e0;
+			pic_param->csc.coef_gcr = 0xffffff44;
+			pic_param->csc.coef_bcr = 0xffffffdc;
+			pic_param->csc.offset_y = (is_10bit) ? 0x40 : 0x10;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		}
+	} else if (inst->ycbcr_enc == V4L2_YCBCR_ENC_709) {
+		if (inst->quantization == V4L2_QUANTIZATION_FULL_RANGE) {
+			/*
+			 * Y   0.2126(R)   0.7152(G)   0.0722(B)
+			 * Cb -0.11457(R) -0.38543(G)  0.5(B)
+			 * Cr  0.5(R)     -0.45415(G) -0.04585(B)
+			 */
+			pic_param->csc.coef_ry = 0x06d;
+			pic_param->csc.coef_gy = 0x16e;
+			pic_param->csc.coef_by = 0x025;
+			pic_param->csc.coef_rcb = 0xffffffc5;
+			pic_param->csc.coef_gcb = 0xffffff3b;
+			pic_param->csc.coef_bcb = 0x100;
+			pic_param->csc.coef_rcr = 0x100;
+			pic_param->csc.coef_gcr = 0xffffff17;
+			pic_param->csc.coef_bcr = 0xffffffe9;
+			pic_param->csc.offset_y = 0x0;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		} else {
+			pic_param->csc.coef_ry = 0x05e;
+			pic_param->csc.coef_gy = 0x13b;
+			pic_param->csc.coef_by = 0x020;
+			pic_param->csc.coef_rcb = 0xffffffcc;
+			pic_param->csc.coef_gcb = 0xffffff53;
+			pic_param->csc.coef_bcb = 0x0e1;
+			pic_param->csc.coef_rcr = 0x0e1;
+			pic_param->csc.coef_gcr = 0xffffff34;
+			pic_param->csc.coef_bcr = 0xffffffeb;
+			pic_param->csc.offset_y = (is_10bit) ? 0x40 : 0x10;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		}
+	} else if (inst->ycbcr_enc == V4L2_YCBCR_ENC_BT2020) {
+		if (inst->quantization == V4L2_QUANTIZATION_FULL_RANGE) {
+			/*
+			 * Y   0.2627(R)   0.678(G)    0.0593(B)
+			 * Cb -0.13963(R) -0.36037(G)  0.5(B)
+			 * Cr  0.5(R)     -0.45979(G) -0.04021(B)
+			 */
+			pic_param->csc.coef_ry = 0x087;
+			pic_param->csc.coef_gy = 0x15b;
+			pic_param->csc.coef_by = 0x01e;
+			pic_param->csc.coef_rcb = 0xffffffb9;
+			pic_param->csc.coef_gcb = 0xffffff47;
+			pic_param->csc.coef_bcb = 0x100;
+			pic_param->csc.coef_rcr = 0x100;
+			pic_param->csc.coef_gcr = 0xffffff15;
+			pic_param->csc.coef_bcr = 0xffffffeb;
+			pic_param->csc.offset_y = 0x0;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		} else {
+			pic_param->csc.coef_ry = 0x074;
+			pic_param->csc.coef_gy = 0x12a;
+			pic_param->csc.coef_by = 0x01a;
+			pic_param->csc.coef_rcb = 0xffffffc1;
+			pic_param->csc.coef_gcb = 0xffffff5e;
+			pic_param->csc.coef_bcb = 0x0e1;
+			pic_param->csc.coef_rcr = 0x0e1;
+			pic_param->csc.coef_gcr = 0xffffff31;
+			pic_param->csc.coef_bcr = 0xffffffee;
+			pic_param->csc.offset_y = (is_10bit) ? 0x40 : 0x10;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		}
+	} else if (inst->ycbcr_enc == V4L2_YCBCR_ENC_SMPTE240M) {
+		if (inst->quantization == V4L2_QUANTIZATION_FULL_RANGE) {
+			/*
+			 * Y   0.2122(R)  0.7013(G)  0.0865(B)
+			 * Cb -0.1161(R) -0.3839(G)  0.5(B)
+			 * Cr  0.5(R)    -0.4451(G) -0.0549(B)
+			 */
+			pic_param->csc.coef_ry = 0x06d;
+			pic_param->csc.coef_gy = 0x167;
+			pic_param->csc.coef_by = 0x02c;
+			pic_param->csc.coef_rcb = 0xffffffc5;
+			pic_param->csc.coef_gcb = 0xffffff3b;
+			pic_param->csc.coef_bcb = 0x100;
+			pic_param->csc.coef_rcr = 0x100;
+			pic_param->csc.coef_gcr = 0xffffff1c;
+			pic_param->csc.coef_bcr = 0xffffffe4;
+			pic_param->csc.offset_y = 0x0;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		} else {
+			pic_param->csc.coef_ry = 0x05d;
+			pic_param->csc.coef_gy = 0x134;
+			pic_param->csc.coef_by = 0x026;
+			pic_param->csc.coef_rcb = 0xffffffcc;
+			pic_param->csc.coef_gcb = 0xffffff53;
+			pic_param->csc.coef_bcb = 0x0e1;
+			pic_param->csc.coef_rcr = 0x0e1;
+			pic_param->csc.coef_gcr = 0xffffff38;
+			pic_param->csc.coef_bcr = 0xffffffe7;
+			pic_param->csc.offset_y = (is_10bit) ? 0x40 : 0x10;
+			pic_param->csc.offset_cb = (is_10bit) ? 0x200 : 0x80;
+			pic_param->csc.offset_cr = (is_10bit) ? 0x200 : 0x80;
+		}
+	} else if (inst->ycbcr_enc == V4L2_YCBCR_ENC_XV601) {
+		if (inst->quantization == V4L2_QUANTIZATION_LIM_RANGE) {
+			/*
+			 * Y   0.2558(R)  0.5021(G)  0.0975(B)
+			 * Cb -0.1476(R) -0.2899(G)  0.4375(B)
+			 * Cr  0.4375(R) -0.3664(G) -0.0711(B)
+			 */
+			pic_param->csc.coef_ry = 0x083;
+			pic_param->csc.coef_gy = 0x101;
+			pic_param->csc.coef_by = 0x032;
+			pic_param->csc.coef_rcb = 0xffffffb4;
+			pic_param->csc.coef_gcb = 0xffffff6c;
+			pic_param->csc.coef_bcb = 0x0e0;
+			pic_param->csc.coef_rcr = 0x0e0;
+			pic_param->csc.coef_gcr = 0xffffff44;
+			pic_param->csc.coef_bcr = 0xffffffdc;
+			pic_param->csc.offset_y = (is_10bit) ? 0x40 : 0x10;
+			pic_param->csc.offset_cb = 0x0;
+			pic_param->csc.offset_cr = 0x0;
+		}
+	} else if (inst->ycbcr_enc == V4L2_YCBCR_ENC_XV709) {
+		if (inst->quantization == V4L2_QUANTIZATION_LIM_RANGE) {
+			/*
+			 * Y   0.1819(R)  0.6118(G)  0.0618(B)
+			 * Cb -0.1003(R) -0.3372(G)  0.4375(B)
+			 * Cr  0.4375(R) -0.3974(G) -0.0401(B)
+			 */
+			pic_param->csc.coef_ry = 0x05d;
+			pic_param->csc.coef_gy = 0x139;
+			pic_param->csc.coef_by = 0x020;
+			pic_param->csc.coef_rcb = 0xffffffcd;
+			pic_param->csc.coef_gcb = 0xffffff53;
+			pic_param->csc.coef_bcb = 0x0e0;
+			pic_param->csc.coef_rcr = 0x0e0;
+			pic_param->csc.coef_gcr = 0xffffff35;
+			pic_param->csc.coef_bcr = 0xffffffeb;
+			pic_param->csc.offset_y = (is_10bit) ? 0x40 : 0x10;
+			pic_param->csc.offset_cb = 0x0;
+			pic_param->csc.offset_cr = 0x0;
+		}
+	}
+}
+
+static void wave6_update_crop_info(struct vpu_instance *inst,
+				   u32 left, u32 top, u32 width, u32 height)
+{
+	u32 enc_pic_width, enc_pic_height;
+
+	inst->crop.left = left;
+	inst->crop.top = top;
+	inst->crop.width = width;
+	inst->crop.height = height;
+
+	inst->codec_rect.left = round_down(left, W6_ENC_CROP_X_POS_STEP);
+	inst->codec_rect.top = round_down(top, W6_ENC_CROP_Y_POS_STEP);
+
+	enc_pic_width = width + left - inst->codec_rect.left;
+	inst->codec_rect.width = round_up(enc_pic_width, W6_ENC_PIC_SIZE_STEP);
+
+	enc_pic_height = height + top - inst->codec_rect.top;
+	inst->codec_rect.height = round_up(enc_pic_height, W6_ENC_PIC_SIZE_STEP);
+}
+
+static int wave6_allocate_aux_buffer(struct vpu_instance *inst,
+				     enum aux_buffer_type type,
+				     unsigned int width,
+				     unsigned int height,
+				     int num)
+{
+	struct aux_buffer_size_info size_info;
+	unsigned int size;
+	int i, ret;
+
+	size_info.width = width;
+	size_info.height = height;
+	size_info.type = type;
+
+	ret = wave6_vpu_enc_get_aux_buffer_size(inst, size_info, &size);
+	if (ret) {
+		dev_err(inst->dev->dev, "%s: Get size fail (type %d)\n", __func__, type);
+		return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		inst->aux_vbuf[type][i].size = size;
+		ret = wave6_vdi_alloc_dma(inst->dev->dev, &inst->aux_vbuf[type][i]);
+		if (ret) {
+			dev_err(inst->dev->dev, "%s: Alloc fail (type %d)\n", __func__, type);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void wave6_update_frame_buf_addr(struct vpu_instance *inst,
+					struct frame_buffer *frame_buf)
+{
+	const struct v4l2_format_info *fmt_info;
+	u32 stride = inst->src_fmt.plane_fmt[0].bytesperline;
+	u32 offset;
+
+	fmt_info = v4l2_format_info(inst->src_fmt.pixelformat);
+	if (!fmt_info)
+		return;
+
+	offset = inst->codec_rect.top * stride + inst->codec_rect.left * fmt_info->bpp[0];
+	frame_buf->buf_y += offset;
+
+	stride = DIV_ROUND_UP(stride, fmt_info->bpp[0]) * fmt_info->bpp[1];
+	offset = inst->codec_rect.top * stride / fmt_info->vdiv / fmt_info->hdiv
+			+ inst->codec_rect.left * fmt_info->bpp[1] / fmt_info->hdiv;
+	frame_buf->buf_cb += offset;
+	frame_buf->buf_cr += offset;
+}
+
+static int wave6_update_seq_param(struct vpu_instance *inst)
+{
+	struct enc_seq_info seq_info;
+	bool changed = false;
+	int ret;
+
+	ret = wave6_vpu_enc_issue_seq_change(inst, &changed);
+	if (ret) {
+		dev_err(inst->dev->dev, "seq change fail %d\n", ret);
+		return ret;
+	}
+
+	if (!changed)
+		return 0;
+
+	ret = wave6_vpu_wait_interrupt(inst, W6_VPU_TIMEOUT);
+	if (ret) {
+		dev_err(inst->dev->dev, "seq change timeout\n");
+		return ret;
+	}
+
+	ret = wave6_vpu_enc_complete_seq_init(inst, &seq_info);
+	if (ret) {
+		dev_err(inst->dev->dev, "seq change error\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void wave6_calc_source_frame_info(struct vpu_instance *inst,
+					 u32 *stride, u32 *luma_size,
+					 u32 *stride_c, u32 *chroma_size)
+{
+	const struct v4l2_format_info *info = v4l2_format_info(inst->src_fmt.pixelformat);
+	u32 height = inst->src_fmt.height;
+
+	*stride = inst->src_fmt.plane_fmt[0].bytesperline;
+	*luma_size = *stride * height;
+
+	if (WARN_ON_ONCE(!info) || info->comp_planes < 2) {
+		*stride_c = 0;
+		*chroma_size = 0;
+		return;
+	}
+
+	*stride_c = DIV_ROUND_UP(*stride, info->bpp[0]);
+	*stride_c = DIV_ROUND_UP(*stride_c, info->hdiv) * info->bpp[1];
+	*chroma_size = *stride_c * DIV_ROUND_UP(height, info->vdiv);
+}
+
+static int wave6_vpu_enc_start_encode(struct vpu_instance *inst)
+{
+	int ret = -EINVAL;
+	struct vb2_v4l2_buffer *src_buf = NULL;
+	struct vb2_v4l2_buffer *dst_buf = NULL;
+	struct vpu_buffer *src_vbuf = NULL;
+	struct vpu_buffer *dst_vbuf = NULL;
+	struct frame_buffer frame_buf;
+	struct enc_param pic_param;
+	u32 stride;
+	u32 luma_size;
+	u32 stride_c;
+	u32 chroma_size;
+	u32 fail_res = 0;
+
+	memset(&pic_param, 0, sizeof(struct enc_param));
+	memset(&frame_buf, 0, sizeof(struct frame_buffer));
+
+	wave6_calc_source_frame_info(inst, &stride, &luma_size,
+				     &stride_c, &chroma_size);
+
+	ret = wave6_update_seq_param(inst);
+	if (ret)
+		goto exit;
+
+	src_buf = wave6_get_valid_src_buf(inst);
+	dst_buf = wave6_get_valid_dst_buf(inst);
+
+	if (!dst_buf) {
+		dev_dbg(inst->dev->dev, "no valid dst buf\n");
+		goto exit;
+	}
+
+	dst_vbuf = wave6_to_vpu_buf(dst_buf);
+	pic_param.pic_stream_buffer_addr = wave6_get_dma_addr(dst_buf, 0);
+	pic_param.pic_stream_buffer_size = vb2_plane_size(&dst_buf->vb2_buf, 0);
+	if (!src_buf) {
+		dev_dbg(inst->dev->dev, "no valid src buf\n");
+		if (inst->state == VPU_INST_STATE_STOP)
+			pic_param.src_end = true;
+		else
+			goto exit;
+	} else {
+		src_vbuf = wave6_to_vpu_buf(src_buf);
+		if (inst->src_fmt.num_planes == 1) {
+			frame_buf.buf_y = wave6_get_dma_addr(src_buf, 0);
+			frame_buf.buf_cb = frame_buf.buf_y + luma_size;
+			frame_buf.buf_cr = frame_buf.buf_cb + chroma_size;
+		} else if (inst->src_fmt.num_planes == 2) {
+			frame_buf.buf_y = wave6_get_dma_addr(src_buf, 0);
+			frame_buf.buf_cb = wave6_get_dma_addr(src_buf, 1);
+			frame_buf.buf_cr = frame_buf.buf_cb + chroma_size;
+		} else if (inst->src_fmt.num_planes == 3) {
+			frame_buf.buf_y = wave6_get_dma_addr(src_buf, 0);
+			frame_buf.buf_cb = wave6_get_dma_addr(src_buf, 1);
+			frame_buf.buf_cr = wave6_get_dma_addr(src_buf, 2);
+		}
+		wave6_update_frame_buf_addr(inst, &frame_buf);
+		frame_buf.stride = stride;
+		frame_buf.stride_c = stride_c;
+		pic_param.src_idx = src_buf->vb2_buf.index;
+		if (src_vbuf->force_key_frame || inst->error_recovery) {
+			pic_param.force_pic = true;
+			pic_param.force_pic_type = ENC_FORCE_PIC_TYPE_IDR;
+			inst->error_recovery = false;
+		}
+		if (src_vbuf->force_frame_qp) {
+			pic_param.force_pic_qp = true;
+			pic_param.force_pic_qp_i = src_vbuf->force_i_frame_qp;
+			pic_param.force_pic_qp_p = src_vbuf->force_p_frame_qp;
+			pic_param.force_pic_qp_b = src_vbuf->force_b_frame_qp;
+		}
+		src_vbuf->ts_start = ktime_get_raw();
+	}
+
+	pic_param.source_frame = &frame_buf;
+	wave6_set_csc(inst, &pic_param);
+
+	if (src_vbuf)
+		src_vbuf->consumed = true;
+	if (dst_vbuf) {
+		dst_vbuf->consumed = true;
+		dst_vbuf->used = true;
+	}
+
+	trace_wave6_vpu_enc_pic(inst, &pic_param);
+
+	ret = wave6_vpu_enc_start_one_frame(inst, &pic_param, &fail_res);
+	if (ret) {
+		dev_err(inst->dev->dev, "[%d] %s: fail %d\n", inst->id, __func__, ret);
+		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_STOP);
+
+		dst_buf = v4l2_m2m_dst_buf_remove(inst->v4l2_fh.m2m_ctx);
+		if (dst_buf) {
+			dst_buf->sequence = inst->sequence;
+			v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
+		}
+
+		src_buf = v4l2_m2m_src_buf_remove(inst->v4l2_fh.m2m_ctx);
+		if (src_buf) {
+			v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+			inst->sequence++;
+			inst->processed_buf_num++;
+			inst->error_buf_num++;
+		}
+	} else {
+		dev_dbg(inst->dev->dev, "%s: success\n", __func__);
+	}
+
+exit:
+	return ret;
+}
+
+static void wave6_handle_encoded_frame(struct vpu_instance *inst,
+				       struct enc_output_info *info)
+{
+	struct vb2_v4l2_buffer *src_buf;
+	struct vb2_v4l2_buffer *dst_buf;
+	struct vpu_buffer *vpu_buf;
+	struct vpu_buffer *dst_vpu_buf;
+	enum vb2_buffer_state state;
+
+	state = info->encoding_success ? VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR;
+
+	src_buf = v4l2_m2m_src_buf_remove_by_idx(inst->v4l2_fh.m2m_ctx,
+						 info->enc_src_idx);
+	if (!src_buf) {
+		dev_err(inst->dev->dev, "[%d] encoder can't find src buffer\n", inst->id);
+		return;
+	}
+
+	vpu_buf = wave6_to_vpu_buf(src_buf);
+	if (!vpu_buf || !vpu_buf->consumed) {
+		dev_err(inst->dev->dev, "[%d] src buffer is not consumed\n", inst->id);
+		return;
+	}
+
+	dst_buf = wave6_get_dst_buf_by_addr(inst, info->bitstream_buffer);
+	if (!dst_buf) {
+		dev_err(inst->dev->dev, "[%d] encoder can't find dst buffer\n", inst->id);
+		return;
+	}
+
+	dst_vpu_buf = wave6_to_vpu_buf(dst_buf);
+
+	dst_vpu_buf->average_qp = info->avg_ctu_qp;
+	dst_vpu_buf->ts_input = vpu_buf->ts_input;
+	dst_vpu_buf->ts_start = vpu_buf->ts_start;
+	dst_vpu_buf->ts_finish = ktime_get_raw();
+	dst_vpu_buf->hw_time = wave6_vpu_cycle_to_ns(inst->dev, info->cycle.frame_cycle);
+	dst_vpu_buf->ts_output = ktime_get_raw();
+	wave6_vpu_handle_performance(inst, dst_vpu_buf);
+
+	v4l2_m2m_buf_copy_metadata(src_buf, dst_buf);
+	v4l2_m2m_buf_done(src_buf, state);
+
+	vb2_set_plane_payload(&dst_buf->vb2_buf, 0, info->bitstream_size);
+	dst_buf->sequence = inst->sequence++;
+	dst_buf->field = V4L2_FIELD_NONE;
+	if (info->pic_type == PIC_TYPE_I)
+		dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME;
+	else if (info->pic_type == PIC_TYPE_P)
+		dst_buf->flags |= V4L2_BUF_FLAG_PFRAME;
+	else if (info->pic_type == PIC_TYPE_B)
+		dst_buf->flags |= V4L2_BUF_FLAG_BFRAME;
+
+	v4l2_m2m_dst_buf_remove_by_buf(inst->v4l2_fh.m2m_ctx, dst_buf);
+	if (state == VB2_BUF_STATE_ERROR) {
+		dev_dbg(inst->dev->dev, "[%d] error frame %d\n", inst->id, inst->sequence);
+		inst->error_recovery = true;
+		inst->error_buf_num++;
+	}
+	v4l2_m2m_buf_done(dst_buf, state);
+	inst->processed_buf_num++;
+}
+
+static void wave6_handle_last_frame(struct vpu_instance *inst,
+				    dma_addr_t addr)
+{
+	struct vb2_v4l2_buffer *dst_buf;
+
+	dst_buf = wave6_get_dst_buf_by_addr(inst, addr);
+	if (!dst_buf)
+		return;
+
+	vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0);
+	dst_buf->field = V4L2_FIELD_NONE;
+	dst_buf->flags |= V4L2_BUF_FLAG_LAST;
+	v4l2_m2m_dst_buf_remove_by_buf(inst->v4l2_fh.m2m_ctx, dst_buf);
+	v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
+
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_PIC_RUN);
+
+	dev_dbg(inst->dev->dev, "[%d] eos\n", inst->id);
+	inst->eos = true;
+
+	v4l2_m2m_set_src_buffered(inst->v4l2_fh.m2m_ctx, false);
+}
+
+static void wave6_vpu_enc_finish_encode(struct vpu_instance *inst, bool error)
+{
+	int ret;
+	struct enc_output_info info;
+
+	if (error) {
+		vb2_queue_error(v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx));
+		vb2_queue_error(v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx));
+
+		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_STOP);
+		inst->eos = true;
+
+		goto finish_encode;
+	}
+
+	memset(&info, 0, sizeof(info));
+
+	ret = wave6_vpu_enc_get_output_info(inst, &info);
+	if (ret) {
+		dev_dbg(inst->dev->dev, "vpu_enc_get_output_info fail %d\n", ret);
+		goto finish_encode;
+	}
+
+	trace_wave6_vpu_enc_done(inst, &info);
+
+	if (info.enc_src_idx >= 0 && info.recon_frame_index >= 0)
+		wave6_handle_encoded_frame(inst, &info);
+	else if (info.recon_frame_index == RECON_IDX_FLAG_ENC_END)
+		wave6_handle_last_frame(inst, info.bitstream_buffer);
+
+finish_encode:
+	wave6_vpu_finish_job(inst);
+}
+
+static int wave6_vpu_enc_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, VPU_ENC_DRV_NAME, sizeof(cap->driver));
+	strscpy(cap->card, VPU_ENC_DRV_NAME, sizeof(cap->card));
+	strscpy(cap->bus_info, "platform:" VPU_ENC_DRV_NAME, sizeof(cap->bus_info));
+
+	return 0;
+}
+
+static int wave6_vpu_enc_enum_framesizes(struct file *f, void *fh, struct v4l2_frmsizeenum *fsize)
+{
+	const struct vpu_format *vpu_fmt;
+
+	if (fsize->index)
+		return -EINVAL;
+
+	vpu_fmt = wave6_find_vpu_fmt(fsize->pixel_format, VPU_FMT_TYPE_CODEC);
+	if (!vpu_fmt) {
+		vpu_fmt = wave6_find_vpu_fmt(fsize->pixel_format, VPU_FMT_TYPE_RAW);
+		if (!vpu_fmt)
+			return -EINVAL;
+	}
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = vpu_fmt->min_width;
+	fsize->stepwise.max_width = vpu_fmt->max_width;
+	fsize->stepwise.step_width = W6_ENC_PIC_SIZE_STEP;
+	fsize->stepwise.min_height = vpu_fmt->min_height;
+	fsize->stepwise.max_height = vpu_fmt->max_height;
+	fsize->stepwise.step_height = W6_ENC_PIC_SIZE_STEP;
+
+	return 0;
+}
+
+static int wave6_vpu_enc_enum_fmt_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	const struct vpu_format *vpu_fmt;
+
+	dev_dbg(inst->dev->dev, "index : %d\n", f->index);
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(f->index, VPU_FMT_TYPE_CODEC);
+	if (!vpu_fmt)
+		return -EINVAL;
+
+	f->pixelformat = vpu_fmt->v4l2_pix_fmt;
+	f->flags = 0;
+
+	return 0;
+}
+
+static int wave6_vpu_enc_try_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vpu_format *vpu_fmt;
+	int width, height;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	if (!V4L2_TYPE_IS_CAPTURE(f->type))
+		return -EINVAL;
+
+	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_CODEC);
+	if (!vpu_fmt) {
+		width = inst->dst_fmt.width;
+		height = inst->dst_fmt.height;
+		pix_mp->pixelformat = inst->dst_fmt.pixelformat;
+		pix_mp->num_planes = inst->dst_fmt.num_planes;
+	} else {
+		width = pix_mp->width;
+		height = pix_mp->height;
+		pix_mp->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		pix_mp->num_planes = vpu_fmt->num_planes;
+	}
+
+	wave6_update_pix_fmt(pix_mp, width, height);
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_enc_s_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	int i, ret;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	ret = wave6_vpu_enc_try_fmt_cap(file, fh, f);
+	if (ret)
+		return ret;
+
+	if (vb2_is_busy(v4l2_m2m_get_vq(inst->v4l2_fh.m2m_ctx, f->type)))
+		return -EBUSY;
+
+	inst->std = wave6_to_codec_std(inst->type, pix_mp->pixelformat);
+	if (inst->std == STD_UNKNOWN) {
+		dev_err(inst->dev->dev, "unsupported pixelformat: %.4s\n",
+			(char *)&pix_mp->pixelformat);
+		return -EINVAL;
+	}
+
+	inst->dst_fmt.width = pix_mp->width;
+	inst->dst_fmt.height = pix_mp->height;
+	inst->dst_fmt.pixelformat = pix_mp->pixelformat;
+	inst->dst_fmt.field = pix_mp->field;
+	inst->dst_fmt.flags = pix_mp->flags;
+	inst->dst_fmt.num_planes = pix_mp->num_planes;
+	for (i = 0; i < inst->dst_fmt.num_planes; i++) {
+		inst->dst_fmt.plane_fmt[i].bytesperline = pix_mp->plane_fmt[i].bytesperline;
+		inst->dst_fmt.plane_fmt[i].sizeimage = pix_mp->plane_fmt[i].sizeimage;
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_enc_g_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	int i;
+
+	pix_mp->width = inst->dst_fmt.width;
+	pix_mp->height = inst->dst_fmt.height;
+	pix_mp->pixelformat = inst->dst_fmt.pixelformat;
+	pix_mp->field = inst->dst_fmt.field;
+	pix_mp->flags = inst->dst_fmt.flags;
+	pix_mp->num_planes = inst->dst_fmt.num_planes;
+	for (i = 0; i < pix_mp->num_planes; i++) {
+		pix_mp->plane_fmt[i].bytesperline = inst->dst_fmt.plane_fmt[i].bytesperline;
+		pix_mp->plane_fmt[i].sizeimage = inst->dst_fmt.plane_fmt[i].sizeimage;
+	}
+
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_enc_enum_fmt_out(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	const struct vpu_format *vpu_fmt;
+
+	dev_dbg(inst->dev->dev, "%s: index %d\n", __func__, f->index);
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(f->index, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt)
+		return -EINVAL;
+
+	f->pixelformat = vpu_fmt->v4l2_pix_fmt;
+	f->flags = 0;
+
+	return 0;
+}
+
+static int wave6_vpu_enc_try_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vpu_format *vpu_fmt;
+	int width, height;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	if (!V4L2_TYPE_IS_OUTPUT(f->type))
+		return -EINVAL;
+
+	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt) {
+		width = inst->src_fmt.width;
+		height = inst->src_fmt.height;
+		pix_mp->pixelformat = inst->src_fmt.pixelformat;
+		pix_mp->num_planes = inst->src_fmt.num_planes;
+	} else {
+		width = clamp(pix_mp->width,
+			      vpu_fmt->min_width, vpu_fmt->max_width);
+		height = clamp(pix_mp->height,
+			       vpu_fmt->min_height, vpu_fmt->max_height);
+
+		pix_mp->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		pix_mp->num_planes = vpu_fmt->num_planes;
+	}
+
+	wave6_update_pix_fmt(pix_mp, width, height);
+
+	if (pix_mp->ycbcr_enc == V4L2_YCBCR_ENC_BT2020_CONST_LUM)
+		pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_BT2020;
+	if (pix_mp->ycbcr_enc == V4L2_YCBCR_ENC_XV601 ||
+	    pix_mp->ycbcr_enc == V4L2_YCBCR_ENC_XV709) {
+		if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
+			pix_mp->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_enc_s_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	const struct vpu_format *vpu_fmt;
+	int i, ret;
+
+	dev_dbg(inst->dev->dev, "%s: 4cc %d w %d h %d plane %d colorspace %d\n",
+		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
+		pix_mp->num_planes, pix_mp->colorspace);
+
+	ret = wave6_vpu_enc_try_fmt_out(file, fh, f);
+	if (ret)
+		return ret;
+
+	if (vb2_is_busy(v4l2_m2m_get_vq(inst->v4l2_fh.m2m_ctx, f->type)))
+		return -EBUSY;
+
+	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt)
+		return -EINVAL;
+
+	inst->src_fmt.width = pix_mp->width;
+	inst->src_fmt.height = pix_mp->height;
+	inst->src_fmt.pixelformat = pix_mp->pixelformat;
+	inst->src_fmt.field = pix_mp->field;
+	inst->src_fmt.flags = pix_mp->flags;
+	inst->src_fmt.num_planes = pix_mp->num_planes;
+	for (i = 0; i < inst->src_fmt.num_planes; i++) {
+		inst->src_fmt.plane_fmt[i].bytesperline = pix_mp->plane_fmt[i].bytesperline;
+		inst->src_fmt.plane_fmt[i].sizeimage = pix_mp->plane_fmt[i].sizeimage;
+	}
+
+	inst->cbcr_interleave = !!(vpu_fmt->flags & VPU_FMT_FLAG_CBCR_INTERLEAVED);
+	inst->nv21 = !!(vpu_fmt->flags & VPU_FMT_FLAG_CRCB_ORDER);
+	inst->colorspace = pix_mp->colorspace;
+	inst->ycbcr_enc = pix_mp->ycbcr_enc;
+	inst->quantization = pix_mp->quantization;
+	inst->xfer_func = pix_mp->xfer_func;
+
+	wave6_update_pix_fmt(&inst->dst_fmt, pix_mp->width, pix_mp->height);
+	wave6_update_crop_info(inst, 0, 0, pix_mp->width, pix_mp->height);
+
+	return 0;
+}
+
+static int wave6_vpu_enc_g_fmt_out(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+	int i;
+
+	pix_mp->width = inst->src_fmt.width;
+	pix_mp->height = inst->src_fmt.height;
+	pix_mp->pixelformat = inst->src_fmt.pixelformat;
+	pix_mp->field = inst->src_fmt.field;
+	pix_mp->flags = inst->src_fmt.flags;
+	pix_mp->num_planes = inst->src_fmt.num_planes;
+	for (i = 0; i < pix_mp->num_planes; i++) {
+		pix_mp->plane_fmt[i].bytesperline = inst->src_fmt.plane_fmt[i].bytesperline;
+		pix_mp->plane_fmt[i].sizeimage = inst->src_fmt.plane_fmt[i].sizeimage;
+	}
+
+	pix_mp->colorspace = inst->colorspace;
+	pix_mp->ycbcr_enc = inst->ycbcr_enc;
+	pix_mp->quantization = inst->quantization;
+	pix_mp->xfer_func = inst->xfer_func;
+
+	return 0;
+}
+
+static int wave6_vpu_enc_g_selection(struct file *file, void *fh, struct v4l2_selection *s)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+
+	dev_dbg(inst->dev->dev, "%s: type %d target %d\n",
+		__func__, s->type, s->target);
+
+	if (!V4L2_TYPE_IS_OUTPUT(s->type))
+		return -EINVAL;
+
+	switch (s->target) {
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		s->r.left = 0;
+		s->r.top = 0;
+		s->r.width = inst->src_fmt.width;
+		s->r.height = inst->src_fmt.height;
+		break;
+	case V4L2_SEL_TGT_CROP:
+		s->r = inst->crop;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_enc_s_selection(struct file *file, void *fh, struct v4l2_selection *s)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	u32 max_crop_w, max_crop_h;
+
+	if (!V4L2_TYPE_IS_OUTPUT(s->type))
+		return -EINVAL;
+
+	if (s->target != V4L2_SEL_TGT_CROP)
+		return -EINVAL;
+
+	if (!(s->flags & (V4L2_SEL_FLAG_GE | V4L2_SEL_FLAG_LE)))
+		s->flags |= V4L2_SEL_FLAG_LE;
+
+	if (s->flags & V4L2_SEL_FLAG_GE) {
+		s->r.left = round_up(s->r.left, W6_ENC_CROP_STEP);
+		s->r.top = round_up(s->r.top, W6_ENC_CROP_STEP);
+		s->r.width = round_up(s->r.width, W6_ENC_CROP_STEP);
+		s->r.height = round_up(s->r.height, W6_ENC_CROP_STEP);
+	}
+	if (s->flags & V4L2_SEL_FLAG_LE) {
+		s->r.left = round_down(s->r.left, W6_ENC_CROP_STEP);
+		s->r.top = round_down(s->r.top, W6_ENC_CROP_STEP);
+		s->r.width = round_down(s->r.width, W6_ENC_CROP_STEP);
+		s->r.height = round_down(s->r.height, W6_ENC_CROP_STEP);
+	}
+
+	s->r.left = clamp_t(s32, s->r.left, 0,
+			    inst->src_fmt.width - W6_MIN_ENC_PIC_WIDTH);
+	s->r.top = clamp_t(s32, s->r.top, 0,
+			   inst->src_fmt.height - W6_MIN_ENC_PIC_HEIGHT);
+
+	max_crop_w = inst->src_fmt.width - s->r.left;
+	max_crop_h = inst->src_fmt.height - s->r.top;
+
+	if (!s->r.width || !s->r.height)
+		return 0;
+	if (max_crop_w < W6_MIN_ENC_PIC_WIDTH)
+		return 0;
+	if (max_crop_h < W6_MIN_ENC_PIC_HEIGHT)
+		return 0;
+
+	s->r.width = clamp(s->r.width, W6_MIN_ENC_PIC_WIDTH, max_crop_w);
+	s->r.height = clamp(s->r.height, W6_MIN_ENC_PIC_HEIGHT, max_crop_h);
+
+	wave6_update_pix_fmt(&inst->dst_fmt, s->r.width, s->r.height);
+	wave6_update_crop_info(inst, s->r.left, s->r.top, s->r.width, s->r.height);
+
+	dev_dbg(inst->dev->dev, "V4L2_SEL_TGT_CROP %dx%dx%dx%d\n",
+		s->r.left, s->r.top, s->r.width, s->r.height);
+
+	return 0;
+}
+
+static int wave6_vpu_enc_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *ec)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	int ret;
+
+	dev_dbg(inst->dev->dev, "%s: cmd %d\n", __func__, ec->cmd);
+
+	ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, ec);
+	if (ret)
+		return ret;
+
+	if (!wave6_vpu_both_queues_are_streaming(inst))
+		return 0;
+
+	switch (ec->cmd) {
+	case V4L2_ENC_CMD_STOP:
+		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_STOP);
+		v4l2_m2m_set_src_buffered(inst->v4l2_fh.m2m_ctx, true);
+		v4l2_m2m_try_schedule(inst->v4l2_fh.m2m_ctx);
+		break;
+	case V4L2_ENC_CMD_START:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_enc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+
+	dev_dbg(inst->dev->dev, "%s: type %d\n", __func__, a->type);
+
+	if (!V4L2_TYPE_IS_OUTPUT(a->type))
+		return -EINVAL;
+
+	a->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+	a->parm.output.timeperframe.numerator = 1;
+	a->parm.output.timeperframe.denominator = inst->enc_ctrls.frame_rate;
+
+	dev_dbg(inst->dev->dev, "%s: numerator : %d | denominator : %d\n",
+		__func__,
+		a->parm.output.timeperframe.numerator,
+		a->parm.output.timeperframe.denominator);
+
+	return 0;
+}
+
+static int wave6_vpu_enc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(file);
+	struct enc_controls *ctrls = &inst->enc_ctrls;
+
+	dev_dbg(inst->dev->dev, "%s: type %d\n", __func__, a->type);
+
+	if (!V4L2_TYPE_IS_OUTPUT(a->type))
+		return -EINVAL;
+
+	a->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+	if (a->parm.output.timeperframe.denominator &&
+	    a->parm.output.timeperframe.numerator) {
+		ctrls->frame_rate = DIV_ROUND_CLOSEST(a->parm.output.timeperframe.denominator,
+						      a->parm.output.timeperframe.numerator);
+		ctrls->frame_rate = clamp(ctrls->frame_rate,
+					  MIN_FRAME_RATE, MAX_FRAME_RATE);
+	} else {
+		a->parm.output.timeperframe.numerator = 1;
+		a->parm.output.timeperframe.denominator = ctrls->frame_rate;
+	}
+
+	dev_dbg(inst->dev->dev, "%s: numerator : %d | denominator : %d\n",
+		__func__,
+		a->parm.output.timeperframe.numerator,
+		a->parm.output.timeperframe.denominator);
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops wave6_vpu_enc_ioctl_ops = {
+	.vidioc_querycap = wave6_vpu_enc_querycap,
+	.vidioc_enum_framesizes = wave6_vpu_enc_enum_framesizes,
+
+	.vidioc_enum_fmt_vid_cap = wave6_vpu_enc_enum_fmt_cap,
+	.vidioc_s_fmt_vid_cap_mplane = wave6_vpu_enc_s_fmt_cap,
+	.vidioc_g_fmt_vid_cap_mplane = wave6_vpu_enc_g_fmt_cap,
+	.vidioc_try_fmt_vid_cap_mplane = wave6_vpu_enc_try_fmt_cap,
+
+	.vidioc_enum_fmt_vid_out = wave6_vpu_enc_enum_fmt_out,
+	.vidioc_s_fmt_vid_out_mplane = wave6_vpu_enc_s_fmt_out,
+	.vidioc_g_fmt_vid_out_mplane = wave6_vpu_enc_g_fmt_out,
+	.vidioc_try_fmt_vid_out_mplane = wave6_vpu_enc_try_fmt_out,
+
+	.vidioc_g_selection = wave6_vpu_enc_g_selection,
+	.vidioc_s_selection = wave6_vpu_enc_s_selection,
+
+	.vidioc_g_parm = wave6_vpu_enc_g_parm,
+	.vidioc_s_parm = wave6_vpu_enc_s_parm,
+
+	.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+	.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+	.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+	.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+	.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+	.vidioc_streamon = v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+
+	.vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd,
+	.vidioc_encoder_cmd = wave6_vpu_enc_encoder_cmd,
+
+	.vidioc_subscribe_event = wave6_vpu_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int wave6_vpu_enc_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct vpu_instance *inst = wave6_ctrl_to_vpu_inst(ctrl);
+	struct enc_controls *p = &inst->enc_ctrls;
+
+	trace_wave6_vpu_s_ctrl(inst, ctrl);
+
+	dev_dbg(inst->dev->dev, "%s: name %s value %d\n",
+		__func__, ctrl->name, ctrl->val);
+
+	switch (ctrl->id) {
+	case V4L2_CID_HFLIP:
+		if (ctrl->val)
+			p->mirror_direction |= MIR_HOR;
+		else
+			p->mirror_direction &= ~MIR_HOR;
+		break;
+	case V4L2_CID_VFLIP:
+		if (ctrl->val)
+			p->mirror_direction |= MIR_VER;
+		else
+			p->mirror_direction &= ~MIR_VER;
+		break;
+	case V4L2_CID_ROTATE:
+		p->rotation_angle = ctrl->val;
+		break;
+	case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT:
+		break;
+	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+		p->gop_size = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE:
+		p->slice_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB:
+		p->slice_max_mb = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
+		p->bitrate_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_BITRATE:
+		p->bitrate = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE:
+		p->frame_rc_enable = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE:
+		p->mb_rc_enable = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME:
+		p->force_key_frame = true;
+		break;
+	case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR:
+		p->prepend_spspps_to_idr = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE:
+		break;
+	case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD:
+		p->intra_refresh_period = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE:
+		p->frame_skip_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE:
+		p->hevc.profile = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL:
+		p->hevc.level = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP:
+		p->hevc.min_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP:
+		p->hevc.max_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP:
+		p->hevc.i_frame_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP:
+		p->hevc.p_frame_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP:
+		p->hevc.b_frame_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE:
+		p->hevc.loop_filter_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2:
+		p->hevc.lf_beta_offset_div2 = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2:
+		p->hevc.lf_tc_offset_div2 = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE:
+		p->hevc.refresh_type = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD:
+		p->hevc.refresh_period = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_CONST_INTRA_PRED:
+		p->hevc.const_intra_pred = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING:
+		p->hevc.strong_smoothing = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION:
+		p->hevc.tmv_prediction = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+		p->h264.profile = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+		p->h264.level = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_MIN_QP:
+		p->h264.min_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_MAX_QP:
+		p->h264.max_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
+		p->h264.i_frame_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
+		p->h264.p_frame_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP:
+		p->h264.b_frame_qp = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE:
+		p->h264.loop_filter_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA:
+		p->h264.loop_filter_beta = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA:
+		p->h264.loop_filter_alpha = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM:
+		p->h264._8x8_transform = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_CONSTRAINED_INTRA_PREDICTION:
+		p->h264.constrained_intra_prediction = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_CHROMA_QP_INDEX_OFFSET:
+		p->h264.chroma_qp_index_offset = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
+		p->h264.entropy_mode = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
+		p->h264.i_period = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE:
+		p->h264.vui_sar_enable = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC:
+		p->h264.vui_sar_idc = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH:
+		p->h264.vui_ext_sar_width = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT:
+		p->h264.vui_ext_sar_height = ctrl->val;
+		break;
+	case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE:
+		p->h264.cpb_size = ctrl->val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops wave6_vpu_enc_ctrl_ops = {
+	.s_ctrl = wave6_vpu_enc_s_ctrl,
+};
+
+static u32 to_video_full_range_flag(enum v4l2_quantization quantization)
+{
+	switch (quantization) {
+	case V4L2_QUANTIZATION_FULL_RANGE:
+		return 1;
+	case V4L2_QUANTIZATION_LIM_RANGE:
+	default:
+		return 0;
+	}
+}
+
+static u32 to_colour_primaries(enum v4l2_colorspace colorspace)
+{
+	switch (colorspace) {
+	case V4L2_COLORSPACE_SMPTE170M:
+		return 6;
+	case V4L2_COLORSPACE_REC709:
+	case V4L2_COLORSPACE_SRGB:
+	case V4L2_COLORSPACE_JPEG:
+		return 1;
+	case V4L2_COLORSPACE_BT2020:
+		return 9;
+	case V4L2_COLORSPACE_DCI_P3:
+		return 11;
+	case V4L2_COLORSPACE_SMPTE240M:
+		return 7;
+	case V4L2_COLORSPACE_470_SYSTEM_M:
+		return 4;
+	case V4L2_COLORSPACE_470_SYSTEM_BG:
+		return 5;
+	case V4L2_COLORSPACE_RAW:
+	default:
+		return 2;
+	}
+}
+
+static u32 to_transfer_characteristics(enum v4l2_colorspace colorspace,
+				       enum v4l2_xfer_func xfer_func)
+{
+	if (xfer_func == V4L2_XFER_FUNC_DEFAULT)
+		xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(colorspace);
+
+	switch (xfer_func) {
+	case V4L2_XFER_FUNC_709:
+		if (colorspace == V4L2_COLORSPACE_SMPTE170M)
+			return 6;
+		else if (colorspace == V4L2_COLORSPACE_BT2020)
+			return 14;
+		else
+			return 1;
+	case V4L2_XFER_FUNC_SRGB:
+		return 13;
+	case V4L2_XFER_FUNC_SMPTE240M:
+		return 7;
+	case V4L2_XFER_FUNC_NONE:
+		return 8;
+	case V4L2_XFER_FUNC_SMPTE2084:
+		return 16;
+	case V4L2_XFER_FUNC_DCI_P3:
+	default:
+		return 2;
+	}
+}
+
+static u32 to_matrix_coeffs(enum v4l2_colorspace colorspace,
+			    enum v4l2_ycbcr_encoding ycbcr_enc)
+{
+	if (ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
+		ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(colorspace);
+
+	switch (ycbcr_enc) {
+	case V4L2_YCBCR_ENC_601:
+	case V4L2_YCBCR_ENC_XV601:
+		if (colorspace == V4L2_COLORSPACE_SMPTE170M)
+			return 6;
+		else
+			return 5;
+	case V4L2_YCBCR_ENC_709:
+	case V4L2_YCBCR_ENC_XV709:
+		return 1;
+	case V4L2_YCBCR_ENC_BT2020:
+		return 9;
+	case V4L2_YCBCR_ENC_BT2020_CONST_LUM:
+		return 10;
+	case V4L2_YCBCR_ENC_SMPTE240M:
+		return 7;
+	default:
+		return 2;
+	}
+}
+
+static void wave6_set_enc_h264_param(struct enc_codec_param *output,
+				     struct h264_enc_controls *ctrls)
+{
+	switch (ctrls->profile) {
+	case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
+	case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
+		output->profile = H264_PROFILE_BP;
+		output->internal_bit_depth = 8;
+		break;
+	case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
+		output->profile = H264_PROFILE_MP;
+		output->internal_bit_depth = 8;
+		break;
+	case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED:
+		output->profile = H264_PROFILE_EXTENDED;
+		output->internal_bit_depth = 8;
+		break;
+	case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
+		output->profile = H264_PROFILE_HP;
+		output->internal_bit_depth = 8;
+		break;
+	default:
+		break;
+	}
+	switch (ctrls->level) {
+	case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
+		output->level = 10;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_1B:
+		output->level = 9;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_1_1:
+		output->level = 11;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_1_2:
+		output->level = 12;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_1_3:
+		output->level = 13;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
+		output->level = 20;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_2_1:
+		output->level = 21;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_2_2:
+		output->level = 22;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_3_0:
+		output->level = 30;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
+		output->level = 31;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
+		output->level = 32;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
+		output->level = 40;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
+		output->level = 41;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
+		output->level = 42;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
+		output->level = 50;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_5_1:
+		output->level = 51;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LEVEL_5_2:
+		output->level = 52;
+		break;
+	default:
+		break;
+	}
+	output->qp = ctrls->i_frame_qp;
+	output->min_qp_i = ctrls->min_qp;
+	output->max_qp_i = ctrls->max_qp;
+	output->min_qp_p = ctrls->min_qp;
+	output->max_qp_p = ctrls->max_qp;
+	output->min_qp_b = ctrls->min_qp;
+	output->max_qp_b = ctrls->max_qp;
+	switch (ctrls->loop_filter_mode) {
+	case V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED:
+		output->en_dbk = 0;
+		output->en_lf_slice_boundary = 0;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED:
+		output->en_dbk = 1;
+		output->en_lf_slice_boundary = 1;
+		break;
+	case V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY:
+		output->en_dbk = 1;
+		output->en_lf_slice_boundary = 0;
+		break;
+	default:
+		break;
+	}
+	output->intra_period = ctrls->i_period;
+	output->beta_offset_div2 = ctrls->loop_filter_beta;
+	output->tc_offset_div2 = ctrls->loop_filter_alpha;
+	if (output->profile >= H264_PROFILE_HP)
+		output->en_transform8x8 = ctrls->_8x8_transform;
+	output->en_const_intra_pred = ctrls->constrained_intra_prediction;
+	output->cb_qp_offset = ctrls->chroma_qp_index_offset;
+	output->cr_qp_offset = ctrls->chroma_qp_index_offset;
+	if (output->profile >= H264_PROFILE_MP)
+		output->en_cabac = ctrls->entropy_mode;
+}
+
+static void wave6_set_enc_hevc_param(struct enc_codec_param *output,
+				     struct hevc_enc_controls *ctrls)
+{
+	switch (ctrls->profile) {
+	case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN:
+		output->profile = HEVC_PROFILE_MAIN;
+		output->internal_bit_depth = 8;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE:
+		output->profile = HEVC_PROFILE_STILLPICTURE;
+		output->internal_bit_depth = 8;
+		output->en_still_picture = true;
+		break;
+	default:
+		break;
+	}
+	switch (ctrls->level) {
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_1:
+		output->level = 10 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_2:
+		output->level = 20 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1:
+		output->level = 21 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_3:
+		output->level = 30 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1:
+		output->level = 31 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_4:
+		output->level = 40 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1:
+		output->level = 41 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_5:
+		output->level = 50 * 3;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1:
+		output->level = 51 * 3;
+		break;
+	default:
+		break;
+	}
+	output->qp = ctrls->i_frame_qp;
+	output->min_qp_i = ctrls->min_qp;
+	output->max_qp_i = ctrls->max_qp;
+	output->min_qp_p = ctrls->min_qp;
+	output->max_qp_p = ctrls->max_qp;
+	output->min_qp_b = ctrls->min_qp;
+	output->max_qp_b = ctrls->max_qp;
+	switch (ctrls->loop_filter_mode) {
+	case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED:
+		output->en_dbk = 0;
+		output->en_sao = 0;
+		output->en_lf_slice_boundary = 0;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_ENABLED:
+		output->en_dbk = 1;
+		output->en_sao = 1;
+		output->en_lf_slice_boundary = 1;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY:
+		output->en_dbk = 1;
+		output->en_sao = 1;
+		output->en_lf_slice_boundary = 0;
+		break;
+	default:
+		break;
+	}
+	switch (ctrls->refresh_type) {
+	case V4L2_MPEG_VIDEO_HEVC_REFRESH_NONE:
+		output->decoding_refresh_type = DEC_REFRESH_TYPE_NON_IRAP;
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR:
+		output->decoding_refresh_type = DEC_REFRESH_TYPE_IDR;
+		break;
+	default:
+		break;
+	}
+	output->intra_period = ctrls->refresh_period;
+	if (output->idr_period) {
+		output->decoding_refresh_type = DEC_REFRESH_TYPE_IDR;
+		output->intra_period = output->idr_period;
+		output->idr_period = 0;
+	}
+	if (output->profile == HEVC_PROFILE_STILLPICTURE) {
+		output->gop_preset_idx = PRESET_IDX_ALL_I;
+		output->decoding_refresh_type = DEC_REFRESH_TYPE_IDR;
+		output->intra_period = 0;
+		output->idr_period = 0;
+	}
+	output->beta_offset_div2 = ctrls->lf_beta_offset_div2;
+	output->tc_offset_div2 = ctrls->lf_tc_offset_div2;
+	output->en_const_intra_pred = ctrls->const_intra_pred;
+	output->en_intra_smooth = ctrls->strong_smoothing;
+	output->en_temporal_mvp = ctrls->tmv_prediction;
+	output->num_ticks_poc_diff_one = DEFAULT_NUM_TICKS_POC_DIFF;
+	output->intra_4x4 = DEFAULT_INTRA_4X4;
+}
+
+static void wave6_set_enc_open_param(struct enc_open_param *open_param,
+				     struct vpu_instance *inst)
+{
+	struct enc_controls *ctrls = &inst->enc_ctrls;
+	struct enc_codec_param *output = &open_param->codec_param;
+	u32 ctu_size = (inst->std == W_AVC_ENC) ? 16 : 64;
+	u32 num_ctu_col = (ALIGN(inst->codec_rect.width, ctu_size) / ctu_size);
+	u32 num_ctu_row = (ALIGN(inst->codec_rect.height, ctu_size) / ctu_size);
+	const struct vpu_format *vpu_fmt;
+
+	vpu_fmt = wave6_find_vpu_fmt(inst->src_fmt.pixelformat, VPU_FMT_TYPE_RAW);
+	if (!vpu_fmt)
+		return;
+
+	open_param->src_format = vpu_fmt->fb_fmt;
+	open_param->source_endian = vpu_fmt->endian;
+	if (vpu_fmt->flags & VPU_FMT_FLAG_SUPPORT_ROT_MIR) {
+		open_param->mir_dir = inst->enc_ctrls.mirror_direction;
+		open_param->rot_angle = inst->enc_ctrls.rotation_angle;
+	}
+	open_param->inst_buffer.temp_base = inst->dev->temp_vbuf.daddr;
+	open_param->inst_buffer.temp_size = inst->dev->temp_vbuf.size;
+	open_param->inst_buffer.ar_base = inst->ar_vbuf.daddr;
+	open_param->pic_width = inst->codec_rect.width;
+	open_param->pic_height = inst->codec_rect.height;
+
+	output->gop_preset_idx = PRESET_IDX_IPP_SINGLE;
+	output->temp_layer_cnt = DEFAULT_TEMP_LAYER_CNT;
+	output->rc_initial_level = DEFAULT_RC_INITIAL_LEVEL;
+	output->pic_rc_max_dqp = DEFAULT_PIC_RC_MAX_DQP;
+	output->rc_initial_qp = DEFAULT_RC_INITIAL_QP;
+	output->qround_inter = DEFAULT_QROUND_INTER;
+	output->qround_intra = DEFAULT_QROUND_INTRA;
+
+	output->frame_rate = ctrls->frame_rate;
+	output->idr_period = ctrls->gop_size;
+	output->rc_mode = ctrls->bitrate_mode;
+	output->rc_update_speed = (ctrls->bitrate_mode) ? DEFAULT_RC_UPDATE_SPEED_CBR :
+							  DEFAULT_RC_UPDATE_SPEED_VBR;
+	output->en_rate_control = ctrls->frame_rc_enable;
+	output->en_cu_level_rate_control = ctrls->mb_rc_enable;
+	output->max_intra_pic_bit = min((inst->dst_fmt.plane_fmt[0].sizeimage * 8),
+					MAX_INTRA_INTER_PIC_BIT);
+	output->max_inter_pic_bit = min((inst->dst_fmt.plane_fmt[0].sizeimage * 8),
+					MAX_INTRA_INTER_PIC_BIT);
+	output->bitrate = ctrls->bitrate;
+	output->cpb_size = wave6_cpb_size_msec(ctrls->h264.cpb_size, ctrls->bitrate);
+	output->slice_mode = ctrls->slice_mode;
+	output->slice_arg = ctrls->slice_max_mb;
+	if (output->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) {
+		if (inst->std == W_AVC_ENC)
+			output->slice_arg = clamp(ctrls->slice_max_mb,
+						  AVC_MIN_SLICE_MAX_MB,
+						  num_ctu_col * num_ctu_row);
+		else
+			output->slice_arg = min(ctrls->slice_max_mb,
+						num_ctu_col * num_ctu_row);
+	}
+	output->forced_idr_header = ctrls->prepend_spspps_to_idr;
+	output->en_skip_frame = (ctrls->frame_skip_mode) ? 1 : 0;
+	if (ctrls->intra_refresh_period) {
+		output->intra_refresh_mode = INTRA_REFRESH_ROW;
+		if (ctrls->intra_refresh_period < num_ctu_row) {
+			output->intra_refresh_arg = (num_ctu_row + ctrls->intra_refresh_period - 1)
+						    / ctrls->intra_refresh_period;
+		} else {
+			output->intra_refresh_arg = 1;
+		}
+	}
+	output->sar.enable = ctrls->h264.vui_sar_enable;
+	output->sar.idc = ctrls->h264.vui_sar_idc;
+	if (output->sar.idc == V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED)
+		output->sar.idc = H264_VUI_SAR_IDC_EXTENDED;
+	output->sar.width = ctrls->h264.vui_ext_sar_width;
+	output->sar.height = ctrls->h264.vui_ext_sar_height;
+	output->color.color_range = to_video_full_range_flag(inst->quantization);
+	output->color.color_primaries = to_colour_primaries(inst->colorspace);
+	output->color.transfer_characteristics = to_transfer_characteristics(inst->colorspace,
+									     inst->xfer_func);
+	output->color.matrix_coefficients = to_matrix_coeffs(inst->colorspace, inst->ycbcr_enc);
+	output->conf_win.left = inst->crop.left - inst->codec_rect.left;
+	output->conf_win.top = inst->crop.top - inst->codec_rect.top;
+	output->conf_win.right = inst->codec_rect.width
+					- inst->crop.width - output->conf_win.left;
+	output->conf_win.bottom = inst->codec_rect.height
+					- inst->crop.height - output->conf_win.top;
+
+	switch (inst->std) {
+	case W_AVC_ENC:
+		wave6_set_enc_h264_param(output, &ctrls->h264);
+		break;
+	case W_HEVC_ENC:
+		wave6_set_enc_hevc_param(output, &ctrls->hevc);
+		break;
+	default:
+		break;
+	}
+}
+
+static int wave6_vpu_enc_create_instance(struct vpu_instance *inst)
+{
+	int ret;
+	struct enc_open_param open_param;
+
+	memset(&open_param, 0, sizeof(struct enc_open_param));
+
+	wave6_vpu_record_performance_timestamps(inst);
+
+	wave6_vpu_core_activate(inst->dev);
+	ret = pm_runtime_resume_and_get(inst->dev->dev);
+	if (ret) {
+		dev_err(inst->dev->dev, "runtime_resume failed %d\n", ret);
+		return ret;
+	}
+
+	inst->ar_vbuf.size = ALIGN(W6_ARBUF_SIZE, 4096);
+	ret = wave6_vdi_alloc_dma(inst->dev->dev, &inst->ar_vbuf);
+	if (ret) {
+		dev_err(inst->dev->dev, "alloc ar of size %zu failed\n",
+			inst->ar_vbuf.size);
+		goto error_pm;
+	}
+
+	wave6_set_enc_open_param(&open_param, inst);
+
+	ret = wave6_vpu_enc_open(inst, &open_param);
+	if (ret) {
+		dev_err(inst->dev->dev, "failed create instance : %d\n", ret);
+		goto error_open;
+	}
+
+	dev_dbg(inst->dev->dev, "[%d] encoder\n", inst->id);
+
+	scoped_guard(spinlock, &inst->dev->inst_lock)
+		list_add_tail(&inst->list, &inst->dev->instances);
+	wave6_vpu_enable_instance(inst);
+	wave6_vpu_create_dbgfs_file(inst);
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_OPEN);
+
+	return 0;
+
+error_open:
+	wave6_vdi_free_dma(&inst->ar_vbuf);
+error_pm:
+	pm_runtime_put_sync(inst->dev->dev);
+
+	return ret;
+}
+
+static int wave6_vpu_enc_initialize_instance(struct vpu_instance *inst)
+{
+	int ret;
+	struct enc_seq_info seq_info;
+	struct v4l2_ctrl *ctrl;
+
+	ret = wave6_vpu_enc_issue_seq_init(inst);
+	if (ret) {
+		dev_err(inst->dev->dev, "seq init fail %d\n", ret);
+		return ret;
+	}
+
+	ret = wave6_vpu_wait_interrupt(inst, W6_VPU_TIMEOUT);
+	if (ret) {
+		dev_err(inst->dev->dev, "seq init timeout\n");
+		return ret;
+	}
+
+	ret = wave6_vpu_enc_complete_seq_init(inst, &seq_info);
+	if (ret) {
+		dev_err(inst->dev->dev, "seq init error\n");
+		return ret;
+	}
+
+	dev_dbg(inst->dev->dev, "min_fb_cnt : %d | min_src_cnt : %d\n",
+		seq_info.min_frame_buffer_count,
+		seq_info.min_src_frame_count);
+
+	ctrl = v4l2_ctrl_find(&inst->v4l2_ctrl_hdl,
+			      V4L2_CID_MIN_BUFFERS_FOR_OUTPUT);
+	if (ctrl)
+		v4l2_ctrl_s_ctrl(ctrl, seq_info.min_src_frame_count);
+
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_INIT_SEQ);
+
+	return 0;
+}
+
+static int wave6_vpu_enc_prepare_fb(struct vpu_instance *inst)
+{
+	int ret;
+	unsigned int i;
+	unsigned int fb_num;
+	unsigned int mv_num;
+	unsigned int fb_stride, fb_stride_c;
+	unsigned int fb_height;
+	unsigned int luma_size;
+	unsigned int chroma_size;
+	struct enc_seq_info *seq_info = &inst->codec_info->enc_info.seq_info;
+	struct enc_open_param *open_param = &inst->codec_info->enc_info.open_param;
+
+	fb_num = seq_info->min_frame_buffer_count;
+	mv_num = seq_info->req_mv_buffer_count;
+
+	fb_stride = ALIGN(inst->codec_rect.width, W6_FBC_BUF_ALIGNMENT);
+	fb_height = ALIGN(inst->codec_rect.height, W6_FBC_BUF_ALIGNMENT);
+	if (open_param->rot_angle == ROT_90 || open_param->rot_angle == ROT_270) {
+		fb_stride = ALIGN(inst->codec_rect.height, W6_FBC_BUF_ALIGNMENT);
+		fb_height = ALIGN(inst->codec_rect.width, W6_FBC_BUF_ALIGNMENT);
+	}
+	luma_size = fb_stride * fb_height;
+
+	fb_stride_c = ALIGN(fb_stride / 2, W6_FBC_BUF_ALIGNMENT);
+	chroma_size = fb_stride_c * fb_height;
+
+	for (i = 0; i < fb_num; i++) {
+		struct frame_buffer *frame = &inst->frame_buf[i];
+		struct vpu_buf *vframe = &inst->frame_vbuf[i];
+
+		vframe->size = luma_size + chroma_size;
+		ret = wave6_vdi_alloc_dma(inst->dev->dev, vframe);
+		if (ret) {
+			dev_err(inst->dev->dev, "alloc FBC buffer fail : %zu\n",
+				vframe->size);
+			goto error;
+		}
+
+		frame->buf_y = vframe->daddr;
+		frame->buf_cb = vframe->daddr + luma_size;
+		frame->buf_cr = (dma_addr_t)-1;
+		frame->stride = fb_stride;
+		frame->stride_c = fb_stride_c;
+		frame->height = fb_height;
+		frame->map_type = COMPRESSED_FRAME_MAP;
+	}
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_FBC_Y_TBL,
+					fb_stride, fb_height, fb_num);
+	if (ret)
+		goto error;
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_FBC_C_TBL,
+					fb_stride, fb_height, fb_num);
+	if (ret)
+		goto error;
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_MV_COL,
+					fb_stride, fb_height, mv_num);
+	if (ret)
+		goto error;
+
+	ret = wave6_allocate_aux_buffer(inst, AUX_BUF_SUB_SAMPLE,
+					fb_stride, fb_height, fb_num);
+	if (ret)
+		goto error;
+
+	ret = wave6_vpu_enc_register_frame_buffer(inst);
+	if (ret) {
+		dev_err(inst->dev->dev, "register frame buffer fail %d\n", ret);
+		goto error;
+	}
+
+	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_PIC_RUN);
+
+	wave6_vpu_record_performance_timestamps(inst);
+
+	return 0;
+
+error:
+	wave6_vpu_enc_release_fb(inst);
+
+	return ret;
+}
+
+static int wave6_vpu_enc_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
+				     unsigned int *num_planes, unsigned int sizes[],
+				     struct device *alloc_devs[])
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(q);
+	struct v4l2_pix_format_mplane inst_format =
+		(V4L2_TYPE_IS_OUTPUT(q->type)) ? inst->src_fmt : inst->dst_fmt;
+	unsigned int i;
+
+	dev_dbg(inst->dev->dev, "%s: num_buffers %d num_planes %d type %d\n",
+		__func__, *num_buffers, *num_planes, q->type);
+
+	if (*num_planes) {
+		if (inst_format.num_planes != *num_planes)
+			return -EINVAL;
+
+		for (i = 0; i < *num_planes; i++) {
+			if (sizes[i] < inst_format.plane_fmt[i].sizeimage)
+				return -EINVAL;
+		}
+	} else {
+		*num_planes = inst_format.num_planes;
+		for (i = 0; i < *num_planes; i++) {
+			sizes[i] = inst_format.plane_fmt[i].sizeimage;
+			dev_dbg(inst->dev->dev, "size[%d] : %d\n", i, sizes[i]);
+		}
+
+		if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+			struct v4l2_ctrl *ctrl;
+			unsigned int min_src_frame_count = 0;
+
+			ctrl = v4l2_ctrl_find(&inst->v4l2_ctrl_hdl,
+					      V4L2_CID_MIN_BUFFERS_FOR_OUTPUT);
+			if (ctrl)
+				min_src_frame_count = v4l2_ctrl_g_ctrl(ctrl);
+
+			*num_buffers = max(*num_buffers, min_src_frame_count);
+		}
+	}
+
+	return 0;
+}
+
+static void wave6_vpu_enc_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vpu_instance *inst = vb2_get_drv_priv(vb->vb2_queue);
+	struct vpu_buffer *vpu_buf = wave6_to_vpu_buf(vbuf);
+
+	dev_dbg(inst->dev->dev, "type %4d index %4d size[0] %4ld size[1] : %4ld | size[2] : %4ld\n",
+		vb->type, vb->index, vb2_plane_size(&vbuf->vb2_buf, 0),
+		vb2_plane_size(&vbuf->vb2_buf, 1), vb2_plane_size(&vbuf->vb2_buf, 2));
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->type)) {
+		vbuf->sequence = inst->queued_src_buf_num++;
+
+		vpu_buf->ts_input = ktime_get_raw();
+		vpu_buf->force_key_frame = inst->enc_ctrls.force_key_frame;
+		inst->enc_ctrls.force_key_frame = false;
+		vpu_buf->force_frame_qp = !inst->enc_ctrls.frame_rc_enable;
+		if (vpu_buf->force_frame_qp) {
+			if (inst->std == W_AVC_ENC) {
+				vpu_buf->force_i_frame_qp = inst->enc_ctrls.h264.i_frame_qp;
+				vpu_buf->force_p_frame_qp = inst->enc_ctrls.h264.p_frame_qp;
+				vpu_buf->force_b_frame_qp = inst->enc_ctrls.h264.b_frame_qp;
+			} else if (inst->std == W_HEVC_ENC) {
+				vpu_buf->force_i_frame_qp = inst->enc_ctrls.hevc.i_frame_qp;
+				vpu_buf->force_p_frame_qp = inst->enc_ctrls.hevc.p_frame_qp;
+				vpu_buf->force_b_frame_qp = inst->enc_ctrls.hevc.b_frame_qp;
+			}
+		}
+	} else {
+		inst->queued_dst_buf_num++;
+	}
+
+	vpu_buf->consumed = false;
+	vpu_buf->used = false;
+	v4l2_m2m_buf_queue(inst->v4l2_fh.m2m_ctx, vbuf);
+}
+
+static void wave6_vpu_enc_buf_finish(struct vb2_buffer *vb)
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vpu_buffer *vpu_buf = wave6_to_vpu_buf(vbuf);
+	struct v4l2_ctrl *ctrl;
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->type))
+		return;
+
+	ctrl = v4l2_ctrl_find(inst->v4l2_fh.ctrl_handler, V4L2_CID_MPEG_VIDEO_AVERAGE_QP);
+	if (ctrl)
+		v4l2_ctrl_s_ctrl(ctrl, vpu_buf->average_qp);
+}
+
+static int wave6_vpu_enc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(q);
+	int ret = 0;
+
+	trace_wave6_vpu_start_streaming(inst, V4L2_TYPE_IS_OUTPUT(q->type));
+
+	if (inst->state == VPU_INST_STATE_NONE) {
+		ret = wave6_vpu_enc_create_instance(inst);
+		if (ret)
+			goto exit;
+	}
+
+	if (inst->state == VPU_INST_STATE_OPEN) {
+		ret = wave6_vpu_enc_initialize_instance(inst);
+		if (ret) {
+			wave6_vpu_enc_destroy_instance(inst, true);
+			goto exit;
+		}
+	}
+
+	if (inst->state == VPU_INST_STATE_INIT_SEQ) {
+		ret = wave6_vpu_enc_prepare_fb(inst);
+		if (ret) {
+			wave6_vpu_enc_destroy_instance(inst, true);
+			goto exit;
+		}
+	}
+
+exit:
+	if (ret)
+		wave6_vpu_return_buffers(inst, q->type, VB2_BUF_STATE_QUEUED);
+
+	return ret;
+}
+
+static void wave6_vpu_enc_stop_streaming(struct vb2_queue *q)
+{
+	struct vpu_instance *inst = vb2_get_drv_priv(q);
+
+	trace_wave6_vpu_stop_streaming(inst, V4L2_TYPE_IS_OUTPUT(q->type));
+
+	dev_dbg(inst->dev->dev, "[%d] %s, input %d, encode %d\n",
+		inst->id, V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture",
+		inst->queued_src_buf_num, inst->sequence);
+
+	if (inst->state == VPU_INST_STATE_NONE)
+		goto exit;
+
+	if (wave6_vpu_both_queues_are_streaming(inst))
+		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_STOP);
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+		wave6_vpu_reset_performance(inst);
+		inst->queued_src_buf_num = 0;
+		inst->processed_buf_num = 0;
+		inst->error_buf_num = 0;
+		inst->sequence = 0;
+		v4l2_m2m_set_src_buffered(inst->v4l2_fh.m2m_ctx, false);
+	} else {
+		inst->eos = false;
+		inst->queued_dst_buf_num = 0;
+	}
+
+	if (V4L2_TYPE_IS_CAPTURE(q->type) && inst->state != VPU_INST_STATE_NONE)
+		wave6_vpu_enc_destroy_instance(inst, true);
+
+exit:
+	wave6_vpu_return_buffers(inst, q->type, VB2_BUF_STATE_ERROR);
+}
+
+static const struct vb2_ops wave6_vpu_enc_vb2_ops = {
+	.queue_setup = wave6_vpu_enc_queue_setup,
+	.buf_queue = wave6_vpu_enc_buf_queue,
+	.buf_finish = wave6_vpu_enc_buf_finish,
+	.start_streaming = wave6_vpu_enc_start_streaming,
+	.stop_streaming = wave6_vpu_enc_stop_streaming,
+};
+
+static void wave6_set_default_format(struct v4l2_pix_format_mplane *src_fmt,
+				     struct v4l2_pix_format_mplane *dst_fmt)
+{
+	const struct vpu_format *vpu_fmt;
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(0, VPU_FMT_TYPE_RAW);
+	if (vpu_fmt) {
+		src_fmt->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		src_fmt->num_planes = vpu_fmt->num_planes;
+		wave6_update_pix_fmt(src_fmt,
+				     W6_DEF_ENC_PIC_WIDTH, W6_DEF_ENC_PIC_HEIGHT);
+	}
+
+	vpu_fmt = wave6_find_vpu_fmt_by_idx(0, VPU_FMT_TYPE_CODEC);
+	if (vpu_fmt) {
+		dst_fmt->pixelformat = vpu_fmt->v4l2_pix_fmt;
+		dst_fmt->num_planes = vpu_fmt->num_planes;
+		wave6_update_pix_fmt(dst_fmt,
+				     W6_DEF_ENC_PIC_WIDTH, W6_DEF_ENC_PIC_HEIGHT);
+	}
+}
+
+static int wave6_vpu_enc_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
+{
+	struct vpu_instance *inst = priv;
+	int ret;
+
+	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->ops = &wave6_vpu_enc_vb2_ops;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->buf_struct_size = sizeof(struct vpu_buffer);
+	src_vq->drv_priv = inst;
+	src_vq->lock = &inst->dev->dev_lock;
+	src_vq->dev = inst->dev->v4l2_dev.dev;
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->ops = &wave6_vpu_enc_vb2_ops;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->buf_struct_size = sizeof(struct vpu_buffer);
+	dst_vq->drv_priv = inst;
+	dst_vq->lock = &inst->dev->dev_lock;
+	dst_vq->dev = inst->dev->v4l2_dev.dev;
+	ret = vb2_queue_init(dst_vq);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct vpu_instance_ops wave6_vpu_enc_inst_ops = {
+	.start_process = wave6_vpu_enc_start_encode,
+	.finish_process = wave6_vpu_enc_finish_encode,
+};
+
+static int wave6_vpu_open_enc(struct file *filp)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct vpu_core_device *core = video_drvdata(filp);
+	struct vpu_instance *inst = NULL;
+	struct v4l2_ctrl_handler *v4l2_ctrl_hdl;
+	int ret;
+
+	inst = kzalloc_obj(*inst);
+	if (!inst)
+		return -ENOMEM;
+	v4l2_ctrl_hdl = &inst->v4l2_ctrl_hdl;
+
+	init_completion(&inst->irq_done);
+	inst->dev = core;
+	inst->type = VPU_INST_TYPE_ENC;
+	inst->ops = &wave6_vpu_enc_inst_ops;
+
+	v4l2_fh_init(&inst->v4l2_fh, vdev);
+	filp->private_data = &inst->v4l2_fh;
+	v4l2_fh_add(&inst->v4l2_fh, filp);
+
+	inst->v4l2_fh.m2m_ctx =
+		v4l2_m2m_ctx_init(core->m2m_dev, inst, wave6_vpu_enc_queue_init);
+	if (IS_ERR(inst->v4l2_fh.m2m_ctx)) {
+		ret = PTR_ERR(inst->v4l2_fh.m2m_ctx);
+		goto free_inst;
+	}
+
+	v4l2_ctrl_handler_init(v4l2_ctrl_hdl, 50);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
+			       V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE, 0,
+			       V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_HEVC_LEVEL,
+			       V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1, 0,
+			       V4L2_MPEG_VIDEO_HEVC_LEVEL_5);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP,
+			  0, 51, 1, 8);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP,
+			  0, 51, 1, 51);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE,
+			       V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY, 0,
+			       V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_ENABLED);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2,
+			  -6, 6, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2,
+			  -6, 6, 1, 0);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE,
+			       V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR,
+			       BIT(V4L2_MPEG_VIDEO_HEVC_REFRESH_CRA),
+			       V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD,
+			  0, 2047, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_CONST_INTRA_PRED,
+			  0, 1, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+			       V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, 0,
+			       V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+			       V4L2_MPEG_VIDEO_H264_LEVEL_5_2, 0,
+			       V4L2_MPEG_VIDEO_H264_LEVEL_5_0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+			  0, 51, 1, 8);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+			  0, 51, 1, 51);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE,
+			       V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY, 0,
+			       V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA,
+			  -6, 6, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA,
+			  -6, 6, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_CONSTRAINED_INTRA_PREDICTION,
+			  0, 1, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_CHROMA_QP_INDEX_OFFSET,
+			  -12, 12, 1, 0);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE,
+			       V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, 0,
+			       V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
+			  0, 2047, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE, 0, 1, 1, 0);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC,
+			       V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED, 0,
+			       V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH,
+			  0, 0xFFFF, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT,
+			  0, 0xFFFF, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_HFLIP,
+			  0, 1, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_VFLIP,
+			  0, 1, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_ROTATE,
+			  0, 270, 90, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE,
+			  0, 18750000, 1, 0);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+			       V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0,
+			       V4L2_MPEG_VIDEO_BITRATE_MODE_CBR);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_BITRATE,
+			  1000, 240000000, 1, 2097152);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+			  0, 2047, 1, 30);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE,
+			       V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB, 0,
+			       V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB,
+			  1, 0x3FFFF, 1, 1);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME,
+			  0, 1, 1, 0);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE,
+			       V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC,
+			       BIT(V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM),
+			       V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD,
+			  0, 2160, 1, 0);
+	v4l2_ctrl_new_std_menu(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			       V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE,
+			       V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT,
+			       BIT(V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_LEVEL_LIMIT),
+			       V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_DISABLED);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, &wave6_vpu_enc_ctrl_ops,
+			  V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 32, 1, 1);
+	v4l2_ctrl_new_std(v4l2_ctrl_hdl, NULL,
+			  V4L2_CID_MPEG_VIDEO_AVERAGE_QP, 0, 51, 1, 0);
+
+	if (v4l2_ctrl_hdl->error) {
+		ret = -ENODEV;
+		goto err_m2m_release;
+	}
+
+	inst->v4l2_fh.ctrl_handler = v4l2_ctrl_hdl;
+	ret = v4l2_ctrl_handler_setup(v4l2_ctrl_hdl);
+	if (ret)
+		goto err_m2m_release;
+
+	wave6_set_default_format(&inst->src_fmt, &inst->dst_fmt);
+	inst->std = wave6_to_codec_std(inst->type, inst->dst_fmt.pixelformat);
+	wave6_update_crop_info(inst, 0, 0, inst->dst_fmt.width, inst->dst_fmt.height);
+	inst->colorspace = V4L2_COLORSPACE_DEFAULT;
+	inst->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+	inst->quantization = V4L2_QUANTIZATION_DEFAULT;
+	inst->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+	inst->enc_ctrls.frame_rate = DEFAULT_FRAME_RATE;
+
+	return 0;
+
+err_m2m_release:
+	v4l2_m2m_ctx_release(inst->v4l2_fh.m2m_ctx);
+	v4l2_ctrl_handler_free(v4l2_ctrl_hdl);
+free_inst:
+	v4l2_fh_del(&inst->v4l2_fh, filp);
+	v4l2_fh_exit(&inst->v4l2_fh);
+	kfree(inst);
+
+	return ret;
+}
+
+static int wave6_vpu_enc_release(struct file *filp)
+{
+	struct vpu_instance *inst = wave6_file_to_vpu_inst(filp);
+
+	wave6_vpu_remove_dbgfs_file(inst);
+	v4l2_m2m_ctx_release(inst->v4l2_fh.m2m_ctx);
+
+	if (inst->state != VPU_INST_STATE_NONE)
+		wave6_vpu_enc_destroy_instance(inst, false);
+
+	v4l2_ctrl_handler_free(&inst->v4l2_ctrl_hdl);
+	v4l2_fh_del(&inst->v4l2_fh, filp);
+	v4l2_fh_exit(&inst->v4l2_fh);
+	kfree(inst);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations wave6_vpu_enc_fops = {
+	.owner = THIS_MODULE,
+	.open = wave6_vpu_open_enc,
+	.release = wave6_vpu_enc_release,
+	.unlocked_ioctl = video_ioctl2,
+	.poll = v4l2_m2m_fop_poll,
+	.mmap = v4l2_m2m_fop_mmap,
+};
+
+int wave6_vpu_enc_register_device(struct vpu_core_device *core)
+{
+	struct video_device *vdev_enc;
+	int ret;
+
+	vdev_enc = devm_kzalloc(core->v4l2_dev.dev, sizeof(*vdev_enc), GFP_KERNEL);
+	if (!vdev_enc)
+		return -ENOMEM;
+
+	core->video_dev_enc = vdev_enc;
+
+	strscpy(vdev_enc->name, VPU_ENC_DEV_NAME, sizeof(vdev_enc->name));
+	vdev_enc->fops = &wave6_vpu_enc_fops;
+	vdev_enc->ioctl_ops = &wave6_vpu_enc_ioctl_ops;
+	vdev_enc->release = video_device_release_empty;
+	vdev_enc->v4l2_dev = &core->v4l2_dev;
+	vdev_enc->vfl_dir = VFL_DIR_M2M;
+	vdev_enc->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+	vdev_enc->lock = &core->dev_lock;
+	video_set_drvdata(vdev_enc, core);
+
+	ret = video_register_device(vdev_enc, VFL_TYPE_VIDEO, -1);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+void wave6_vpu_enc_unregister_device(struct vpu_core_device *core)
+{
+	video_unregister_device(core->video_dev_enc);
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c
new file mode 100644
index 000000000000..a9a168b8bf26
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - v4l2 driver helper interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/clk.h>
+#include <linux/math64.h>
+#include "wave6-vpu.h"
+#include "wave6-vpu-core.h"
+#include "wave6-vpu-dbg.h"
+#include "wave6-trace.h"
+
+void wave6_update_pix_fmt(struct v4l2_pix_format_mplane *pix_mp,
+			  unsigned int width,
+			  unsigned int height)
+{
+	const struct v4l2_format_info *fmt_info;
+	unsigned int stride_y;
+	int i;
+
+	pix_mp->width = width;
+	pix_mp->height = height;
+	pix_mp->flags = 0;
+	pix_mp->field = V4L2_FIELD_NONE;
+	memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved));
+
+	fmt_info = v4l2_format_info(pix_mp->pixelformat);
+	if (!fmt_info) {
+		pix_mp->plane_fmt[0].bytesperline = 0;
+		if (!pix_mp->plane_fmt[0].sizeimage)
+			pix_mp->plane_fmt[0].sizeimage = width * height;
+
+		return;
+	}
+
+	stride_y = width * fmt_info->bpp[0];
+	if (pix_mp->plane_fmt[0].bytesperline <= W6_MAX_PIC_STRIDE)
+		stride_y = max(stride_y, pix_mp->plane_fmt[0].bytesperline);
+	stride_y = round_up(stride_y, W6_PIC_STRIDE_ALIGNMENT);
+	pix_mp->plane_fmt[0].bytesperline = stride_y;
+	pix_mp->plane_fmt[0].sizeimage = stride_y * height;
+
+	stride_y = DIV_ROUND_UP(stride_y, fmt_info->bpp[0]);
+
+	for (i = 1; i < fmt_info->comp_planes; i++) {
+		unsigned int stride_c, sizeimage_c;
+
+		stride_c = DIV_ROUND_UP(stride_y, fmt_info->hdiv) *
+			   fmt_info->bpp[i];
+		sizeimage_c = stride_c * DIV_ROUND_UP(height, fmt_info->vdiv);
+
+		if (fmt_info->mem_planes == 1) {
+			pix_mp->plane_fmt[0].sizeimage += sizeimage_c;
+		} else {
+			pix_mp->plane_fmt[i].bytesperline = stride_c;
+			pix_mp->plane_fmt[i].sizeimage = sizeimage_c;
+		}
+	}
+}
+
+dma_addr_t wave6_get_dma_addr(struct vb2_v4l2_buffer *buf, unsigned int plane_no)
+{
+	return vb2_dma_contig_plane_dma_addr(&buf->vb2_buf, plane_no) +
+			buf->planes[plane_no].data_offset;
+}
+
+struct vb2_v4l2_buffer *wave6_get_dst_buf_by_addr(struct vpu_instance *inst,
+						  dma_addr_t addr)
+{
+	struct vb2_v4l2_buffer *vb2_v4l2_buf;
+	struct v4l2_m2m_buffer *v4l2_m2m_buf;
+	struct vb2_v4l2_buffer *dst_buf = NULL;
+
+	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
+		vb2_v4l2_buf = &v4l2_m2m_buf->vb;
+		if (addr == wave6_get_dma_addr(vb2_v4l2_buf, 0)) {
+			dst_buf = vb2_v4l2_buf;
+			break;
+		}
+	}
+
+	return dst_buf;
+}
+
+enum codec_std wave6_to_codec_std(enum vpu_instance_type type, unsigned int v4l2_pix_fmt)
+{
+	enum codec_std std = STD_UNKNOWN;
+
+	if (v4l2_pix_fmt == V4L2_PIX_FMT_H264)
+		std = (type == VPU_INST_TYPE_DEC) ? W_AVC_DEC : W_AVC_ENC;
+	else if (v4l2_pix_fmt == V4L2_PIX_FMT_HEVC)
+		std = (type == VPU_INST_TYPE_DEC) ? W_HEVC_DEC : W_HEVC_ENC;
+
+	return std;
+}
+
+const char *wave6_vpu_instance_state_name(enum vpu_instance_state state)
+{
+	switch (state) {
+	case VPU_INST_STATE_NONE:
+		return "none";
+	case VPU_INST_STATE_OPEN:
+		return "open";
+	case VPU_INST_STATE_INIT_SEQ:
+		return "init_seq";
+	case VPU_INST_STATE_PIC_RUN:
+		return "pic_run";
+	case VPU_INST_STATE_SEEK:
+		return "seek";
+	case VPU_INST_STATE_STOP:
+		return "stop";
+	default:
+		return "unknown";
+	}
+}
+
+static bool wave6_vpu_valid_instance_transition(struct vpu_instance *inst,
+						enum vpu_instance_state next)
+{
+	bool is_decoder = (inst->type == VPU_INST_TYPE_DEC);
+
+	switch (inst->state) {
+	case VPU_INST_STATE_NONE:
+		/* to OPEN: instance created */
+		if (next == VPU_INST_STATE_OPEN)
+			return true;
+		break;
+	case VPU_INST_STATE_OPEN:
+		if (is_decoder) {
+			/* to NONE: instance destroyed */
+			/* to INIT_SEQ: sequence header parsed */
+			/* to SEEK: output stop_streaming called */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_INIT_SEQ ||
+			    next == VPU_INST_STATE_SEEK)
+				return true;
+		} else {
+			/* to NONE: instance destroyed */
+			/* to INIT_SEQ: encoder parameter configured */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_INIT_SEQ)
+				return true;
+		}
+		break;
+	case VPU_INST_STATE_INIT_SEQ:
+		if (is_decoder) {
+			/* to NONE: instance destroyed */
+			/* to PIC_RUN: frame buffers registered */
+			/* to SEEK: output stop_streaming called */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_PIC_RUN ||
+			    next == VPU_INST_STATE_SEEK)
+				return true;
+		} else {
+			/* to NONE: instance destroyed */
+			/* to PIC_RUN: frame buffers registered */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_PIC_RUN)
+				return true;
+		}
+		break;
+	case VPU_INST_STATE_PIC_RUN:
+		if (is_decoder) {
+			/* to NONE: instance destroyed */
+			/* to INIT_SEQ: new sequence header parsed */
+			/* to SEEK: output stop_streaming called */
+			/* to STOP: decode command failed */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_INIT_SEQ ||
+			    next == VPU_INST_STATE_SEEK ||
+			    next == VPU_INST_STATE_STOP)
+				return true;
+		} else {
+			/* to NONE: instance destroyed */
+			/* to STOP: */
+			/*   encode command failed, encode frame failed */
+			/*   V4L2_ENC_CMD_STOP called, stop_streaming called */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_STOP)
+				return true;
+		}
+		break;
+	case VPU_INST_STATE_SEEK:
+		if (is_decoder) {
+			/* to NONE: instance destroyed */
+			/* to OPEN/INIT_SEQ/PIC_RUN/STOP: */
+			/*   previous state restored on output start_streaming */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_OPEN ||
+			    next == VPU_INST_STATE_INIT_SEQ ||
+			    next == VPU_INST_STATE_PIC_RUN ||
+			    next == VPU_INST_STATE_STOP)
+				return true;
+		}
+		break;
+	case VPU_INST_STATE_STOP:
+		if (is_decoder) {
+			/* to NONE: instance destroyed */
+			/* to SEEK: output stop_streaming called */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_SEEK)
+				return true;
+		} else {
+			/* to NONE: instance destroyed */
+			/* to PIC_RUN: last frame encoded */
+			/* to STOP: */
+			/*   encode command failed, encode frame failed */
+			/*   V4L2_ENC_CMD_STOP called, stop_streaming called */
+			if (next == VPU_INST_STATE_NONE ||
+			    next == VPU_INST_STATE_PIC_RUN ||
+			    next == VPU_INST_STATE_STOP)
+				return true;
+		}
+		break;
+	}
+
+	dev_err(inst->dev->dev, "[%d] invalid transition: %s -> %s\n",
+		inst->id,
+		wave6_vpu_instance_state_name(inst->state),
+		wave6_vpu_instance_state_name(next));
+
+	return false;
+}
+
+void wave6_vpu_set_instance_state(struct vpu_instance *inst,
+				  enum vpu_instance_state state)
+{
+	if (!wave6_vpu_valid_instance_transition(inst, state))
+		return;
+
+	trace_wave6_vpu_set_state(inst, state);
+
+	dev_dbg(inst->dev->dev, "[%d] %s -> %s\n",
+		inst->id,
+		wave6_vpu_instance_state_name(inst->state),
+		wave6_vpu_instance_state_name(state));
+
+	inst->state = state;
+}
+
+u64 wave6_vpu_cycle_to_ns(struct vpu_core_device *core, u64 cycle)
+{
+	unsigned long rate;
+
+	if (!core->num_clks || !core->clks)
+		return 0;
+
+	rate = clk_get_rate(core->clks[0].clk);
+	if (!rate)
+		return 0;
+
+	return (cycle * NSEC_PER_SEC) / rate;
+}
+
+int wave6_vpu_wait_interrupt(struct vpu_instance *inst, unsigned int timeout)
+{
+	int ret;
+
+	ret = wait_for_completion_timeout(&inst->irq_done,
+					  msecs_to_jiffies(timeout));
+	if (!ret)
+		return -ETIMEDOUT;
+
+	reinit_completion(&inst->irq_done);
+
+	return 0;
+}
+
+int wave6_vpu_subscribe_event(struct v4l2_fh *fh,
+			      const struct v4l2_event_subscription *sub)
+{
+	struct vpu_instance *inst = wave6_fh_to_vpu_inst(fh);
+	bool is_decoder = inst->type == VPU_INST_TYPE_DEC;
+
+	dev_dbg(inst->dev->dev, "%s: [%s] type: %d id: %d | flags: %d\n",
+		__func__, is_decoder ? "decoder" : "encoder", sub->type,
+		sub->id, sub->flags);
+
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		if (is_decoder)
+			return v4l2_src_change_event_subscribe(fh, sub);
+		return -EINVAL;
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subscribe_event(fh, sub);
+	default:
+		return -EINVAL;
+	}
+}
+
+void wave6_vpu_return_buffers(struct vpu_instance *inst,
+			      unsigned int type, enum vb2_buffer_state state)
+{
+	struct vb2_v4l2_buffer *buf;
+	int i;
+
+	if (V4L2_TYPE_IS_OUTPUT(type)) {
+		while ((buf = v4l2_m2m_src_buf_remove(inst->v4l2_fh.m2m_ctx)))
+			v4l2_m2m_buf_done(buf, state);
+	} else {
+		while ((buf = v4l2_m2m_dst_buf_remove(inst->v4l2_fh.m2m_ctx))) {
+			for (i = 0; i < inst->dst_fmt.num_planes; i++)
+				vb2_set_plane_payload(&buf->vb2_buf, i, 0);
+			v4l2_m2m_buf_done(buf, state);
+		}
+	}
+}
+
+u32 wave6_vpu_get_consumed_fb_num(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *vb2_v4l2_buf;
+	struct v4l2_m2m_buffer *v4l2_m2m_buf;
+	struct vpu_buffer *vpu_buf;
+	u32 num = 0;
+
+	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
+		vb2_v4l2_buf = &v4l2_m2m_buf->vb;
+		vpu_buf = wave6_to_vpu_buf(vb2_v4l2_buf);
+		if (vpu_buf->consumed)
+			num++;
+	}
+
+	return num;
+}
+
+static bool wave6_vpu_check_fb_available(struct vpu_instance *inst)
+{
+	struct vb2_v4l2_buffer *vb2_v4l2_buf;
+	struct v4l2_m2m_buffer *v4l2_m2m_buf;
+	struct vpu_buffer *vpu_buf;
+
+	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
+		vb2_v4l2_buf = &v4l2_m2m_buf->vb;
+		vpu_buf = wave6_to_vpu_buf(vb2_v4l2_buf);
+
+		if (!vpu_buf->used)
+			return true;
+	}
+
+	return false;
+}
+
+static int wave6_vpu_job_ready(void *priv)
+{
+	struct vpu_instance *inst = priv;
+
+	dev_dbg(inst->dev->dev, "[%d]%s: state %d\n",
+		inst->id, __func__, inst->state);
+
+	if (inst->state == VPU_INST_STATE_OPEN)
+		return 1;
+	if (inst->state < VPU_INST_STATE_PIC_RUN)
+		return 0;
+	if (inst->state == VPU_INST_STATE_STOP && inst->eos)
+		return 0;
+	if (!wave6_vpu_check_fb_available(inst))
+		return 0;
+
+	return 1;
+}
+
+static void wave6_vpu_device_run_timeout(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct vpu_core_device *core = container_of(dwork, struct vpu_core_device, task_timer);
+	struct vpu_instance *inst = v4l2_m2m_get_curr_priv(core->m2m_dev);
+	struct vb2_v4l2_buffer *src_buf = NULL;
+	struct vb2_v4l2_buffer *dst_buf = NULL;
+
+	if (!inst)
+		return;
+
+	dev_err(inst->dev->dev, "[%d] sequence %d timeout\n", inst->id, inst->sequence);
+	src_buf = v4l2_m2m_src_buf_remove(inst->v4l2_fh.m2m_ctx);
+	if (src_buf) {
+		v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+		if (inst->type == VPU_INST_TYPE_DEC)
+			inst->processed_buf_num++;
+		inst->error_buf_num++;
+	}
+
+	dst_buf = v4l2_m2m_dst_buf_remove(inst->v4l2_fh.m2m_ctx);
+	if (dst_buf)
+		v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
+
+	vb2_queue_error(v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx));
+	vb2_queue_error(v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx));
+
+	v4l2_m2m_job_finish(inst->dev->m2m_dev, inst->v4l2_fh.m2m_ctx);
+}
+
+static void wave6_vpu_device_run(void *priv)
+{
+	struct vpu_instance *inst = priv;
+	int ret = -EINVAL;
+
+	dev_dbg(inst->dev->dev, "[%d]%s: state %d\n",
+		inst->id, __func__, inst->state);
+
+	if (inst->state < VPU_INST_STATE_PIC_RUN && inst->ops->prepare_process) {
+		inst->ops->prepare_process(inst);
+
+		v4l2_m2m_job_finish(inst->dev->m2m_dev, inst->v4l2_fh.m2m_ctx);
+		return;
+	}
+
+	ret = inst->ops->start_process(inst);
+	if (!ret)
+		schedule_delayed_work(&inst->dev->task_timer, msecs_to_jiffies(W6_VPU_TIMEOUT));
+	else
+		v4l2_m2m_job_finish(inst->dev->m2m_dev, inst->v4l2_fh.m2m_ctx);
+}
+
+void wave6_vpu_finish_job(struct vpu_instance *inst)
+{
+	cancel_delayed_work(&inst->dev->task_timer);
+	v4l2_m2m_job_finish(inst->dev->m2m_dev, inst->v4l2_fh.m2m_ctx);
+}
+
+void wave6_vpu_record_performance_timestamps(struct vpu_instance *inst)
+{
+	if (WARN_ON(!inst))
+		return;
+
+	if (inst->state == VPU_INST_STATE_NONE &&
+	    !inst->performance.ts_start)
+		inst->performance.ts_start = ktime_get_raw();
+
+	if (inst->state == VPU_INST_STATE_PIC_RUN &&
+	    !inst->performance.ts_first)
+		inst->performance.ts_first = ktime_get_raw();
+}
+
+void wave6_vpu_handle_performance(struct vpu_instance *inst, struct vpu_buffer *vpu_buf)
+{
+	s64 latency, time_spent;
+
+	if (WARN_ON(!inst || !vpu_buf))
+		return;
+
+	inst->performance.ts_last = vpu_buf->ts_output;
+
+	latency = vpu_buf->ts_output - vpu_buf->ts_input;
+	time_spent = vpu_buf->ts_finish - vpu_buf->ts_start;
+
+	if (!inst->performance.latency_first)
+		inst->performance.latency_first = latency;
+	inst->performance.latency_max = max_t(s64, latency, inst->performance.latency_max);
+
+	if (!inst->performance.min_process_time)
+		inst->performance.min_process_time = time_spent;
+	else if (inst->performance.min_process_time > time_spent)
+		inst->performance.min_process_time = time_spent;
+
+	if (inst->performance.max_process_time < time_spent)
+		inst->performance.max_process_time = time_spent;
+
+	inst->performance.total_sw_time += time_spent;
+	inst->performance.total_hw_time += vpu_buf->hw_time;
+}
+
+void wave6_vpu_reset_performance(struct vpu_instance *inst)
+{
+	if (WARN_ON(!inst))
+		return;
+
+	if (inst->processed_buf_num) {
+		s64 tmp;
+		s64 fps_act = 0, fps_sw = 0, fps_hw = 0;
+		s64 duration;
+		struct vpu_performance_info *perf = &inst->performance;
+
+		tmp = MSEC_PER_SEC * inst->processed_buf_num;
+		duration = (perf->ts_last - perf->ts_first) / NSEC_PER_MSEC;
+		if (duration > 0)
+			fps_act = DIV_ROUND_CLOSEST(tmp, duration);
+		duration = perf->total_sw_time / NSEC_PER_MSEC;
+		if (duration > 0)
+			fps_sw = DIV_ROUND_CLOSEST(tmp, duration);
+		duration = perf->total_hw_time / NSEC_PER_MSEC;
+		if (duration)
+			fps_hw = DIV_ROUND_CLOSEST(tmp, duration);
+		dev_dbg(inst->dev->dev,
+			"[%d] fps actual: %lld, sw: %lld, hw: %lld, latency(ms) %llu.%06llu\n",
+			inst->id, fps_act, fps_sw, fps_hw,
+			perf->latency_first / NSEC_PER_MSEC,
+			perf->latency_first % NSEC_PER_MSEC);
+	}
+
+	memset(&inst->performance, 0, sizeof(inst->performance));
+}
+
+static const struct v4l2_m2m_ops wave6_vpu_m2m_ops = {
+	.device_run = wave6_vpu_device_run,
+	.job_ready = wave6_vpu_job_ready,
+};
+
+int wave6_vpu_init_m2m_dev(struct vpu_core_device *core)
+{
+	core->m2m_dev = v4l2_m2m_init(&wave6_vpu_m2m_ops);
+	if (IS_ERR(core->m2m_dev)) {
+		dev_err(core->dev, "v4l2_m2m_init fail: %ld\n", PTR_ERR(core->m2m_dev));
+		return PTR_ERR(core->m2m_dev);
+	}
+
+	INIT_DELAYED_WORK(&core->task_timer, wave6_vpu_device_run_timeout);
+
+	return 0;
+}
+
+void wave6_vpu_release_m2m_dev(struct vpu_core_device *core)
+{
+	v4l2_m2m_release(core->m2m_dev);
+}
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 5/9] media: chips-media: wave6: Add Wave6 core driver
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
                   ` (3 preceding siblings ...)
  2026-06-24  7:20 ` [PATCH v6 4/9] media: chips-media: wave6: Add v4l2 m2m driver support Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:38   ` sashiko-bot
  2026-06-24  7:20 ` [PATCH v6 6/9] media: chips-media: wave6: Improve debugging capabilities Nas Chung
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung,
	Ming Qian

Add the core driver for the Chips&Media Wave6 video codec IP.

The hardware contains one control register region and four interface
register regions for a shared video processing engine. This driver
handles the interface register regions, each with its own MMIO range and
interrupt, while relying on the control driver for firmware loading and
shared resource management.

It configures the V4L2 mem2mem devices and communicates with the Wave6
hardware to perform video processing tasks.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
 .../chips-media/wave6/wave6-vpu-core.c        | 437 ++++++++++++++++++
 .../chips-media/wave6/wave6-vpu-core.h        | 126 +++++
 2 files changed, 563 insertions(+)
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-core.h

diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
new file mode 100644
index 000000000000..e54a2a39ff9a
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 core driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/debugfs.h>
+#include <linux/iopoll.h>
+#include "wave6-vpu-core.h"
+#include "wave6-regdefine.h"
+#include "wave6-vpuconfig.h"
+#include "wave6-hw.h"
+#include "wave6-vpu-dbg.h"
+
+#define CREATE_TRACE_POINTS
+#include "wave6-trace.h"
+
+#define WAVE6_VPU_DEBUGFS_DIR "wave6"
+
+static irqreturn_t wave6_vpu_core_irq(int irq, void *dev_id)
+{
+	struct vpu_core_device *core = dev_id;
+	struct vpu_irq irq_info;
+
+	if (!vpu_read_reg(core, W6_VPU_VPU_INT_STS))
+		return IRQ_NONE;
+
+	irq_info.status = vpu_read_reg(core, W6_VPU_VINT_REASON);
+	irq_info.inst_idc = vpu_read_reg(core, W6_RET_INT_INSTANCE_INFO);
+
+	vpu_write_reg(core, W6_RET_INT_INSTANCE_INFO, INT_INSTANCE_INFO_CLEAR);
+	vpu_write_reg(core, W6_VPU_VINT_REASON_CLEAR, irq_info.status);
+	vpu_write_reg(core, W6_VPU_VINT_CLEAR, VINT_CLEAR);
+
+	trace_wave6_vpu_irq(core, irq_info.status, irq_info.inst_idc);
+
+	if (irq_info.status & BIT(W6_INT_BIT_REQ_WORK_BUF)) {
+		if (core->vpu)
+			core->vpu->req_work_buffer(core->vpu, core);
+
+		return IRQ_HANDLED;
+	}
+
+	kfifo_in(&core->irq_fifo, &irq_info, sizeof(struct vpu_irq));
+
+	return IRQ_WAKE_THREAD;
+}
+
+static struct vpu_instance *wave6_vpu_core_get_instance(struct vpu_core_device *core,
+							u32 inst_idc)
+{
+	struct vpu_instance *inst;
+
+	guard(spinlock)(&core->inst_lock);
+
+	list_for_each_entry(inst, &core->instances, list) {
+		if ((BIT(inst->id) & inst_idc) && inst->enable) {
+			atomic_inc(&inst->refcount);
+			return inst;
+		}
+	}
+
+	return NULL;
+}
+
+static void wave6_vpu_core_put_instance(struct vpu_instance *inst)
+{
+	if (!inst)
+		return;
+
+	guard(spinlock)(&inst->dev->inst_lock);
+	atomic_dec_if_positive(&inst->refcount);
+}
+
+void wave6_vpu_enable_instance(struct vpu_instance *inst)
+{
+	scoped_guard(spinlock, &inst->dev->inst_lock) {
+		atomic_set(&inst->refcount, 0);
+		inst->enable = true;
+	}
+}
+
+void wave6_vpu_disable_instance(struct vpu_instance *inst)
+{
+	int count;
+
+	scoped_guard(spinlock, &inst->dev->inst_lock)
+		inst->enable = false;
+
+	if (read_poll_timeout(atomic_read, count, !count,
+			      W6_VPU_POLL_DELAY_US,
+			      W6_VPU_POLL_TIMEOUT,
+			      true, &inst->refcount))
+		dev_dbg(inst->dev->dev, "[%d] disable timeout\n", inst->id);
+}
+
+static irqreturn_t wave6_vpu_core_irq_thread(int irq, void *dev_id)
+{
+	struct vpu_core_device *core = dev_id;
+	struct vpu_instance *inst;
+	struct vpu_irq irq_info;
+
+	while (kfifo_len(&core->irq_fifo)) {
+		bool error = false;
+
+		if (!kfifo_out(&core->irq_fifo, &irq_info, sizeof(struct vpu_irq)))
+			break;
+
+		inst = wave6_vpu_core_get_instance(core, irq_info.inst_idc);
+		if (!inst)
+			continue;
+
+		if ((irq_info.status & BIT(W6_INT_BIT_INIT_SEQ)) ||
+		    (irq_info.status & BIT(W6_INT_BIT_ENC_SET_PARAM))) {
+			complete(&inst->irq_done);
+			wave6_vpu_core_put_instance(inst);
+			continue;
+		}
+
+		if (irq_info.status & BIT(W6_INT_BIT_BSBUF_ERROR))
+			error = true;
+
+		if (inst->ops && inst->ops->finish_process)
+			inst->ops->finish_process(inst, error);
+
+		wave6_vpu_core_put_instance(inst);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void wave6_vpu_core_check_state(struct vpu_core_device *core)
+{
+	u32 val;
+	int ret;
+
+	guard(mutex)(&core->hw_lock);
+
+	ret = read_poll_timeout(vpu_read_reg, val, val != 0,
+				W6_VPU_POLL_DELAY_US, W6_VPU_POLL_TIMEOUT,
+				false, core, W6_VPU_VCPU_CUR_PC);
+	if (ret)
+		return;
+
+	wave6_vpu_enable_interrupt(core);
+	ret = wave6_vpu_get_version(core);
+	if (ret) {
+		dev_err(core->dev, "wave6_vpu_get_version fail\n");
+		return;
+	}
+
+	dev_dbg(core->dev, "product 0x%x, fw_ver %d.%d.%d(r%d), hw_ver 0x%x\n",
+		core->attr.product_code,
+		FW_VERSION_MAJOR(core->attr.fw_version),
+		FW_VERSION_MINOR(core->attr.fw_version),
+		FW_VERSION_REL(core->attr.fw_version),
+		core->attr.fw_revision,
+		core->attr.hw_version);
+
+	if (core->attr.fw_version < core->res->compatible_fw_version)
+		dev_err(core->dev, "fw version is too low (< v%d.%d.%d)\n",
+			FW_VERSION_MAJOR(core->res->compatible_fw_version),
+			FW_VERSION_MINOR(core->res->compatible_fw_version),
+			FW_VERSION_REL(core->res->compatible_fw_version));
+}
+
+void wave6_vpu_core_activate(struct vpu_core_device *core)
+{
+	core->active = true;
+}
+
+static void wave6_vpu_core_wait_activated(struct vpu_core_device *core)
+{
+	if (core->active)
+		wave6_vpu_core_check_state(core);
+}
+
+static int wave6_vpu_core_probe(struct platform_device *pdev)
+{
+	struct vpu_core_device *core;
+	const struct wave6_vpu_core_resource *res;
+	int ret;
+	int irq;
+
+	res = dev_get_platdata(&pdev->dev);
+	if (!res) {
+		dev_err(&pdev->dev, "There is no platform data\n");
+		return -ENODEV;
+	}
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret);
+		return ret;
+	}
+
+	core = devm_kzalloc(&pdev->dev, sizeof(*core), GFP_KERNEL);
+	if (!core)
+		return -ENOMEM;
+
+	ret = devm_mutex_init(&pdev->dev, &core->dev_lock);
+	if (ret)
+		return ret;
+
+	ret = devm_mutex_init(&pdev->dev, &core->hw_lock);
+	if (ret)
+		return ret;
+
+	spin_lock_init(&core->inst_lock);
+	INIT_LIST_HEAD(&core->instances);
+	dev_set_drvdata(&pdev->dev, core);
+	core->dev = &pdev->dev;
+	core->res = res;
+
+	if (pdev->dev.parent->driver && pdev->dev.parent->driver->name &&
+	    !strcmp(pdev->dev.parent->driver->name, WAVE6_VPU_PLATFORM_DRIVER_NAME))
+		core->vpu = dev_get_drvdata(pdev->dev.parent);
+
+	core->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(core->reg_base))
+		return PTR_ERR(core->reg_base);
+
+	ret = devm_clk_bulk_get_all(&pdev->dev, &core->clks);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "failed to get clocks\n");
+
+	core->num_clks = ret;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq,
+					wave6_vpu_core_irq,
+					wave6_vpu_core_irq_thread,
+					0, "vpu_irq", core);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
+		return ret;
+	}
+
+	ret = v4l2_device_register(&pdev->dev, &core->v4l2_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register v4l2_dev: %d\n", ret);
+		return ret;
+	}
+
+	ret = wave6_vpu_init_m2m_dev(core);
+	if (ret)
+		goto err_v4l2_unregister;
+
+	ret = kfifo_alloc(&core->irq_fifo,
+			  MAX_NUM_INSTANCE * sizeof(struct vpu_irq),
+			  GFP_KERNEL);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to allocate fifo\n");
+		goto err_m2m_dev_release;
+	}
+
+	core->temp_vbuf.size = ALIGN(W6_TEMPBUF_SIZE, 4096);
+	ret = wave6_vdi_alloc_dma(core->dev, &core->temp_vbuf);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to allocate temp_vbuf: %d\n", ret);
+		goto err_kfifo_free;
+	}
+
+	core->debugfs = debugfs_lookup(WAVE6_VPU_DEBUGFS_DIR, NULL);
+	if (!IS_ERR_OR_NULL(core->debugfs))
+		dput(core->debugfs);
+	else
+		core->debugfs = debugfs_create_dir(WAVE6_VPU_DEBUGFS_DIR, NULL);
+
+	pm_runtime_enable(&pdev->dev);
+
+	if (core->res->codec_types & WAVE6_IS_DEC) {
+		ret = wave6_vpu_dec_register_device(core);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"failed to register video_dev_dec: %d\n", ret);
+			goto err_temp_vbuf_free;
+		}
+	}
+	if (core->res->codec_types & WAVE6_IS_ENC) {
+		ret = wave6_vpu_enc_register_device(core);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"failed to register video_dev_enc: %d\n", ret);
+			goto err_dec_unreg;
+		}
+	}
+
+	dev_dbg(&pdev->dev, "Added wave6 driver with caps %s %s\n",
+		core->res->codec_types & WAVE6_IS_ENC ? "'ENCODE'" : "",
+		core->res->codec_types & WAVE6_IS_DEC ? "'DECODE'" : "");
+
+	return 0;
+
+err_dec_unreg:
+	if (core->res->codec_types & WAVE6_IS_DEC)
+		wave6_vpu_dec_unregister_device(core);
+err_temp_vbuf_free:
+	pm_runtime_disable(&pdev->dev);
+	wave6_vdi_free_dma(&core->temp_vbuf);
+err_kfifo_free:
+	kfifo_free(&core->irq_fifo);
+err_m2m_dev_release:
+	wave6_vpu_release_m2m_dev(core);
+err_v4l2_unregister:
+	v4l2_device_unregister(&core->v4l2_dev);
+
+	return ret;
+}
+
+static void wave6_vpu_core_remove(struct platform_device *pdev)
+{
+	struct vpu_core_device *core = dev_get_drvdata(&pdev->dev);
+
+	pm_runtime_disable(&pdev->dev);
+
+	wave6_vpu_enc_unregister_device(core);
+	wave6_vpu_dec_unregister_device(core);
+	wave6_vdi_free_dma(&core->temp_vbuf);
+	kfifo_free(&core->irq_fifo);
+	wave6_vpu_release_m2m_dev(core);
+	v4l2_device_unregister(&core->v4l2_dev);
+}
+
+static int wave6_vpu_core_runtime_suspend(struct device *dev)
+{
+	struct vpu_core_device *core = dev_get_drvdata(dev);
+
+	if (WARN_ON(!core))
+		return -ENODEV;
+
+	/*
+	 * Only call parent VPU put_vpu if the core has a parent and is active.
+	 * - core->vpu: prevent access in core without parent VPU.
+	 * - core->active: execute sleep only after m2m streaming is started.
+	 */
+	if (core->vpu && core->active)
+		core->vpu->put_vpu(core->vpu, core);
+
+	if (core->num_clks)
+		clk_bulk_disable_unprepare(core->num_clks, core->clks);
+
+	return 0;
+}
+
+static int wave6_vpu_core_runtime_resume(struct device *dev)
+{
+	struct vpu_core_device *core = dev_get_drvdata(dev);
+	int ret = 0;
+
+	if (WARN_ON(!core))
+		return -ENODEV;
+
+	if (core->num_clks) {
+		ret = clk_bulk_prepare_enable(core->num_clks, core->clks);
+		if (ret) {
+			dev_err(dev, "failed to enable clocks: %d\n", ret);
+			return ret;
+		}
+	}
+
+	/*
+	 * Only call parent VPU get_vpu if the core has a parent and is active.
+	 * - core->vpu: prevent access in core without parent VPU.
+	 * - core->active: execute boot only after m2m streaming is started.
+	 */
+	if (core->vpu && core->active)
+		ret = core->vpu->get_vpu(core->vpu, core);
+
+	if (!ret)
+		wave6_vpu_core_wait_activated(core);
+	else if (core->num_clks)
+		clk_bulk_disable_unprepare(core->num_clks, core->clks);
+
+	return ret;
+}
+
+static int wave6_vpu_core_suspend(struct device *dev)
+{
+	struct vpu_core_device *core = dev_get_drvdata(dev);
+	int ret;
+
+	v4l2_m2m_suspend(core->m2m_dev);
+
+	ret = pm_runtime_force_suspend(dev);
+	if (ret)
+		v4l2_m2m_resume(core->m2m_dev);
+
+	return ret;
+}
+
+static int wave6_vpu_core_resume(struct device *dev)
+{
+	struct vpu_core_device *core = dev_get_drvdata(dev);
+	int ret;
+
+	ret = pm_runtime_force_resume(dev);
+	if (ret)
+		return ret;
+
+	v4l2_m2m_resume(core->m2m_dev);
+
+	return 0;
+}
+
+static const struct dev_pm_ops wave6_vpu_core_pm_ops = {
+	RUNTIME_PM_OPS(wave6_vpu_core_runtime_suspend,
+		       wave6_vpu_core_runtime_resume, NULL)
+	SYSTEM_SLEEP_PM_OPS(wave6_vpu_core_suspend,
+			    wave6_vpu_core_resume)
+};
+
+static struct platform_driver wave6_vpu_core_driver = {
+	.driver = {
+		.name = WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME,
+		.pm = pm_ptr(&wave6_vpu_core_pm_ops),
+	},
+	.probe = wave6_vpu_core_probe,
+	.remove = wave6_vpu_core_remove,
+};
+
+module_platform_driver(wave6_vpu_core_driver);
+MODULE_ALIAS("platform:" WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME);
+MODULE_DESCRIPTION("chips&media Wave6 VPU CORE V4L2 driver");
+MODULE_AUTHOR("CHIPS&MEDIA INC");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-core.h b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.h
new file mode 100644
index 000000000000..779e8a132f57
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 core driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPU_CORE_H__
+#define __WAVE6_VPU_CORE_H__
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+#include "wave6-vpuconfig.h"
+#include "wave6-vpuapi.h"
+
+#define vpu_write_reg(CORE, ADDR, DATA) wave6_vpu_writel(CORE, ADDR, DATA)
+#define vpu_read_reg(CORE, ADDR) wave6_vpu_readl(CORE, ADDR)
+
+struct vpu_buffer {
+	struct v4l2_m2m_buffer v4l2_m2m_buf;
+	bool consumed;
+	bool used;
+	bool error;
+	bool force_key_frame;
+	bool force_frame_qp;
+	u32 force_i_frame_qp;
+	u32 force_p_frame_qp;
+	u32 force_b_frame_qp;
+	ktime_t ts_input;
+	ktime_t ts_start;
+	ktime_t ts_finish;
+	ktime_t ts_output;
+	u64 hw_time;
+	u32 average_qp;
+};
+
+enum vpu_fmt_type {
+	VPU_FMT_TYPE_CODEC	= 0,
+	VPU_FMT_TYPE_RAW	= 1
+};
+
+#define VPU_FMT_FLAG_CBCR_INTERLEAVED	BIT(0)
+#define VPU_FMT_FLAG_CRCB_ORDER		BIT(1)
+#define VPU_FMT_FLAG_10BIT		BIT(2)
+#define VPU_FMT_FLAG_RGB		BIT(3)
+#define VPU_FMT_FLAG_SUPPORT_ROT_MIR	BIT(4)
+
+struct vpu_format {
+	unsigned int v4l2_pix_fmt;
+	unsigned int max_width;
+	unsigned int min_width;
+	unsigned int max_height;
+	unsigned int min_height;
+	unsigned int num_planes;
+	enum frame_buffer_format fb_fmt;
+	enum endian_mode endian;
+	enum csc_format_order csc_fmt_order;
+	unsigned int flags;
+};
+
+static inline struct vpu_instance *wave6_fh_to_vpu_inst(struct v4l2_fh *vfh)
+{
+	return container_of(vfh, struct vpu_instance, v4l2_fh);
+}
+
+static inline struct vpu_instance *wave6_file_to_vpu_inst(struct file *filp)
+{
+	return wave6_fh_to_vpu_inst(file_to_v4l2_fh(filp));
+}
+
+static inline struct vpu_instance *wave6_ctrl_to_vpu_inst(struct v4l2_ctrl *vctrl)
+{
+	return container_of(vctrl->handler, struct vpu_instance, v4l2_ctrl_hdl);
+}
+
+static inline struct vpu_buffer *wave6_to_vpu_buf(struct vb2_v4l2_buffer *vbuf)
+{
+	return container_of(vbuf, struct vpu_buffer, v4l2_m2m_buf.vb);
+}
+
+static inline bool wave6_vpu_both_queues_are_streaming(struct vpu_instance *inst)
+{
+	struct vb2_queue *vq_cap = v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx);
+	struct vb2_queue *vq_out = v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx);
+
+	return vb2_is_streaming(vq_cap) && vb2_is_streaming(vq_out);
+}
+
+u32 wave6_vpu_get_consumed_fb_num(struct vpu_instance *inst);
+void wave6_vpu_core_activate(struct vpu_core_device *core);
+void wave6_update_pix_fmt(struct v4l2_pix_format_mplane *pix_mp,
+			  unsigned int width,
+			  unsigned int height);
+struct vb2_v4l2_buffer *wave6_get_dst_buf_by_addr(struct vpu_instance *inst,
+						  dma_addr_t addr);
+dma_addr_t wave6_get_dma_addr(struct vb2_v4l2_buffer *buf,
+			      unsigned int plane_no);
+enum codec_std wave6_to_codec_std(enum vpu_instance_type type, unsigned int v4l2_pix_fmt);
+const char *wave6_vpu_instance_state_name(enum vpu_instance_state state);
+void wave6_vpu_set_instance_state(struct vpu_instance *inst,
+				  enum vpu_instance_state state);
+u64 wave6_vpu_cycle_to_ns(struct vpu_core_device *core, u64 cycle);
+int wave6_vpu_wait_interrupt(struct vpu_instance *inst, unsigned int timeout);
+int wave6_vpu_dec_register_device(struct vpu_core_device *core);
+void wave6_vpu_dec_unregister_device(struct vpu_core_device *core);
+int wave6_vpu_enc_register_device(struct vpu_core_device *core);
+void wave6_vpu_enc_unregister_device(struct vpu_core_device *core);
+void wave6_vpu_finish_job(struct vpu_instance *inst);
+void wave6_vpu_record_performance_timestamps(struct vpu_instance *inst);
+void wave6_vpu_handle_performance(struct vpu_instance *inst,
+				  struct vpu_buffer *vpu_buf);
+void wave6_vpu_reset_performance(struct vpu_instance *inst);
+int wave6_vpu_init_m2m_dev(struct vpu_core_device *core);
+void wave6_vpu_release_m2m_dev(struct vpu_core_device *core);
+int wave6_vpu_subscribe_event(struct v4l2_fh *fh,
+			      const struct v4l2_event_subscription *sub);
+void wave6_vpu_return_buffers(struct vpu_instance *inst,
+			      unsigned int type, enum vb2_buffer_state state);
+void wave6_vpu_enable_instance(struct vpu_instance *inst);
+void wave6_vpu_disable_instance(struct vpu_instance *inst);
+
+#endif /* __WAVE6_VPU_CORE_H__ */
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 6/9] media: chips-media: wave6: Improve debugging capabilities
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
                   ` (4 preceding siblings ...)
  2026-06-24  7:20 ` [PATCH v6 5/9] media: chips-media: wave6: Add Wave6 core driver Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:36   ` sashiko-bot
  2026-06-24  7:20 ` [PATCH v6 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device Nas Chung
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung,
	Ming Qian

Add debugfs entries and trace events to provide detailed
debugging information.
These enhancements help diagnose issues and improve debugging
capabilities for the Wave6 core driver.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
 .../platform/chips-media/wave6/wave6-trace.h  | 289 ++++++++++++++++++
 .../chips-media/wave6/wave6-vpu-dbg.c         | 177 +++++++++++
 .../chips-media/wave6/wave6-vpu-dbg.h         |  14 +
 3 files changed, 480 insertions(+)
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-trace.h
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h

diff --git a/drivers/media/platform/chips-media/wave6/wave6-trace.h b/drivers/media/platform/chips-media/wave6/wave6-trace.h
new file mode 100644
index 000000000000..2c80923e2f29
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-trace.h
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 driver tracer
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM wave6
+
+#if !defined(__WAVE6_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
+#define __WAVE6_TRACE_H__
+
+#include <linux/tracepoint.h>
+#include <media/videobuf2-v4l2.h>
+
+DECLARE_EVENT_CLASS(wave6_vpu_register_access,
+		    TP_PROTO(struct device *dev, u32 addr, u32 value),
+		    TP_ARGS(dev, addr, value),
+		    TP_STRUCT__entry(__string(name, dev_name(dev))
+				     __field(u32, addr)
+				     __field(u32, value)),
+		    TP_fast_assign(__assign_str(name);
+				   __entry->addr = addr;
+				   __entry->value = value;),
+		    TP_printk("%s:0x%03x 0x%08x",
+			      __get_str(name), __entry->addr, __entry->value));
+
+DEFINE_EVENT(wave6_vpu_register_access, wave6_vpu_writel,
+	     TP_PROTO(struct device *dev, u32 addr, u32 value),
+	     TP_ARGS(dev, addr, value));
+DEFINE_EVENT(wave6_vpu_register_access, wave6_vpu_readl,
+	     TP_PROTO(struct device *dev, u32 addr, u32 value),
+	     TP_ARGS(dev, addr, value));
+
+TRACE_EVENT(wave6_vpu_send_command,
+	    TP_PROTO(struct vpu_core_device *core, u32 id, u32 std, u32 cmd),
+	    TP_ARGS(core, id, std, cmd),
+	    TP_STRUCT__entry(__string(name, dev_name(core->dev))
+			     __field(u32, id)
+			     __field(u32, std)
+			     __field(u32, cmd)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = id;
+			   __entry->std = std;
+			   __entry->cmd = cmd;),
+	    TP_printk("%s: inst id %d, std 0x%x, cmd 0x%x",
+		      __get_str(name), __entry->id,
+		      __entry->std, __entry->cmd));
+
+TRACE_EVENT(wave6_vpu_irq,
+	    TP_PROTO(struct vpu_core_device *core, u32 irq, u32 idc),
+	    TP_ARGS(core, irq, idc),
+	    TP_STRUCT__entry(__string(name, dev_name(core->dev))
+			     __field(u32, irq)
+			     __field(u32, idc)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->irq = irq;
+			   __entry->idc = idc;),
+	    TP_printk("%s: irq 0x%x, idc 0x%x",
+		      __get_str(name), __entry->irq, __entry->idc));
+
+TRACE_EVENT(wave6_vpu_set_state,
+	    TP_PROTO(struct vpu_instance *inst, u32 state),
+	    TP_ARGS(inst, state),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __string(cur_state, wave6_vpu_instance_state_name(inst->state))
+			     __string(nxt_state, wave6_vpu_instance_state_name(state))),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __assign_str(cur_state);
+			   __assign_str(nxt_state);),
+	    TP_printk("%s: inst[%d] set state %s -> %s",
+		      __get_str(name), __entry->id,
+		      __get_str(cur_state), __get_str(nxt_state)));
+
+DECLARE_EVENT_CLASS(wave6_vpu_inst_internal,
+		    TP_PROTO(struct vpu_instance *inst, bool is_out),
+		    TP_ARGS(inst, is_out),
+		    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+				     __field(u32, id)
+				     __string(type, is_out ? "output" : "capture")
+				     __field(u32, pixelformat)
+				     __field(u32, width)
+				     __field(u32, height)
+				     __field(u32, buf_cnt_src)
+				     __field(u32, buf_cnt_dst)
+				     __field(u32, processed_cnt)
+				     __field(u32, error_cnt)),
+		    TP_fast_assign(__assign_str(name);
+				   __entry->id = inst->id;
+				   __assign_str(type);
+				   __entry->pixelformat = is_out ? inst->src_fmt.pixelformat :
+								   inst->dst_fmt.pixelformat;
+				   __entry->width = is_out ? inst->src_fmt.width :
+							     inst->dst_fmt.width;
+				   __entry->height = is_out ? inst->src_fmt.height :
+							      inst->dst_fmt.height;
+				   __entry->buf_cnt_src = inst->queued_src_buf_num;
+				   __entry->buf_cnt_dst = inst->queued_dst_buf_num;
+				   __entry->processed_cnt = inst->processed_buf_num;
+				   __entry->error_cnt = inst->error_buf_num;),
+		    TP_printk("%s: inst[%d] %s %c%c%c%c %dx%d, input %d, %d, process %d, error %d",
+			      __get_str(name), __entry->id, __get_str(type),
+			      __entry->pixelformat,
+			      __entry->pixelformat >> 8,
+			      __entry->pixelformat >> 16,
+			      __entry->pixelformat >> 24,
+			      __entry->width, __entry->height,
+			      __entry->buf_cnt_src, __entry->buf_cnt_dst,
+			      __entry->processed_cnt, __entry->error_cnt));
+
+DEFINE_EVENT(wave6_vpu_inst_internal, wave6_vpu_start_streaming,
+	     TP_PROTO(struct vpu_instance *inst, bool is_out),
+	     TP_ARGS(inst, is_out));
+
+DEFINE_EVENT(wave6_vpu_inst_internal, wave6_vpu_stop_streaming,
+	     TP_PROTO(struct vpu_instance *inst, bool is_out),
+	     TP_ARGS(inst, is_out));
+
+TRACE_EVENT(wave6_vpu_dec_pic,
+	    TP_PROTO(struct vpu_instance *inst, u32 srcidx, u32 size),
+	    TP_ARGS(inst, srcidx, size),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __field(u32, srcidx)
+			     __field(u32, start)
+			     __field(u32, size)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __entry->srcidx = srcidx;
+			   __entry->start = inst->codec_info->dec_info.stream_rd_ptr;
+			   __entry->size = size;),
+	    TP_printk("%s: inst[%d] src[%2d] %8x, %d",
+		      __get_str(name), __entry->id,
+		      __entry->srcidx, __entry->start, __entry->size));
+
+TRACE_EVENT(wave6_vpu_source_change,
+	    TP_PROTO(struct vpu_instance *inst, struct dec_seq_info *info),
+	    TP_ARGS(inst, info),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __field(u32, width)
+			     __field(u32, height)
+			     __field(u32, profile)
+			     __field(u32, level)
+			     __field(u32, tier)
+			     __field(u32, min_fb_cnt)
+			     __field(u32, disp_delay)
+			     __field(u32, quantization)
+			     __field(u32, colorspace)
+			     __field(u32, xfer_func)
+			     __field(u32, ycbcr_enc)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __entry->width = info->pic_width,
+			   __entry->height = info->pic_height,
+			   __entry->profile = info->profile,
+			   __entry->level = info->level;
+			   __entry->tier = info->tier;
+			   __entry->min_fb_cnt = info->min_frame_buffer_count;
+			   __entry->disp_delay = info->frame_buf_delay;
+			   __entry->quantization = inst->quantization;
+			   __entry->colorspace = inst->colorspace;
+			   __entry->xfer_func = inst->xfer_func;
+			   __entry->ycbcr_enc = inst->ycbcr_enc;),
+	    TP_printk("%s: inst[%d] %dx%d profile %d %d %d min_fb %d delay %d color %d %d %d %d",
+		      __get_str(name), __entry->id,
+		      __entry->width, __entry->height,
+		      __entry->profile, __entry->level, __entry->tier,
+		      __entry->min_fb_cnt, __entry->disp_delay,
+		      __entry->quantization, __entry->colorspace,
+		      __entry->xfer_func, __entry->ycbcr_enc));
+
+TRACE_EVENT(wave6_vpu_dec_done,
+	    TP_PROTO(struct vpu_instance *inst, struct dec_output_info *info),
+	    TP_ARGS(inst, info),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __field(u32, dec_flag)
+			     __field(u32, dec_poc)
+			     __field(u32, disp_flag)
+			     __field(u32, disp_cnt)
+			     __field(u32, rel_cnt)
+			     __field(u32, src_ch)
+			     __field(u32, eos)
+			     __field(u32, error)
+			     __field(u32, warn)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __entry->dec_flag = info->frame_decoded;
+			   __entry->dec_poc = info->decoded_poc;
+			   __entry->disp_flag = info->frame_display;
+			   __entry->disp_cnt = info->disp_frame_num;
+			   __entry->rel_cnt = info->release_disp_frame_num;
+			   __entry->src_ch = info->notification_flags & DEC_NOTI_FLAG_SEQ_CHANGE;
+			   __entry->eos = info->stream_end;
+			   __entry->error = info->error_reason;
+			   __entry->warn = info->warn_info;),
+	    TP_printk("%s: inst[%d] dec %d %d disp %d(%d) rel %d src_ch %d eos %d error 0x%x 0x%x",
+		      __get_str(name), __entry->id,
+		      __entry->dec_flag, __entry->dec_poc,
+		      __entry->disp_flag, __entry->disp_cnt,
+		      __entry->rel_cnt,
+		      __entry->src_ch, __entry->eos,
+		      __entry->error, __entry->warn));
+
+TRACE_EVENT(wave6_vpu_enc_pic,
+	    TP_PROTO(struct vpu_instance *inst, struct enc_param *param),
+	    TP_ARGS(inst, param),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __field(u32, srcidx)
+			     __field(u32, buf_y)
+			     __field(u32, buf_cb)
+			     __field(u32, buf_cr)
+			     __field(u32, stride)
+			     __field(u32, buf_strm)
+			     __field(u32, size_strm)
+			     __field(u32, force_type_enable)
+			     __field(u32, force_type)
+			     __field(u32, end_flag)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __entry->srcidx = param->src_idx;
+			   __entry->buf_y = param->source_frame->buf_y;
+			   __entry->buf_cb = param->source_frame->buf_cb;
+			   __entry->buf_cr = param->source_frame->buf_cr;
+			   __entry->stride = param->source_frame->stride;
+			   __entry->buf_strm = param->pic_stream_buffer_addr;
+			   __entry->size_strm = param->pic_stream_buffer_size;
+			   __entry->force_type_enable = param->force_pic;
+			   __entry->force_type = param->force_pic_type;
+			   __entry->end_flag = param->src_end;),
+	    TP_printk("%s: inst[%d] src[%2d] %8x %8x %8x(%d) dst %8x(%d) force type %d(%d) end %d",
+		      __get_str(name), __entry->id, __entry->srcidx,
+		      __entry->buf_y, __entry->buf_cb, __entry->buf_cr,
+		      __entry->stride, __entry->buf_strm, __entry->size_strm,
+		      __entry->force_type_enable, __entry->force_type,
+		      __entry->end_flag));
+
+TRACE_EVENT(wave6_vpu_enc_done,
+	    TP_PROTO(struct vpu_instance *inst, struct enc_output_info *info),
+	    TP_ARGS(inst, info),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __field(u32, srcidx)
+			     __field(u32, frmidx)
+			     __field(u32, size)
+			     __field(u32, type)
+			     __field(u32, avg_qp)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __entry->srcidx = info->enc_src_idx;
+			   __entry->frmidx = info->recon_frame_index;
+			   __entry->size = info->bitstream_size;
+			   __entry->type = info->pic_type;
+			   __entry->avg_qp = info->avg_ctu_qp;),
+	    TP_printk("%s: inst[%d] src %d, frame %d, size %d, type %d, qp %d, eos %d",
+		      __get_str(name), __entry->id,
+		      __entry->srcidx, __entry->frmidx,
+		      __entry->size, __entry->type, __entry->avg_qp,
+		      __entry->frmidx == RECON_IDX_FLAG_ENC_END));
+
+TRACE_EVENT(wave6_vpu_s_ctrl,
+	    TP_PROTO(struct vpu_instance *inst, struct v4l2_ctrl *ctrl),
+	    TP_ARGS(inst, ctrl),
+	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
+			     __field(u32, id)
+			     __string(ctrl_name, ctrl->name)
+			     __field(u32, val)),
+	    TP_fast_assign(__assign_str(name);
+			   __entry->id = inst->id;
+			   __assign_str(ctrl_name);
+			   __entry->val = ctrl->val;),
+	    TP_printk("%s: inst[%d] %s = %d",
+		      __get_str(name), __entry->id,
+		      __get_str(ctrl_name), __entry->val));
+
+#endif /* __WAVE6_TRACE_H__ */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE wave6-trace
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
new file mode 100644
index 000000000000..99a68fe4f999
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - debug interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/types.h>
+#include <linux/debugfs.h>
+#include "wave6-vpu-core.h"
+#include "wave6-vpu-dbg.h"
+
+static int wave6_vpu_dbg_instance(struct seq_file *s, void *data)
+{
+	struct vpu_instance *inst = s->private;
+	struct vpu_performance_info *perf = &inst->performance;
+	struct vb2_queue *vq;
+	s64 tmp;
+	s64 fps;
+
+	if (!inst->v4l2_fh.m2m_ctx)
+		return 0;
+
+	seq_printf(s, "[%s]\n",
+		   inst->type == VPU_INST_TYPE_DEC ? "Decoder" : "Encoder");
+
+	seq_printf(s, "%s : product 0x%x, fw_ver %d.%d.%d(r%d), hw_ver 0x%x\n",
+		   dev_name(inst->dev->dev),
+		   inst->dev->attr.product_code,
+		   FW_VERSION_MAJOR(inst->dev->attr.fw_version),
+		   FW_VERSION_MINOR(inst->dev->attr.fw_version),
+		   FW_VERSION_REL(inst->dev->attr.fw_version),
+		   inst->dev->attr.fw_revision,
+		   inst->dev->attr.hw_version);
+
+	seq_printf(s, "state = %s\n",
+		   wave6_vpu_instance_state_name(inst->state));
+
+	vq = v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx);
+	seq_printf(s, "output (%2d, %2d): fmt = %c%c%c%c %d x %d, %d;\n",
+		   vb2_is_streaming(vq),
+		   vb2_get_num_buffers(vq),
+		   inst->src_fmt.pixelformat,
+		   inst->src_fmt.pixelformat >> 8,
+		   inst->src_fmt.pixelformat >> 16,
+		   inst->src_fmt.pixelformat >> 24,
+		   inst->src_fmt.width,
+		   inst->src_fmt.height,
+		   vq->last_buffer_dequeued);
+
+	vq = v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx);
+	seq_printf(s, "capture(%2d, %2d): fmt = %c%c%c%c %d x %d, %d;\n",
+		   vb2_is_streaming(vq),
+		   vb2_get_num_buffers(vq),
+		   inst->dst_fmt.pixelformat,
+		   inst->dst_fmt.pixelformat >> 8,
+		   inst->dst_fmt.pixelformat >> 16,
+		   inst->dst_fmt.pixelformat >> 24,
+		   inst->dst_fmt.width,
+		   inst->dst_fmt.height,
+		   vq->last_buffer_dequeued);
+
+	seq_printf(s, "crop: (%d, %d) %d x %d\n",
+		   inst->crop.left,
+		   inst->crop.top,
+		   inst->crop.width,
+		   inst->crop.height);
+
+	if (inst->scaler_info.enable)
+		seq_printf(s, "scale: %d x %d\n",
+			   inst->scaler_info.width, inst->scaler_info.height);
+
+	seq_printf(s, "queued src %d, dst %d, process %d, sequence %d, error %d, drain %d:%d\n",
+		   inst->queued_src_buf_num,
+		   inst->queued_dst_buf_num,
+		   inst->processed_buf_num,
+		   inst->sequence,
+		   inst->error_buf_num,
+		   inst->v4l2_fh.m2m_ctx->out_q_ctx.buffered,
+		   inst->eos);
+
+	seq_puts(s, "fps");
+	tmp = MSEC_PER_SEC * inst->processed_buf_num;
+	if (perf->ts_last > perf->ts_first + NSEC_PER_MSEC) {
+		fps = DIV_ROUND_CLOSEST(tmp, (perf->ts_last - perf->ts_first) / NSEC_PER_MSEC);
+		seq_printf(s, " actual: %lld;", fps);
+	}
+	if (perf->total_sw_time) {
+		fps = DIV_ROUND_CLOSEST(tmp, perf->total_sw_time / NSEC_PER_MSEC);
+		seq_printf(s, " sw: %lld;", fps);
+	}
+	if (perf->total_hw_time) {
+		fps = DIV_ROUND_CLOSEST(tmp, perf->total_hw_time / NSEC_PER_MSEC);
+		seq_printf(s, " hw: %lld", fps);
+	}
+	seq_putc(s, '\n');
+
+	seq_printf(s, "latency(ms) first: %llu.%06llu, max %llu.%06llu, setup %llu.%06llu\n",
+		   perf->latency_first / NSEC_PER_MSEC,
+		   perf->latency_first % NSEC_PER_MSEC,
+		   perf->latency_max / NSEC_PER_MSEC,
+		   perf->latency_max % NSEC_PER_MSEC,
+		   (perf->ts_first - perf->ts_start) / NSEC_PER_MSEC,
+		   (perf->ts_first - perf->ts_start) % NSEC_PER_MSEC);
+
+	seq_printf(s, "process frame time(ms) min: %llu.%06llu, max %llu.%06llu\n",
+		   perf->min_process_time / NSEC_PER_MSEC,
+		   perf->min_process_time % NSEC_PER_MSEC,
+		   perf->max_process_time / NSEC_PER_MSEC,
+		   perf->max_process_time % NSEC_PER_MSEC);
+
+	if (inst->type == VPU_INST_TYPE_DEC) {
+		seq_printf(s, "%s order\n",
+			   inst->disp_mode == DISP_MODE_DISP_ORDER ? "display" : "decode");
+	} else {
+		struct enc_info *p_enc_info = &inst->codec_info->enc_info;
+		struct enc_codec_param *param = &p_enc_info->open_param.codec_param;
+
+		seq_printf(s, "profile %d, level %d, tier %d\n",
+			   param->profile, param->level, param->tier);
+
+		seq_printf(s, "frame_rate %d, idr_period %d, intra_period %d\n",
+			   param->frame_rate, param->idr_period, param->intra_period);
+
+		seq_printf(s, "rc %d, mode %d, bitrate %d\n",
+			   param->en_rate_control,
+			   param->rc_mode,
+			   param->bitrate);
+
+		seq_printf(s, "qp %d, i_qp [%d, %d], p_qp [%d, %d], b_qp [%d, %d]\n",
+			   param->qp,
+			   param->min_qp_i, param->max_qp_i,
+			   param->min_qp_p, param->max_qp_p,
+			   param->min_qp_b, param->max_qp_b);
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_dbg_open(struct inode *inode, struct file *filp)
+{
+	return single_open(filp, wave6_vpu_dbg_instance, inode->i_private);
+}
+
+static const struct file_operations wave6_vpu_dbg_fops = {
+	.owner = THIS_MODULE,
+	.open = wave6_vpu_dbg_open,
+	.release = single_release,
+	.read = seq_read,
+	.llseek = seq_lseek,
+};
+
+int wave6_vpu_create_dbgfs_file(struct vpu_instance *inst)
+{
+	char name[64];
+
+	if (WARN_ON(!inst || !inst->dev || IS_ERR_OR_NULL(inst->dev->debugfs)))
+		return -EINVAL;
+
+	scnprintf(name, sizeof(name), "instance.%d", inst->id);
+	inst->debugfs = debugfs_create_file((const char *)name,
+					    0444,
+					    inst->dev->debugfs,
+					    inst,
+					    &wave6_vpu_dbg_fops);
+
+	return 0;
+}
+
+void wave6_vpu_remove_dbgfs_file(struct vpu_instance *inst)
+{
+	if (!inst || !inst->debugfs)
+		return;
+
+	debugfs_remove(inst->debugfs);
+	inst->debugfs = NULL;
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h
new file mode 100644
index 000000000000..6453eb2de76f
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - debug interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPU_DBG_H__
+#define __WAVE6_VPU_DBG_H__
+
+int wave6_vpu_create_dbgfs_file(struct vpu_instance *inst);
+void wave6_vpu_remove_dbgfs_file(struct vpu_instance *inst);
+
+#endif /* __WAVE6_VPU_DBG_H__ */
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
                   ` (5 preceding siblings ...)
  2026-06-24  7:20 ` [PATCH v6 6/9] media: chips-media: wave6: Improve debugging capabilities Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:36   ` sashiko-bot
  2026-06-24  7:20 ` [PATCH v6 8/9] media: chips-media: wave6: Add Wave6 control driver Nas Chung
  2026-06-24  7:20 ` [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node Nas Chung
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung,
	Ming Qian

Add a thermal cooling device for the Wave6 VPU.
The device operates within the Linux thermal framework,
adjusting the VPU performance state based on thermal conditions.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
 .../chips-media/wave6/wave6-vpu-thermal.c     | 139 ++++++++++++++++++
 .../chips-media/wave6/wave6-vpu-thermal.h     |  24 +++
 2 files changed, 163 insertions(+)
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h

diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
new file mode 100644
index 000000000000..91cd826e8119
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 thermal cooling interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ *
+ */
+
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/units.h>
+#include <linux/slab.h>
+#include "wave6-vpu-thermal.h"
+
+static int wave6_vpu_thermal_cooling_update(struct vpu_thermal_cooling *thermal,
+					    int state)
+{
+	unsigned long new_clock_rate;
+	int ret;
+
+	if (state > thermal->thermal_max || !thermal->cooling)
+		return 0;
+
+	new_clock_rate = DIV_ROUND_UP(thermal->freq_table[state], HZ_PER_KHZ);
+	dev_dbg(thermal->dev, "receive cooling state: %d, new clock rate %ld\n",
+		state, new_clock_rate);
+
+	ret = dev_pm_genpd_set_performance_state(thermal->dev, new_clock_rate);
+	if (ret && !((ret == -ENODEV) || (ret == -EOPNOTSUPP))) {
+		dev_err(thermal->dev, "failed to set perf to %lu, ret = %d\n",
+			new_clock_rate, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int wave6_vpu_cooling_get_max_state(struct thermal_cooling_device *cdev,
+					   unsigned long *state)
+{
+	struct vpu_thermal_cooling *thermal = cdev->devdata;
+
+	*state = thermal->thermal_max;
+
+	return 0;
+}
+
+static int wave6_vpu_cooling_get_cur_state(struct thermal_cooling_device *cdev,
+					   unsigned long *state)
+{
+	struct vpu_thermal_cooling *thermal = cdev->devdata;
+
+	*state = thermal->thermal_event;
+
+	return 0;
+}
+
+static int wave6_vpu_cooling_set_cur_state(struct thermal_cooling_device *cdev,
+					   unsigned long state)
+{
+	struct vpu_thermal_cooling *thermal = cdev->devdata;
+
+	thermal->thermal_event = state;
+	wave6_vpu_thermal_cooling_update(thermal, state);
+
+	return 0;
+}
+
+static const struct thermal_cooling_device_ops wave6_cooling_ops = {
+	.get_max_state = wave6_vpu_cooling_get_max_state,
+	.get_cur_state = wave6_vpu_cooling_get_cur_state,
+	.set_cur_state = wave6_vpu_cooling_set_cur_state,
+};
+
+int wave6_vpu_cooling_init(struct device *dev, struct vpu_thermal_cooling *thermal)
+{
+	int i;
+	int num_opps;
+	unsigned long freq;
+	int ret = -EINVAL;
+
+	if (WARN_ON(!thermal || !thermal->dev))
+		return -EINVAL;
+
+	num_opps = dev_pm_opp_get_opp_count(thermal->dev);
+	if (num_opps < 0) {
+		dev_err(thermal->dev, "fail to get pm opp count, ret = %d\n", num_opps);
+		return num_opps;
+	}
+	if (num_opps == 0) {
+		dev_err(thermal->dev, "no OPP entries found\n");
+		return -ENODEV;
+	}
+
+	thermal->freq_table = devm_kcalloc(dev, num_opps,
+					   sizeof(*thermal->freq_table),
+					   GFP_KERNEL);
+	if (!thermal->freq_table) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) {
+		struct dev_pm_opp *opp;
+
+		opp = dev_pm_opp_find_freq_floor(thermal->dev, &freq);
+		if (IS_ERR(opp))
+			break;
+
+		dev_pm_opp_put(opp);
+
+		dev_dbg(thermal->dev, "[%d] = %lu\n", i, freq);
+		if (freq < 100 * HZ_PER_MHZ)
+			break;
+
+		thermal->freq_table[i] = freq;
+		thermal->thermal_max = i;
+	}
+
+	if (!thermal->thermal_max)
+		goto error;
+
+	thermal->thermal_event = 0;
+	thermal->cooling = devm_thermal_of_cooling_device_register(dev,
+								   dev->of_node,
+								   dev_name(thermal->dev),
+								   thermal,
+								   &wave6_cooling_ops);
+	if (IS_ERR(thermal->cooling)) {
+		dev_err(thermal->dev, "register cooling device failed\n");
+		ret = PTR_ERR(thermal->cooling);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	return ret;
+}
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h
new file mode 100644
index 000000000000..7c5e8aed6ef7
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 thermal cooling interface
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ *
+ */
+
+#ifndef __WAVE6_VPU_THERMAL_H__
+#define __WAVE6_VPU_THERMAL_H__
+
+#include <linux/thermal.h>
+
+struct vpu_thermal_cooling {
+	struct device *dev;
+	int thermal_event;
+	int thermal_max;
+	struct thermal_cooling_device *cooling;
+	unsigned long *freq_table;
+};
+
+int wave6_vpu_cooling_init(struct device *dev, struct vpu_thermal_cooling *thermal);
+
+#endif /* __WAVE6_VPU_THERMAL_H__ */
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 8/9] media: chips-media: wave6: Add Wave6 control driver
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
                   ` (6 preceding siblings ...)
  2026-06-24  7:20 ` [PATCH v6 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24  7:41   ` sashiko-bot
  2026-06-24  7:20 ` [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node Nas Chung
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung,
	Ming Qian

Add the control driver for the Chips&Media Wave6 video codec IP.

The hardware contains one control register region and four interface
register regions for a shared video processing engine.

This driver handles the control region and manages shared resources such
as firmware loading, firmware memory allocation, and coordination required
by the interface register regions.

It also instantiates and coordinates with the `wave6-core` child devices
for firmware and power state management.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>
---
 drivers/media/platform/chips-media/Kconfig    |   1 +
 drivers/media/platform/chips-media/Makefile   |   1 +
 .../media/platform/chips-media/wave6/Kconfig  |  17 +
 .../media/platform/chips-media/wave6/Makefile |  17 +
 .../platform/chips-media/wave6/wave6-vpu.c    | 821 ++++++++++++++++++
 .../platform/chips-media/wave6/wave6-vpu.h    | 143 +++
 6 files changed, 1000 insertions(+)
 create mode 100644 drivers/media/platform/chips-media/wave6/Kconfig
 create mode 100644 drivers/media/platform/chips-media/wave6/Makefile
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu.c
 create mode 100644 drivers/media/platform/chips-media/wave6/wave6-vpu.h

diff --git a/drivers/media/platform/chips-media/Kconfig b/drivers/media/platform/chips-media/Kconfig
index ad350eb6b1fc..8ef7fc8029a4 100644
--- a/drivers/media/platform/chips-media/Kconfig
+++ b/drivers/media/platform/chips-media/Kconfig
@@ -4,3 +4,4 @@ comment "Chips&Media media platform drivers"
 
 source "drivers/media/platform/chips-media/coda/Kconfig"
 source "drivers/media/platform/chips-media/wave5/Kconfig"
+source "drivers/media/platform/chips-media/wave6/Kconfig"
diff --git a/drivers/media/platform/chips-media/Makefile b/drivers/media/platform/chips-media/Makefile
index 6b5d99de8b54..b9a07a91c9d6 100644
--- a/drivers/media/platform/chips-media/Makefile
+++ b/drivers/media/platform/chips-media/Makefile
@@ -2,3 +2,4 @@
 
 obj-y += coda/
 obj-y += wave5/
+obj-y += wave6/
diff --git a/drivers/media/platform/chips-media/wave6/Kconfig b/drivers/media/platform/chips-media/wave6/Kconfig
new file mode 100644
index 000000000000..63d79c56c7fc
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_WAVE6_VPU
+	tristate "Chips&Media Wave6 Codec Driver"
+	depends on V4L_MEM2MEM_DRIVERS
+	depends on VIDEO_DEV && OF
+	depends on ARCH_MXC || COMPILE_TEST
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_MEM2MEM_DEV
+	select GENERIC_ALLOCATOR
+	help
+	  Chips&Media Wave6 stateful codec driver.
+	  The wave6 driver manages shared resources such as firmware memory.
+	  The wave6-core driver provides encoding and decoding capabilities
+	  for H.264, HEVC, and other video formats.
+	  To compile this driver as modules, choose M here: the
+	  modules will be called wave6 and wave6-core.
diff --git a/drivers/media/platform/chips-media/wave6/Makefile b/drivers/media/platform/chips-media/wave6/Makefile
new file mode 100644
index 000000000000..06f8ac9bef14
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# tell define_trace.h where to find the trace header
+CFLAGS_wave6-vpu-core.o := -I$(src)
+
+wave6-objs += wave6-vpu.o \
+	      wave6-vpu-thermal.o
+obj-$(CONFIG_VIDEO_WAVE6_VPU) += wave6.o
+
+wave6-core-objs += wave6-vpu-core.o \
+		   wave6-vpu-v4l2.o \
+		   wave6-vpu-dbg.o \
+		   wave6-vpuapi.o \
+		   wave6-vpu-dec.o \
+		   wave6-vpu-enc.o \
+		   wave6-hw.o
+obj-$(CONFIG_VIDEO_WAVE6_VPU) += wave6-core.o
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu.c b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
new file mode 100644
index 000000000000..c6efc4578183
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * Wave6 series multi-standard codec IP - wave6 driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/property.h>
+#include <linux/of_irq.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
+#include <linux/genalloc.h>
+
+#include "wave6-vpuconfig.h"
+#include "wave6-regdefine.h"
+#include "wave6-vpu.h"
+
+static const struct wave6_vpu_resource wave633c_data = {
+	.fw_name = "cnm/wave633c_imx9_codec_fw.bin",
+	/* For HEVC, AVC, 4096x4096, 8bit */
+	.sram_size = 0x14800,
+};
+
+static const struct wave6_vpu_core_resource wave633c_core_data = {
+	.codec_types = WAVE633_CODEC_TYPE,
+	.compatible_fw_version = WAVE633_COMPATIBLE_FW_VERSION,
+};
+
+static const char *wave6_vpu_state_name(enum wave6_vpu_state state)
+{
+	switch (state) {
+	case WAVE6_VPU_STATE_OFF:
+		return "off";
+	case WAVE6_VPU_STATE_PREPARE:
+		return "prepare";
+	case WAVE6_VPU_STATE_ON:
+		return "on";
+	case WAVE6_VPU_STATE_SLEEP:
+		return "sleep";
+	default:
+		return "unknown";
+	}
+}
+
+static bool wave6_vpu_valid_transition(struct wave6_vpu_device *vpu,
+				       enum wave6_vpu_state next)
+{
+	switch (vpu->state) {
+	case WAVE6_VPU_STATE_OFF:
+		/* to PREPARE: first boot attempt */
+		/* to ON: already booted before, skipping boot */
+		if (next == WAVE6_VPU_STATE_PREPARE ||
+		    next == WAVE6_VPU_STATE_ON)
+			return true;
+		break;
+	case WAVE6_VPU_STATE_PREPARE:
+		/* to OFF: boot failed */
+		/* to ON: boot successful */
+		if (next == WAVE6_VPU_STATE_OFF ||
+		    next == WAVE6_VPU_STATE_ON)
+			return true;
+		break;
+	case WAVE6_VPU_STATE_ON:
+		/* to OFF: sleep failed */
+		/* to SLEEP: sleep successful */
+		if (next == WAVE6_VPU_STATE_OFF ||
+		    next == WAVE6_VPU_STATE_SLEEP)
+			return true;
+		break;
+	case WAVE6_VPU_STATE_SLEEP:
+		/* to OFF: resume failed */
+		/* to ON: resume successful */
+		if (next == WAVE6_VPU_STATE_OFF ||
+		    next == WAVE6_VPU_STATE_ON)
+			return true;
+		break;
+	}
+
+	dev_err(vpu->dev, "invalid transition: %s -> %s\n",
+		wave6_vpu_state_name(vpu->state), wave6_vpu_state_name(next));
+
+	return false;
+}
+
+static void wave6_vpu_set_state(struct wave6_vpu_device *vpu,
+				enum wave6_vpu_state state)
+{
+	if (!wave6_vpu_valid_transition(vpu, state))
+		return;
+
+	dev_dbg(vpu->dev, "set state: %s -> %s\n",
+		wave6_vpu_state_name(vpu->state), wave6_vpu_state_name(state));
+
+	vpu->state = state;
+}
+
+static int wave6_vpu_wait_busy(struct vpu_core_device *core)
+{
+	u32 val;
+
+	return read_poll_timeout(wave6_vdi_readl, val, !val,
+				 W6_VPU_POLL_DELAY_US, W6_VPU_POLL_TIMEOUT,
+				 false, core->reg_base, W6_VPU_BUSY_STATUS);
+}
+
+static int wave6_vpu_check_result(struct vpu_core_device *core)
+{
+	if (wave6_vdi_readl(core->reg_base, W6_RET_SUCCESS))
+		return 0;
+
+	return wave6_vdi_readl(core->reg_base, W6_RET_FAIL_REASON);
+}
+
+static u32 wave6_vpu_get_code_buf_size(struct wave6_vpu_device *vpu)
+{
+	return min_t(u32, vpu->code_buf.size, W6_MAX_CODE_BUF_SIZE);
+}
+
+static void wave6_vpu_remap_code_buf(struct wave6_vpu_device *vpu)
+{
+	dma_addr_t code_base = vpu->code_buf.dma_addr;
+	u32 i, reg_val;
+
+	for (i = 0; i < wave6_vpu_get_code_buf_size(vpu) / W6_MAX_REMAP_PAGE_SIZE; i++) {
+		reg_val = REMAP_CTRL_ON |
+			  REMAP_CTRL_INDEX(i) |
+			  REMAP_CTRL_PAGE_SIZE_ON |
+			  REMAP_CTRL_PAGE_SIZE(W6_MAX_REMAP_PAGE_SIZE);
+		wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_CTRL_GB, reg_val);
+		wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_VADDR_GB,
+				 i * W6_MAX_REMAP_PAGE_SIZE);
+		wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_PADDR_GB,
+				 code_base + i * W6_MAX_REMAP_PAGE_SIZE);
+	}
+}
+
+static void wave6_vpu_init_code_buf(struct wave6_vpu_device *vpu)
+{
+	if (vpu->code_buf.size < W6_CODE_BUF_SIZE) {
+		dev_warn(vpu->dev,
+			 "code buf size (%zu) is too small\n", vpu->code_buf.size);
+		memset(&vpu->code_buf, 0, sizeof(vpu->code_buf));
+		return;
+	}
+
+	vpu->code_buf.vaddr = devm_memremap(vpu->dev,
+					    vpu->code_buf.phys_addr,
+					    vpu->code_buf.size,
+					    MEMREMAP_WC);
+	if (!vpu->code_buf.vaddr) {
+		memset(&vpu->code_buf, 0, sizeof(vpu->code_buf));
+		return;
+	}
+
+	vpu->code_buf.dma_addr = dma_map_resource(vpu->dev,
+						  vpu->code_buf.phys_addr,
+						  vpu->code_buf.size,
+						  DMA_BIDIRECTIONAL,
+						  0);
+	if (dma_mapping_error(vpu->dev, vpu->code_buf.dma_addr)) {
+		memset(&vpu->code_buf, 0, sizeof(vpu->code_buf));
+		return;
+	}
+}
+
+static void wave6_vpu_allocate_work_buffers(struct wave6_vpu_device *vpu)
+{
+	struct vpu_buf *buf;
+	int i;
+
+	for (i = 0; i < MAX_NUM_INSTANCE; i++) {
+		buf = &vpu->work_buffers[i];
+		buf->size = W637DEC_WORKBUF_SIZE_FOR_CQ;
+
+		if (wave6_vdi_alloc_dma(vpu->dev, buf)) {
+			dev_warn(vpu->dev, "Failed to allocate work_buffers\n");
+			return;
+		}
+
+		vpu->work_buffers_alloc++;
+	}
+}
+
+static void wave6_vpu_free_work_buffers(struct wave6_vpu_device *vpu)
+{
+	int i;
+
+	for (i = 0; i < vpu->work_buffers_alloc; i++)
+		wave6_vdi_free_dma(&vpu->work_buffers[i]);
+
+	vpu->work_buffers_alloc = 0;
+	vpu->work_buffers_avail = 0;
+}
+
+static void wave6_vpu_init_work_buf(struct wave6_vpu_device *vpu,
+				    struct vpu_core_device *core)
+{
+	int ret;
+
+	lockdep_assert_held(&vpu->lock);
+
+	wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+	wave6_vdi_writel(core->reg_base, W6_COMMAND, W6_CMD_INIT_WORK_BUF);
+	wave6_vdi_writel(core->reg_base, W6_VPU_HOST_INT_REQ, HOST_INT_REQ_ON);
+
+	ret = wave6_vpu_wait_busy(core);
+	if (ret) {
+		dev_err(vpu->dev, "init work buf failed\n");
+		return;
+	}
+
+	ret = wave6_vpu_check_result(core);
+	if (ret) {
+		dev_err(vpu->dev, "init work buf failed, reason 0x%x\n", ret);
+		return;
+	}
+
+	vpu->work_buffers_avail = vpu->work_buffers_alloc;
+}
+
+static int wave6_vpu_init_vpu(struct wave6_vpu_device *vpu,
+			      struct vpu_core_device *core)
+{
+	int ret;
+
+	lockdep_assert_held(&vpu->lock);
+
+	/* try init directly as firmware is running */
+	if (wave6_vdi_readl(core->reg_base, W6_VPU_VCPU_CUR_PC))
+		goto init_done;
+
+	wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_PREPARE);
+
+	wave6_vpu_remap_code_buf(vpu);
+
+	wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+	wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_BASE_CORE0,
+			 vpu->sram_buf.dma_addr);
+	wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_SIZE_CORE0,
+			 vpu->sram_buf.size);
+	wave6_vdi_writel(vpu->reg_base, W6_COMMAND_GB, W6_CMD_INIT_VPU);
+	wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_CORE_START_GB,
+			 REMAP_CORE_START_ON);
+
+	ret = wave6_vpu_wait_busy(core);
+	if (ret) {
+		dev_err(vpu->dev, "init vpu timeout\n");
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return -EINVAL;
+	}
+
+	ret = wave6_vpu_check_result(core);
+	if (ret) {
+		dev_err(vpu->dev, "init vpu fail, reason 0x%x\n", ret);
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return -EIO;
+	}
+
+init_done:
+	wave6_vpu_init_work_buf(vpu, core);
+	wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_ON);
+
+	return 0;
+}
+
+static int wave6_vpu_sleep(struct wave6_vpu_device *vpu,
+			   struct vpu_core_device *core)
+{
+	int ret;
+
+	lockdep_assert_held(&vpu->lock);
+
+	if (!wave6_vdi_readl(core->reg_base, W6_VPU_VCPU_CUR_PC)) {
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return 0;
+	}
+
+	wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+	wave6_vdi_writel(core->reg_base, W6_COMMAND, W6_CMD_SLEEP_VPU);
+	wave6_vdi_writel(core->reg_base, W6_VPU_HOST_INT_REQ, HOST_INT_REQ_ON);
+
+	ret = wave6_vpu_wait_busy(core);
+	if (ret) {
+		dev_err(vpu->dev, "sleep vpu timeout\n");
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return -EINVAL;
+	}
+
+	ret = wave6_vpu_check_result(core);
+	if (ret) {
+		dev_err(vpu->dev, "sleep vpu fail, reason 0x%x\n", ret);
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return -EIO;
+	}
+
+	wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_SLEEP);
+
+	return 0;
+}
+
+static int wave6_vpu_wakeup(struct wave6_vpu_device *vpu,
+			    struct vpu_core_device *core)
+{
+	int ret;
+
+	lockdep_assert_held(&vpu->lock);
+
+	/* try wakeup directly as firmware is running */
+	if (wave6_vdi_readl(core->reg_base, W6_VPU_VCPU_CUR_PC))
+		goto wakeup_done;
+
+	wave6_vpu_remap_code_buf(vpu);
+
+	wave6_vdi_writel(core->reg_base, W6_VPU_BUSY_STATUS, BUSY_STATUS_SET);
+	wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_BASE_CORE0,
+			 vpu->sram_buf.dma_addr);
+	wave6_vdi_writel(core->reg_base, W6_CMD_INIT_VPU_SEC_AXI_SIZE_CORE0,
+			 vpu->sram_buf.size);
+	wave6_vdi_writel(vpu->reg_base, W6_COMMAND_GB, W6_CMD_WAKEUP_VPU);
+	wave6_vdi_writel(vpu->reg_base, W6_VPU_REMAP_CORE_START_GB,
+			 REMAP_CORE_START_ON);
+
+	ret = wave6_vpu_wait_busy(core);
+	if (ret) {
+		dev_err(vpu->dev, "wakeup vpu timeout\n");
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return -EINVAL;
+	}
+
+	ret = wave6_vpu_check_result(core);
+	if (ret) {
+		dev_err(vpu->dev, "wakeup vpu fail, reason 0x%x\n", ret);
+		wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_OFF);
+		return -EIO;
+	}
+
+wakeup_done:
+	wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_ON);
+
+	return 0;
+}
+
+static int wave6_vpu_try_boot(struct wave6_vpu_device *vpu,
+			      struct vpu_core_device *core)
+{
+	u32 product_code;
+	int ret;
+
+	lockdep_assert_held(&vpu->lock);
+
+	if (vpu->state != WAVE6_VPU_STATE_OFF && vpu->state != WAVE6_VPU_STATE_SLEEP)
+		return 0;
+
+	product_code = wave6_vdi_readl(core->reg_base, W6_VPU_RET_PRODUCT_CODE);
+	if (!wave6_is_product_w_series(product_code)) {
+		dev_err(vpu->dev, "unknown product : %08x\n", product_code);
+		return -EINVAL;
+	}
+
+	if (vpu->state == WAVE6_VPU_STATE_SLEEP) {
+		ret = wave6_vpu_wakeup(vpu, core);
+		return ret;
+	}
+
+	ret = wave6_vpu_init_vpu(vpu, core);
+
+	return ret;
+}
+
+static int wave6_vpu_get(struct wave6_vpu_device *vpu,
+			 struct vpu_core_device *core)
+{
+	int ret;
+
+	if (WARN_ON(!vpu || !core))
+		return -EINVAL;
+
+	guard(mutex)(&vpu->lock);
+
+	if (!vpu->fw_available)
+		return -EINVAL;
+
+	/* Only the first core executes boot; others return */
+	if (atomic_inc_return(&vpu->core_count) > 1)
+		return 0;
+
+	ret = pm_runtime_resume_and_get(vpu->dev);
+	if (ret)
+		goto error_pm;
+
+	ret = wave6_vpu_try_boot(vpu, core);
+	if (ret)
+		goto error_boot;
+
+	return 0;
+
+error_boot:
+	pm_runtime_put_sync(vpu->dev);
+error_pm:
+	atomic_dec(&vpu->core_count);
+
+	return ret;
+}
+
+static void wave6_vpu_put(struct wave6_vpu_device *vpu,
+			  struct vpu_core_device *core)
+{
+	if (WARN_ON(!vpu || !core))
+		return;
+
+	guard(mutex)(&vpu->lock);
+
+	if (!vpu->fw_available)
+		return;
+
+	/* Only the last core executes sleep; others return */
+	if (atomic_dec_return(&vpu->core_count) > 0)
+		return;
+
+	wave6_vpu_sleep(vpu, core);
+
+	if (!pm_runtime_suspended(vpu->dev))
+		pm_runtime_put_sync(vpu->dev);
+}
+
+static void wave6_vpu_require_work_buffer(struct wave6_vpu_device *vpu,
+					  struct vpu_core_device *core)
+{
+	struct vpu_buf *vb;
+	u32 size;
+
+	if (WARN_ON(!vpu || !core))
+		return;
+
+	size = wave6_vdi_readl(core->reg_base, W6_CMD_SET_WORK_BUF_SIZE);
+	if (!size)
+		return;
+
+	if (WARN_ON(size > W637DEC_WORKBUF_SIZE_FOR_CQ))
+		goto exit;
+
+	if (WARN_ON(vpu->work_buffers_avail == 0))
+		goto exit;
+
+	vpu->work_buffers_avail--;
+	vb = &vpu->work_buffers[vpu->work_buffers_avail];
+
+	wave6_vdi_writel(core->reg_base, W6_CMD_SET_WORK_BUF_ADDR, vb->daddr);
+
+exit:
+	wave6_vdi_writel(core->reg_base, W6_CMD_SET_WORK_BUF_SIZE, SET_WORK_BUF_SIZE_ACK);
+}
+
+static int wave6_vpu_create_cores(struct wave6_vpu_device *vpu)
+{
+	struct device_node *child;
+	int num_cores = 0;
+
+	for_each_available_child_of_node(vpu->dev->of_node, child) {
+		struct platform_device_info info = {};
+		struct platform_device *pdev;
+		struct resource res[2];
+		int irq;
+
+		if (num_cores >= W6_VPU_MAX_NUM_CORE) {
+			of_node_put(child);
+			break;
+		}
+
+		if (of_address_to_resource(child, 0, &res[0])) {
+			dev_warn(vpu->dev, "%pOF: missing reg property\n", child);
+			continue;
+		}
+
+		irq = of_irq_get(child, 0);
+		if (irq < 0) {
+			dev_warn(vpu->dev, "%pOF: missing interrupts property\n", child);
+			continue;
+		}
+		res[1] = DEFINE_RES_IRQ(irq);
+
+		info.fwnode = of_fwnode_handle(child);
+		info.parent = vpu->dev;
+		info.name = WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME;
+		info.id = num_cores;
+		info.dma_mask = DMA_BIT_MASK(32);
+		info.res = res;
+		info.num_res = ARRAY_SIZE(res);
+		info.data = &wave633c_core_data;
+		info.size_data = sizeof(wave633c_core_data);
+
+		pdev = platform_device_register_full(&info);
+		if (IS_ERR(pdev)) {
+			dev_err(vpu->dev, "Failed to register core %d: %ld\n",
+				num_cores, PTR_ERR(pdev));
+			continue;
+		}
+
+		vpu->core_pdevs[num_cores] = pdev;
+		num_cores++;
+	}
+
+	return num_cores;
+}
+
+static void wave6_vpu_destroy_cores(struct wave6_vpu_device *vpu)
+{
+	int i;
+
+	for (i = 0; i < W6_VPU_MAX_NUM_CORE; i++) {
+		struct platform_device *pdev = vpu->core_pdevs[i];
+
+		if (!pdev)
+			continue;
+
+		platform_device_unregister(pdev);
+		vpu->core_pdevs[i] = NULL;
+	}
+}
+
+static void wave6_vpu_release(struct wave6_vpu_device *vpu)
+{
+	guard(mutex)(&vpu->lock);
+
+	vpu->fw_available = false;
+	wave6_vpu_destroy_cores(vpu);
+	wave6_vpu_free_work_buffers(vpu);
+	if (vpu->sram_pool && vpu->sram_buf.vaddr) {
+		dma_unmap_resource(vpu->dev,
+				   vpu->sram_buf.dma_addr,
+				   vpu->sram_buf.size,
+				   DMA_BIDIRECTIONAL,
+				   0);
+		gen_pool_free(vpu->sram_pool,
+			      (unsigned long)vpu->sram_buf.vaddr,
+			      vpu->sram_buf.size);
+	}
+	if (vpu->code_buf.dma_addr)
+		dma_unmap_resource(vpu->dev,
+				   vpu->code_buf.dma_addr,
+				   vpu->code_buf.size,
+				   DMA_BIDIRECTIONAL,
+				   0);
+}
+
+static void wave6_vpu_load_firmware(const struct firmware *fw, void *context)
+{
+	struct wave6_vpu_device *vpu = context;
+
+	guard(mutex)(&vpu->lock);
+
+	if (!fw || !fw->data) {
+		dev_err(vpu->dev, "No firmware.\n");
+		return;
+	}
+
+	if (!vpu->fw_available)
+		goto exit;
+
+	if (fw->size + W6_EXTRA_CODE_BUF_SIZE > wave6_vpu_get_code_buf_size(vpu)) {
+		dev_err(vpu->dev, "firmware size (%zd > %zd) is too big\n",
+			fw->size, vpu->code_buf.size);
+		vpu->fw_available = false;
+		goto exit;
+	}
+
+	memcpy(vpu->code_buf.vaddr, fw->data, fw->size);
+
+	vpu->get_vpu = wave6_vpu_get;
+	vpu->put_vpu = wave6_vpu_put;
+	vpu->req_work_buffer = wave6_vpu_require_work_buffer;
+
+	if (!wave6_vpu_create_cores(vpu)) {
+		dev_err(vpu->dev, "Failed to create VPU cores\n");
+		vpu->fw_available = false;
+	}
+
+exit:
+	release_firmware(fw);
+}
+
+static void wave6_vpu_detach_pm_domains(struct wave6_vpu_device *vpu)
+{
+	if (!vpu->num_pm_domains)
+		return;
+
+	for (int i = 0; i < vpu->num_pm_domains; i++) {
+		struct device *pd_dev = vpu->pd_list->pd_devs[i];
+
+		if (!IS_ERR_OR_NULL(pd_dev) && !pm_runtime_suspended(pd_dev))
+			pm_runtime_force_suspend(pd_dev);
+	}
+
+	dev_pm_domain_detach_list(vpu->pd_list);
+	vpu->pd_list = NULL;
+	vpu->num_pm_domains = 0;
+}
+
+static int wave6_vpu_attach_pm_domains(struct wave6_vpu_device *vpu)
+{
+	int ret;
+
+	vpu->num_pm_domains = of_count_phandle_with_args(vpu->dev->of_node,
+							 "power-domains",
+							 "#power-domain-cells");
+	if (vpu->num_pm_domains < 0) {
+		dev_err(vpu->dev, "No power domains defined for vpu node\n");
+		return vpu->num_pm_domains;
+	}
+
+	if (vpu->num_pm_domains == 1) {
+		/* genpd_dev_pm_attach() attach automatically if count is 1 */
+		vpu->num_pm_domains = 0;
+		return 0;
+	}
+
+	ret = dev_pm_domain_attach_list(vpu->dev, NULL, &vpu->pd_list);
+	if (ret < 0) {
+		dev_err(vpu->dev, "Can't attach pm domains, ret = %d\n", ret);
+		return ret;
+	}
+
+	vpu->num_pm_domains = ret;
+
+	return 0;
+}
+
+static struct device *wave6_vpu_get_performance_dev(struct wave6_vpu_device *vpu)
+{
+	int index;
+
+	if (!vpu->num_pm_domains)
+		return vpu->dev;
+
+	index = of_property_match_string(vpu->dev->of_node,
+					 "power-domain-names", "perf");
+	if (index < 0 || index >= vpu->num_pm_domains)
+		return NULL;
+
+	return vpu->pd_list->pd_devs[index];
+}
+
+static int wave6_vpu_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	struct wave6_vpu_device *vpu;
+	const struct wave6_vpu_resource *res;
+	int ret;
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret);
+		return ret;
+	}
+
+	res = device_get_match_data(&pdev->dev);
+	if (!res)
+		return -ENODEV;
+
+	vpu = devm_kzalloc(&pdev->dev, sizeof(*vpu), GFP_KERNEL);
+	if (!vpu)
+		return -ENOMEM;
+
+	ret = devm_mutex_init(&pdev->dev, &vpu->lock);
+	if (ret)
+		return ret;
+
+	atomic_set(&vpu->core_count, 0);
+	dev_set_drvdata(&pdev->dev, vpu);
+	vpu->dev = &pdev->dev;
+	vpu->res = res;
+	vpu->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(vpu->reg_base))
+		return PTR_ERR(vpu->reg_base);
+
+	ret = devm_clk_bulk_get_all(&pdev->dev, &vpu->clks);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "failed to get clocks\n");
+
+	vpu->num_clks = ret;
+
+	ret = wave6_vpu_attach_pm_domains(vpu);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed to attach pm domains\n");
+
+	np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
+	if (np) {
+		struct resource mem;
+
+		ret = of_address_to_resource(np, 0, &mem);
+		of_node_put(np);
+		if (!ret) {
+			vpu->code_buf.phys_addr = mem.start;
+			vpu->code_buf.size = resource_size(&mem);
+			wave6_vpu_init_code_buf(vpu);
+		} else {
+			dev_warn(&pdev->dev, "memory-region is not available.\n");
+		}
+	}
+
+	vpu->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0);
+	if (vpu->sram_pool) {
+		vpu->sram_buf.size = vpu->res->sram_size;
+		vpu->sram_buf.vaddr = gen_pool_dma_alloc(vpu->sram_pool,
+							 vpu->sram_buf.size,
+							 &vpu->sram_buf.phys_addr);
+		if (!vpu->sram_buf.vaddr) {
+			vpu->sram_buf.size = 0;
+		} else {
+			vpu->sram_buf.dma_addr = dma_map_resource(&pdev->dev,
+								  vpu->sram_buf.phys_addr,
+								  vpu->sram_buf.size,
+								  DMA_BIDIRECTIONAL,
+								  0);
+			if (dma_mapping_error(&pdev->dev, vpu->sram_buf.dma_addr)) {
+				gen_pool_free(vpu->sram_pool,
+					      (unsigned long)vpu->sram_buf.vaddr,
+					      vpu->sram_buf.size);
+				memset(&vpu->sram_buf, 0, sizeof(vpu->sram_buf));
+			}
+		}
+	}
+
+	vpu->thermal.dev = wave6_vpu_get_performance_dev(vpu);
+	if (vpu->thermal.dev) {
+		ret = wave6_vpu_cooling_init(vpu->dev, &vpu->thermal);
+		if (ret)
+			dev_err(&pdev->dev, "failed to initialize thermal cooling, %d\n", ret);
+	}
+
+	wave6_vpu_allocate_work_buffers(vpu);
+
+	pm_runtime_enable(&pdev->dev);
+	vpu->fw_available = true;
+
+	request_module("platform:%s", WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME);
+
+	ret = firmware_request_nowait_nowarn(THIS_MODULE,
+					     vpu->res->fw_name,
+					     &pdev->dev,
+					     GFP_KERNEL,
+					     vpu,
+					     wave6_vpu_load_firmware);
+	if (ret) {
+		dev_err(&pdev->dev, "request firmware fail, ret = %d\n", ret);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	pm_runtime_disable(&pdev->dev);
+	wave6_vpu_release(vpu);
+	wave6_vpu_detach_pm_domains(vpu);
+
+	return ret;
+}
+
+static void wave6_vpu_remove(struct platform_device *pdev)
+{
+	struct wave6_vpu_device *vpu = dev_get_drvdata(&pdev->dev);
+
+	pm_runtime_disable(vpu->dev);
+	wave6_vpu_release(vpu);
+	wave6_vpu_detach_pm_domains(vpu);
+}
+
+static int wave6_vpu_runtime_suspend(struct device *dev)
+{
+	struct wave6_vpu_device *vpu = dev_get_drvdata(dev);
+
+	clk_bulk_disable_unprepare(vpu->num_clks, vpu->clks);
+
+	return 0;
+}
+
+static int wave6_vpu_runtime_resume(struct device *dev)
+{
+	struct wave6_vpu_device *vpu = dev_get_drvdata(dev);
+
+	return clk_bulk_prepare_enable(vpu->num_clks, vpu->clks);
+}
+
+static const struct dev_pm_ops wave6_vpu_pm_ops = {
+	RUNTIME_PM_OPS(wave6_vpu_runtime_suspend,
+		       wave6_vpu_runtime_resume, NULL)
+	SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+			    pm_runtime_force_resume)
+};
+
+static const struct of_device_id wave6_vpu_ids[] = {
+	{ .compatible = "nxp,imx95-vpu", .data = &wave633c_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, wave6_vpu_ids);
+
+static struct platform_driver wave6_vpu_driver = {
+	.driver = {
+		.name = WAVE6_VPU_PLATFORM_DRIVER_NAME,
+		.of_match_table = wave6_vpu_ids,
+		.pm = pm_ptr(&wave6_vpu_pm_ops),
+	},
+	.probe = wave6_vpu_probe,
+	.remove = wave6_vpu_remove,
+};
+
+module_platform_driver(wave6_vpu_driver);
+MODULE_DESCRIPTION("chips&media Wave6 VPU driver");
+MODULE_AUTHOR("CHIPS&MEDIA INC");
+MODULE_FIRMWARE("cnm/wave633c_imx9_codec_fw.bin");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu.h b/drivers/media/platform/chips-media/wave6/wave6-vpu.h
new file mode 100644
index 000000000000..ec3c9299526b
--- /dev/null
+++ b/drivers/media/platform/chips-media/wave6/wave6-vpu.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Wave6 series multi-standard codec IP - wave6 driver
+ *
+ * Copyright (C) 2025 CHIPS&MEDIA INC
+ */
+
+#ifndef __WAVE6_VPU_H__
+#define __WAVE6_VPU_H__
+
+#include <linux/device.h>
+#include "wave6-vpu-thermal.h"
+#include "wave6-vdi.h"
+#include "wave6-vpuapi.h"
+
+#define WAVE6_VPU_PLATFORM_DRIVER_NAME "wave6-vpu"
+#define WAVE6_VPU_CORE_PLATFORM_DRIVER_NAME "wave6-vpu-core"
+
+struct wave6_vpu_device;
+struct vpu_core_device;
+
+/**
+ * enum wave6_vpu_state - VPU states
+ * @WAVE6_VPU_STATE_OFF:	VPU is powered off
+ * @WAVE6_VPU_STATE_PREPARE:	VPU is booting
+ * @WAVE6_VPU_STATE_ON:		VPU is running
+ * @WAVE6_VPU_STATE_SLEEP:	VPU is in a sleep mode
+ */
+enum wave6_vpu_state {
+	WAVE6_VPU_STATE_OFF,
+	WAVE6_VPU_STATE_PREPARE,
+	WAVE6_VPU_STATE_ON,
+	WAVE6_VPU_STATE_SLEEP
+};
+
+/**
+ * struct wave6_vpu_dma_buf - VPU buffer from reserved memory or gen_pool
+ * @size:	Buffer size
+ * @dma_addr:	Mapped address for device access
+ * @vaddr:	Kernel virtual address
+ * @phys_addr:	Physical address of the reserved memory region or gen_pool
+ *
+ * Represents a buffer allocated from pre-reserved device memory regions or
+ * SRAM via gen_pool_dma_alloc(). Used for code and SRAM buffers only.
+ * Managed by the VPU device.
+ */
+struct wave6_vpu_dma_buf {
+	size_t size;
+	dma_addr_t dma_addr;
+	void *vaddr;
+	phys_addr_t phys_addr;
+};
+
+/**
+ * struct wave6_vpu_resource - VPU device compatible data
+ * @fw_name:	Firmware name for the device
+ * @sram_size:	Required SRAM size
+ */
+struct wave6_vpu_resource {
+	const char *fw_name;
+	u32 sram_size;
+};
+
+#define WAVE6_IS_ENC				BIT(0)
+#define WAVE6_IS_DEC				BIT(1)
+
+#define WAVE633_CODEC_TYPE			(WAVE6_IS_ENC | WAVE6_IS_DEC)
+#define WAVE633_COMPATIBLE_FW_VERSION		0x2010000
+
+/**
+ * struct wave6_vpu_core_resource - VPU CORE device compatible data
+ * @codec_types:		Bitmask of supported codec types
+ * @compatible_fw_version:	Firmware version compatible with driver
+ */
+struct wave6_vpu_core_resource {
+	int codec_types;
+	u32 compatible_fw_version;
+};
+
+/**
+ * struct wave6_vpu_device - VPU driver structure
+ * @get_vpu:		Function pointer, boot or wake the device
+ * @put_vpu:		Function pointer, power off or suspend the device
+ * @req_work_buffer:	Function pointer, request allocation of a work buffer
+ * @dev:		Platform device pointer
+ * @reg_base:		Base address of MMIO registers
+ * @clks:		Array of clock handles
+ * @num_clks:		Number of entries in @clks
+ * @state:		Device state
+ * @lock:		Mutex protecting device data, register access
+ * @fw_available:	Firmware availability flag
+ * @res:		Device compatible data
+ * @sram_pool:		Genalloc pool for SRAM allocations
+ * @sram_buf:		Optional SRAM buffer
+ * @code_buf:		Firmware code buffer
+ * @work_buffers:	Array of work buffers
+ * @work_buffers_alloc:	Number of allocated work buffers
+ * @work_buffers_avail:	Number of available work buffers
+ * @thermal:		Thermal cooling device
+ * @core_count:		Number of available VPU core devices
+ *
+ * @get_vpu, @put_vpu, @req_work_buffer are called by VPU core devices.
+ *
+ * Buffers such as @sram_buf, @code_buf, and @work_buffers are managed
+ * by the VPU device and accessed exclusively by the firmware.
+ */
+struct wave6_vpu_device {
+	int (*get_vpu)(struct wave6_vpu_device *vpu,
+		       struct vpu_core_device *core);
+	void (*put_vpu)(struct wave6_vpu_device *vpu,
+			struct vpu_core_device *core);
+	void (*req_work_buffer)(struct wave6_vpu_device *vpu,
+				struct vpu_core_device *core);
+	struct device *dev;
+	void __iomem *reg_base;
+	struct clk_bulk_data *clks;
+	int num_clks;
+	enum wave6_vpu_state state;
+	struct mutex lock; /* Protects device data, register access */
+
+	/* Prevents boot or sleep sequence if firmware is unavailable. */
+	bool fw_available;
+
+	const struct wave6_vpu_resource *res;
+	struct gen_pool *sram_pool;
+	struct wave6_vpu_dma_buf sram_buf;
+	struct wave6_vpu_dma_buf code_buf;
+
+	/* Allocates per-instance, used for storing instance-specific data. */
+	struct vpu_buf work_buffers[MAX_NUM_INSTANCE];
+	u32 work_buffers_alloc;
+	u32 work_buffers_avail;
+
+	struct vpu_thermal_cooling thermal;
+	atomic_t core_count;
+
+	int num_pm_domains;
+	struct dev_pm_domain_list *pd_list;
+
+	struct platform_device *core_pdevs[W6_VPU_MAX_NUM_CORE];
+};
+
+#endif /* __WAVE6_VPU_H__ */
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node
  2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
                   ` (7 preceding siblings ...)
  2026-06-24  7:20 ` [PATCH v6 8/9] media: chips-media: wave6: Add Wave6 control driver Nas Chung
@ 2026-06-24  7:20 ` Nas Chung
  2026-06-24 11:50   ` Francesco Dolcini
  8 siblings, 1 reply; 17+ messages in thread
From: Nas Chung @ 2026-06-24  7:20 UTC (permalink / raw)
  To: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer
  Cc: linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut, Nas Chung

Add the Chips and Media wave633 video codec node on IMX95 SoCs.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
---
 .../boot/dts/freescale/imx95-19x19-evk.dts    | 11 ++++++
 arch/arm64/boot/dts/freescale/imx95.dtsi      | 36 +++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
index 041fd838fabb..7edd1c69966a 100644
--- a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
@@ -76,6 +76,11 @@ linux_cma: linux,cma {
 			linux,cma-default;
 			reusable;
 		};
+
+		vpu_boot: memory@a0000000 {
+			reg = <0 0xa0000000 0 0x100000>;
+			no-map;
+		};
 	};
 
 	flexcan1_phy: can-phy0 {
@@ -1142,3 +1147,9 @@ &tpm6 {
 	pinctrl-0 = <&pinctrl_tpm6>;
 	status = "okay";
 };
+
+&vpu {
+	memory-region = <&vpu_boot>;
+	sram = <&sram1>;
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/freescale/imx95.dtsi b/arch/arm64/boot/dts/freescale/imx95.dtsi
index 71394871d8dd..f0b2bc2be907 100644
--- a/arch/arm64/boot/dts/freescale/imx95.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx95.dtsi
@@ -2012,6 +2012,42 @@ vpu_blk_ctrl: clock-controller@4c410000 {
 			assigned-clock-rates = <133333333>, <667000000>, <500000000>;
 		};
 
+		vpu: video-codec@4c4c0000 {
+			compatible = "nxp,imx95-vpu";
+			reg = <0x0 0x4c4c0000 0x0 0x10000>;
+			clocks = <&scmi_clk IMX95_CLK_VPU>,
+				 <&vpu_blk_ctrl IMX95_CLK_VPUBLK_WAVE>;
+			clock-names = "core", "vpublk";
+			power-domains = <&scmi_devpd IMX95_PD_VPU>,
+					<&scmi_perf IMX95_PERF_VPU>;
+			power-domain-names = "vpu", "perf";
+			#cooling-cells = <2>;
+			#address-cells = <2>;
+			#size-cells = <2>;
+			ranges;
+			status = "disabled";
+
+			interface@4c480000 {
+				reg = <0x0 0x4c480000 0x0 0x10000>;
+				interrupts = <GIC_SPI 299 IRQ_TYPE_LEVEL_HIGH>;
+			};
+
+			interface@4c490000 {
+				reg = <0x0 0x4c490000 0x0 0x10000>;
+				interrupts = <GIC_SPI 300 IRQ_TYPE_LEVEL_HIGH>;
+			};
+
+			interface@4c4a0000 {
+				reg = <0x0 0x4c4a0000 0x0 0x10000>;
+				interrupts = <GIC_SPI 301 IRQ_TYPE_LEVEL_HIGH>;
+			};
+
+			interface@4c4b0000 {
+				reg = <0x0 0x4c4b0000 0x0 0x10000>;
+				interrupts = <GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH>;
+			};
+		};
+
 		jpegdec: jpegdec@4c500000 {
 			compatible = "nxp,imx95-jpgdec", "nxp,imx8qxp-jpgdec";
 			reg = <0x0 0x4C500000 0x0 0x00050000>;
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device
  2026-06-24  7:20 ` [PATCH v6 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device Nas Chung
@ 2026-06-24  7:36   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-24  7:36 UTC (permalink / raw)
  To: Nas Chung; +Cc: devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Ignored error return in set_cur_state callback causes thermal framework desynchronization.
- [Medium] Initial thermal state changes during registration are silently dropped due to a check for `!thermal->cooling`.
--

commit f2e1f0ee6d5cf6a10bb958433178a8d9e3143413
Author: Nas Chung <nas.chung@chipsnmedia.com>

media: chips-media: wave6: Add Wave6 thermal cooling device

Add a thermal cooling device for the Wave6 VPU. The device operates within
the Linux thermal framework, adjusting the VPU performance state based on
thermal conditions.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c
> new file mode 100644
> index 0000000000000..91cd826e81195
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-thermal.c

[ ... ]

> +static int wave6_vpu_thermal_cooling_update(struct vpu_thermal_cooling *thermal,
> +					    int state)
> +{
> +	unsigned long new_clock_rate;
> +	int ret;
> +
> +	if (state > thermal->thermal_max || !thermal->cooling)
> +		return 0;

[Severity: Medium]
Will this silently drop initial thermal state changes during registration?

When devm_thermal_of_cooling_device_register() is called, it can synchronously
trigger a state update before the registration function returns and assigns the
pointer to thermal->cooling. Because thermal->cooling is not yet assigned,
this check evaluates to true and skips the hardware update.

[ ... ]

> +static int wave6_vpu_cooling_set_cur_state(struct thermal_cooling_device *cdev,
> +					   unsigned long state)
> +{
> +	struct vpu_thermal_cooling *thermal = cdev->devdata;
> +
> +	thermal->thermal_event = state;
> +	wave6_vpu_thermal_cooling_update(thermal, state);
> +
> +	return 0;
> +}

[Severity: Medium]
Should the return value of wave6_vpu_thermal_cooling_update() be checked before
returning success to the thermal framework?

If wave6_vpu_thermal_cooling_update() fails, this function still unconditionally
returns 0 and updates the local thermal->thermal_event state. This could leave
the thermal framework thinking the hardware state has changed when the hardware
is actually stuck in its previous state.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624072043.238-1-nas.chung@chipsnmedia.com?part=7

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 6/9] media: chips-media: wave6: Improve debugging capabilities
  2026-06-24  7:20 ` [PATCH v6 6/9] media: chips-media: wave6: Improve debugging capabilities Nas Chung
@ 2026-06-24  7:36   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-24  7:36 UTC (permalink / raw)
  To: Nas Chung; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Kernel panic due to division by zero in debugfs FPS calculation.
- [High] Lockless access to `vb2_queue` internals causes Use-After-Free during concurrent `REQBUFS(0)`.
- [Medium] Checking `IS_ERR_OR_NULL` on an optional debugfs dentry inappropriately triggers `WARN_ON` when debugfs is disabled.
- [Medium] 32-bit integer multiplication overflow when calculating FPS statistics.
- [Medium] Data truncation of 64-bit DMA addresses in trace events.
- [Low] Use of 64-bit division and modulo operators breaks compilation on 32-bit architectures.
- [Low] Incorrect chaining of assignments with comma operators in `TP_fast_assign`.
--

commit 59a3991812bdf6b7f45f197b804c2beea6a0e9aa
Author: Nas Chung <nas.chung@chipsnmedia.com>
Subject: media: chips-media: wave6: Improve debugging capabilities

Add debugfs entries and trace events to provide detailed
debugging information.
These enhancements help diagnose issues and improve debugging
capabilities for the Wave6 core driver.

Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
Tested-by: Ming Qian <ming.qian@oss.nxp.com>
Tested-by: Marek Vasut <marek.vasut@mailbox.org>

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-trace.h b/drivers/media/platform/chips-media/wave6/wave6-trace.h
> new file mode 100644
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-trace.h
[ ... ]
> +TRACE_EVENT(wave6_vpu_source_change,
> +	    TP_PROTO(struct vpu_instance *inst, struct dec_seq_info *info),
> +	    TP_ARGS(inst, info),
[ ... ]
> +	    TP_fast_assign(__assign_str(name);
> +			   __entry->id = inst->id;
> +			   __entry->width = info->pic_width,
> +			   __entry->height = info->pic_height,

[Severity: Low]
Is there a typo in these assignments? The width and height fields are
terminated with commas instead of semicolons inside TP_fast_assign.

> +			   __entry->profile = info->profile,
> +			   __entry->level = info->level;
[ ... ]
> +TRACE_EVENT(wave6_vpu_enc_pic,
> +	    TP_PROTO(struct vpu_instance *inst, struct enc_param *param),
> +	    TP_ARGS(inst, param),
> +	    TP_STRUCT__entry(__string(name, dev_name(inst->dev->dev))
> +			     __field(u32, id)
> +			     __field(u32, srcidx)
> +			     __field(u32, buf_y)

[Severity: Medium]
Does this truncate 64-bit physical addresses? 

Variables like buf_y and buf_cb are dma_addr_t, which can be 64-bit on 
platforms like arm64. Declaring these fields as u32 will lose the upper 
32 bits of the address in the trace output.

> +			     __field(u32, buf_cb)
> +			     __field(u32, buf_cr)
> +			     __field(u32, stride)
[ ... ]
> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dbg.c
[ ... ]
> +static int wave6_vpu_dbg_instance(struct seq_file *s, void *data)
> +{
> +	struct vpu_instance *inst = s->private;
> +	struct vpu_performance_info *perf = &inst->performance;
> +	struct vb2_queue *vq;
[ ... ]
> +	vq = v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx);
> +	seq_printf(s, "output (%2d, %2d): fmt = %c%c%c%c %d x %d, %d;\n",
> +		   vb2_is_streaming(vq),
> +		   vb2_get_num_buffers(vq),

[Severity: High]
Can this lead to a use-after-free? 

This accesses the queue state locklessly in wave6_vpu_dbg_instance(). If a
concurrent userspace thread calls VIDIOC_REQBUFS with count=0, the buffers
bitmap is freed.

If the lockless read evaluates vb2_get_num_buffers() at the same time, it 
can race against the release and cause bitmap_weight() to read uninitialized
or freed memory.

> +		   inst->src_fmt.pixelformat,
[ ... ]
> +	seq_puts(s, "fps");
> +	tmp = MSEC_PER_SEC * inst->processed_buf_num;

[Severity: Medium]
Can this multiplication overflow on 32-bit architectures? 

MSEC_PER_SEC and inst->processed_buf_num are both 32-bit integers. The result
will wrap around after about 4.29 million frames before the implicit cast to 
the 64-bit tmp variable.

> +	if (perf->ts_last > perf->ts_first + NSEC_PER_MSEC) {
> +		fps = DIV_ROUND_CLOSEST(tmp, (perf->ts_last - perf->ts_first) / NSEC_PER_MSEC);

[Severity: Low]
Will this break the build on 32-bit platforms? 

The calculation performs direct division using the / operator on 64-bit types, 
which results in undefined references to __divdi3. Should this use a kernel 
math helper like div64_s64() instead?

> +		seq_printf(s, " actual: %lld;", fps);
> +	}
> +	if (perf->total_sw_time) {
> +		fps = DIV_ROUND_CLOSEST(tmp, perf->total_sw_time / NSEC_PER_MSEC);

[Severity: High]
What happens if total_sw_time or total_hw_time is non-zero but less than
1,000,000 nanoseconds?

The division perf->total_sw_time / NSEC_PER_MSEC will evaluate to zero.
Passing zero as the divisor to DIV_ROUND_CLOSEST causes an immediate
division by zero exception and a kernel panic.

> +		seq_printf(s, " sw: %lld;", fps);
> +	}
> +	if (perf->total_hw_time) {
> +		fps = DIV_ROUND_CLOSEST(tmp, perf->total_hw_time / NSEC_PER_MSEC);
> +		seq_printf(s, " hw: %lld", fps);
> +	}
[ ... ]
> +int wave6_vpu_create_dbgfs_file(struct vpu_instance *inst)
> +{
> +	char name[64];
> +
> +	if (WARN_ON(!inst || !inst->dev || IS_ERR_OR_NULL(inst->dev->debugfs)))
> +		return -EINVAL;

[Severity: Medium]
Is it intended to trigger a warning when debugfs is disabled?

If CONFIG_DEBUG_FS is disabled, debugfs_create_dir() returns ERR_PTR(-ENODEV).
Using IS_ERR_OR_NULL() here will trigger the WARN_ON() every time a new
encoder or decoder instance is created, dumping a stack trace into the kernel
log.

> +
> +	scnprintf(name, sizeof(name), "instance.%d", inst->id);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624072043.238-1-nas.chung@chipsnmedia.com?part=6

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 5/9] media: chips-media: wave6: Add Wave6 core driver
  2026-06-24  7:20 ` [PATCH v6 5/9] media: chips-media: wave6: Add Wave6 core driver Nas Chung
@ 2026-06-24  7:38   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-24  7:38 UTC (permalink / raw)
  To: Nas Chung; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [High] Allocating `struct video_device` with `devm_kzalloc()` and an empty release callback causes a Use-After-Free when the driver is unbound while a file descriptor is open.
- [High] Freeing `core->irq_fifo` in `remove()` while the devm-managed IRQ handler is still active creates a use-after-free window.
- [High] Returning early for `W6_INT_BIT_REQ_WORK_BUF` in `wave6_vpu_core_irq` drops other concurrent hardware interrupts.
- [High] The interrupt thread drops completion events because `wave6_vpu_core_get_instance()` only returns the first matching instance.
- [High] Writing 0 to `W6_RET_INT_INSTANCE_INFO` creates a race condition that can drop newly raised interrupts.
- [High] The hardware interrupt handler is registered before the kfifo is allocated, leading to a potential NULL pointer dereference on early interrupts.
- [High] Hardware initialization failures during PM runtime resume are silently ignored, falsely reporting a successful resume.
- [High] Video devices are unregistered after PM runtime is disabled, allowing active file operations to access hardware without power management.
- [High] The IRQ handler unconditionally accesses hardware registers without checking if the device is active.
- [High] `wave6_vpu_disable_instance()` ignores timeouts while waiting for refcount to drop, potentially leading to Use-After-Free and ABA problems with stale events.
- [Medium] The driver creates a debugfs directory but never removes it, causing a memory leak on module unload.
- [Medium] Clocks are left permanently enabled if the driver is removed while the device is runtime-active.
- [Medium] Ignoring the return value of `kfifo_in()` allows interrupts to be silently dropped if the queue fills up.
--

commit dac8dbfd6064814484fc40f423abb75bd9d68a7c
Author: Nas Chung <nas.chung@chipsnmedia.com>

media: chips-media: wave6: Add Wave6 core driver

Add the core driver for the Chips&Media Wave6 video codec IP.
It configures the V4L2 mem2mem devices and communicates with the Wave6
hardware to perform video processing tasks.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
> new file mode 100644
> index 0000000000000..e54a2a39ff9a3
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-core.c
[ ... ]
> +static irqreturn_t wave6_vpu_core_irq(int irq, void *dev_id)
> +{
> +	struct vpu_core_device *core = dev_id;
> +	struct vpu_irq irq_info;
> +
> +	if (!vpu_read_reg(core, W6_VPU_VPU_INT_STS))
> +		return IRQ_NONE;

[Severity: High]
Does this unconditionally access hardware registers without checking if the
device is active?

If a spurious interrupt occurs while the device is runtime-suspended and its
clocks are disabled, this unclocked register read could cause an asynchronous
bus fault on platforms like i.MX95. Should the handler use
pm_runtime_get_if_active() before reading MMIO registers?

> +
> +	irq_info.status = vpu_read_reg(core, W6_VPU_VINT_REASON);
> +	irq_info.inst_idc = vpu_read_reg(core, W6_RET_INT_INSTANCE_INFO);
> +
> +	vpu_write_reg(core, W6_RET_INT_INSTANCE_INFO, INT_INSTANCE_INFO_CLEAR);

[Severity: High]
Does unconditionally writing 0 to W6_RET_INT_INSTANCE_INFO create a race
condition?

If the hardware finishes another operation and sets a new bit in this register
between the read and the write, the write of 0 will clear the newly set bit
before it can be read, permanently dropping the interrupt for that instance.

> +	vpu_write_reg(core, W6_VPU_VINT_REASON_CLEAR, irq_info.status);
> +	vpu_write_reg(core, W6_VPU_VINT_CLEAR, VINT_CLEAR);
> +
> +	trace_wave6_vpu_irq(core, irq_info.status, irq_info.inst_idc);
> +
> +	if (irq_info.status & BIT(W6_INT_BIT_REQ_WORK_BUF)) {
> +		if (core->vpu)
> +			core->vpu->req_work_buffer(core->vpu, core);
> +
> +		return IRQ_HANDLED;
> +	}

[Severity: High]
Will returning early for W6_INT_BIT_REQ_WORK_BUF drop other concurrent
hardware interrupts?

Because the entire irq_info.status mask was already cleared from the hardware,
returning here bypasses the kfifo_in() call. Any other interrupt events (like
DEC_PIC or BSBUF_ERROR) present in the status are never queued to the
interrupt thread and will be permanently lost, potentially hanging the
affected instances.

> +
> +	kfifo_in(&core->irq_fifo, &irq_info, sizeof(struct vpu_irq));

[Severity: Medium]
What happens if the FIFO fills up?

kfifo_in() returns the number of bytes queued (or 0 if full). Ignoring the
return value allows interrupts to be silently dropped if a burst occurs before
the threaded handler can process them, leaving VPU instances waiting
indefinitely. Should this at least log an error on overflow?

> +
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +static struct vpu_instance *wave6_vpu_core_get_instance(struct vpu_core_device *core,
> +							u32 inst_idc)
> +{
> +	struct vpu_instance *inst;
> +
> +	guard(spinlock)(&core->inst_lock);
> +
> +	list_for_each_entry(inst, &core->instances, list) {
> +		if ((BIT(inst->id) & inst_idc) && inst->enable) {
> +			atomic_inc(&inst->refcount);
> +			return inst;
> +		}
> +	}
> +
> +	return NULL;
> +}

[Severity: High]
Does this only return the first matching instance?

The hardware provides a bitmask of interrupting instances in inst_idc. By
iterating and returning only the first matched instance, any other instances
that had their bits set in the same inst_idc mask are completely ignored,
causing them to miss their interrupts and hang indefinitely.

[ ... ]
> +void wave6_vpu_disable_instance(struct vpu_instance *inst)
> +{
> +	int count;
> +
> +	scoped_guard(spinlock, &inst->dev->inst_lock)
> +		inst->enable = false;
> +
> +	if (read_poll_timeout(atomic_read, count, !count,
> +			      W6_VPU_POLL_DELAY_US,
> +			      W6_VPU_POLL_TIMEOUT,
> +			      true, &inst->refcount))
> +		dev_dbg(inst->dev->dev, "[%d] disable timeout\n", inst->id);
> +}

[Severity: High]
Does ignoring the timeout here lead to a potential use-after-free and ABA
problem?

If read_poll_timeout() times out, the function merely logs a debug message and
returns, allowing the caller to proceed to destroy the instance. If the
delayed IRQ thread is still processing the instance, it will access freed
memory.

Additionally, refcount only tracks events actively being processed by the
thread, not un-popped events still in the kfifo. Thus, disable_instance() can
return while events are still in the FIFO. If a new instance is created
reusing the same ID, the IRQ thread could pop the stale event and incorrectly
match it to the new instance.

[ ... ]
> +static void wave6_vpu_core_check_state(struct vpu_core_device *core)
> +{
> +	u32 val;
> +	int ret;
> +
> +	guard(mutex)(&core->hw_lock);
> +
> +	ret = read_poll_timeout(vpu_read_reg, val, val != 0,
> +				W6_VPU_POLL_DELAY_US, W6_VPU_POLL_TIMEOUT,
> +				false, core, W6_VPU_VCPU_CUR_PC);
> +	if (ret)
> +		return;

[Severity: High]
Are hardware initialization failures swallowed here?

This function returns void and drops the error code if read_poll_timeout()
or wave6_vpu_get_version() fails.

[ ... ]
> +static int wave6_vpu_core_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	ret = devm_request_threaded_irq(&pdev->dev, irq,
> +					wave6_vpu_core_irq,
> +					wave6_vpu_core_irq_thread,
> +					0, "vpu_irq", core);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = v4l2_device_register(&pdev->dev, &core->v4l2_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register v4l2_dev: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wave6_vpu_init_m2m_dev(core);
> +	if (ret)
> +		goto err_v4l2_unregister;
> +
> +	ret = kfifo_alloc(&core->irq_fifo,
> +			  MAX_NUM_INSTANCE * sizeof(struct vpu_irq),
> +			  GFP_KERNEL);

[Severity: High]
Could an early interrupt cause a NULL pointer dereference?

devm_request_threaded_irq() is called before kfifo_alloc(). If an interrupt
fires immediately upon registration, wave6_vpu_core_irq() will execute and
attempt to push the event into the unallocated FIFO using kfifo_in(),
leading to a crash.

[ ... ]
> +	core->debugfs = debugfs_lookup(WAVE6_VPU_DEBUGFS_DIR, NULL);
> +	if (!IS_ERR_OR_NULL(core->debugfs))
> +		dput(core->debugfs);
> +	else
> +		core->debugfs = debugfs_create_dir(WAVE6_VPU_DEBUGFS_DIR, NULL);

[Severity: Medium]
Is there a missing debugfs_remove() call for this directory in
wave6_vpu_core_remove()? This leaks the directory on module unload.

> +
> +	pm_runtime_enable(&pdev->dev);
> +
> +	if (core->res->codec_types & WAVE6_IS_DEC) {
> +		ret = wave6_vpu_dec_register_device(core);

[Severity: High]
Does allocating the video_device with devm_kzalloc() inside
wave6_vpu_dec_register_device() cause a use-after-free?

wave6_vpu_dec_register_device() and wave6_vpu_enc_register_device() allocate
the video device via devm_kzalloc() and assign an empty release callback. If
a user holds a file descriptor open and the driver is unbound, devres frees
the video_device memory. When the user later closes the file descriptor, the
V4L2 core accesses the now-freed video_device struct.

[ ... ]
> +static void wave6_vpu_core_remove(struct platform_device *pdev)
> +{
> +	struct vpu_core_device *core = dev_get_drvdata(&pdev->dev);
> +
> +	pm_runtime_disable(&pdev->dev);
> +
> +	wave6_vpu_enc_unregister_device(core);
> +	wave6_vpu_dec_unregister_device(core);

[Severity: High]
Is PM runtime disabled before the user-facing video devices are unregistered?

Unregistering video devices after pm_runtime_disable() allows active file
operations to access hardware without power management. If a user application
issues an ioctl during this window, the driver may attempt to wake the device,
failing and leading to unexpected errors or unpowered hardware access.

[Severity: Medium]
Are clocks left permanently enabled if the driver is removed while the device
is active?

Since this function does not explicitly suspend the device or disable clocks,
and devm_clk_bulk_get_all() only releases the clock references without
disabling them, the device clocks will be left enabled if the driver is
unbound while active (e.g., streaming).

> +	wave6_vdi_free_dma(&core->temp_vbuf);
> +	kfifo_free(&core->irq_fifo);

[Severity: High]
Does manually freeing core->irq_fifo create a use-after-free window?

The IRQ was requested using devm_request_threaded_irq(), meaning it will be
freed during the devres cleanup phase after remove() returns. Manually calling
kfifo_free() here creates a window where the devm-managed IRQ remains active
but the kfifo and DMA memory are already freed. A late or spurious interrupt
during this window will trigger kfifo_in() on freed memory.

> +	wave6_vpu_release_m2m_dev(core);
> +	v4l2_device_unregister(&core->v4l2_dev);
> +}
[ ... ]
> +static int wave6_vpu_core_runtime_resume(struct device *dev)
> +{
[ ... ]
> +	/*
> +	 * Only call parent VPU get_vpu if the core has a parent and is active.
> +	 * - core->vpu: prevent access in core without parent VPU.
> +	 * - core->active: execute boot only after m2m streaming is started.
> +	 */
> +	if (core->vpu && core->active)
> +		ret = core->vpu->get_vpu(core->vpu, core);
> +
> +	if (!ret)
> +		wave6_vpu_core_wait_activated(core);
> +	else if (core->num_clks)
> +		clk_bulk_disable_unprepare(core->num_clks, core->clks);
> +
> +	return ret;
> +}

[Severity: High]
Because wave6_vpu_core_check_state() is void and cannot propagate failures,
wave6_vpu_core_wait_activated() will return without an error if it times out
or fails to get the version.

This means wave6_vpu_core_runtime_resume() will return 0 (success) to the PM
framework even if hardware initialization failed, leaving the device unable to
generate interrupts and hanging subsequent V4L2 operations.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624072043.238-1-nas.chung@chipsnmedia.com?part=5

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 4/9] media: chips-media: wave6: Add v4l2 m2m driver support
  2026-06-24  7:20 ` [PATCH v6 4/9] media: chips-media: wave6: Add v4l2 m2m driver support Nas Chung
@ 2026-06-24  7:40   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-24  7:40 UTC (permalink / raw)
  To: Nas Chung; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] On encoder/decoder start failure, the driver removes the wrong source buffer from the M2M queue, leading to premature buffer completion and Use-After-Free.
- [High] The decoder `VIDIOC_S_FMT` handlers lack `vb2_is_busy()` checks, allowing userspace to dynamically change the pixel format during streaming.
- [High] Missing `vb2_is_busy()` check in encoder's `s_selection` allows changing crop dimensions during streaming, leading to hardware DMA out-of-bounds writes.
- [High] The `video_device` structure is allocated with `devm_kzalloc`, leading to a Use-After-Free on driver unbind.
- [High] A race condition in the global `task_timer` timeout handler leads to Use-After-Free and M2M state corruption.
- [High] Unprotected traversal of `v4l2_m2m` ready queues causes linked list corruption.
- [High] Out-of-bounds array reads and writes due to unvalidated frame buffer and motion vector counts returned directly from hardware registers.
- [High] Missing hardware reset and completion reinitialization on VPU timeout leaves a stray completion that corrupts future hardware syncs.
- [Medium] Conditionally dropping the PM usage counter based on `pm_runtime_suspended()` leaks the usage counter.
- [Medium] Decoder permanently enables the hardware scaler once scaling is requested.
--

commit 85193b153b6b5079480d70d95e569973a1a2732e
Author: Nas Chung <nas.chung@chipsnmedia.com>

media: chips-media: wave6: Add v4l2 m2m driver support

This commit adds V4L2 M2M stateful driver support for the Chips&Media
Wave6 video codec IP, covering both encoder and decoder. It includes
multiplanar format support, scaling, and extensive V4L2 encoder controls
for AVC and HEVC formats.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c
> index 0000000000000..9545264c19b7f 100644
> --- a/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-dec.c

[ ... ]

> @@ -152,6 +152,8 @@ static void wave6_vpu_dec_destroy_instance(struct vpu_instance *inst, bool remov
>  	wave6_vpu_dec_release_fb(inst);
>  	wave6_vpu_set_instance_state(inst, VPU_INST_STATE_NONE);
>  
>  	if (!pm_runtime_suspended(inst->dev->dev))
>  		pm_runtime_put_sync(inst->dev->dev);
>  }

[Severity: Medium]
Will this skip dropping the PM usage counter if the device is runtime suspended?

If the device enters runtime suspend (e.g., via system suspend) and
pm_runtime_suspended() returns true, pm_runtime_put_sync() is skipped. This
seems to leave the usage counter elevated, which could permanently prevent the
VPU from entering runtime suspend again.

[ ... ]

> @@ -219,6 +219,8 @@ static void wave6_vpu_dec_handle_dst_buffer(struct vpu_instance *inst)
>  	int consumed_num = wave6_vpu_get_consumed_fb_num(inst);
>  	int ret;
>  
>  	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
>  		dst_buf = &v4l2_m2m_buf->vb;
>  		vpu_buf = wave6_to_vpu_buf(dst_buf);

[Severity: High]
Does traversing the V4L2 M2M ready queue here require holding the rdy_spinlock?

The v4l2_m2m_for_each_dst_buf() macro iterates over the rdy_queue, which can be
modified concurrently by VIDIOC_QBUF in process context. Traversing this queue
without the rdy_spinlock might result in following stale list pointers and
causing linked list corruption.

[ ... ]

> @@ -598,6 +598,8 @@ static int wave6_vpu_dec_start_decode(struct vpu_instance *inst)
>  		dev_err(inst->dev->dev, "[%d] %s: fail %d\n", inst->id, __func__, ret);
>  		wave6_vpu_set_instance_state(inst, VPU_INST_STATE_STOP);
>  
>  		src_buf = v4l2_m2m_src_buf_remove(inst->v4l2_fh.m2m_ctx);
>  		if (src_buf) {
>  			v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);

[Severity: High]
If start_one_frame fails, could removing the first buffer from the source queue
complete the wrong buffer?

Since the VPU driver allows multiple source buffers to be in flight (such as
for B-frame reordering), the first buffer in the M2M rdy_queue might be
actively processing. Unconditionally popping it via v4l2_m2m_src_buf_remove()
and completing it with VB2_BUF_STATE_ERROR would return an active buffer to
userspace. If userspace then frees or unmaps it, this could lead to a
Use-After-Free or DMA memory corruption.

[ ... ]

> @@ -682,6 +682,8 @@ static int wave6_vpu_dec_s_fmt_cap(struct file *file, void *fh, struct v4l2_form
>  		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
>  		pix_mp->num_planes, pix_mp->colorspace);
>  
>  	ret = wave6_vpu_dec_try_fmt_cap(file, fh, f);
>  	if (ret)
>  		return ret;
>  
>  	vpu_fmt = wave6_find_vpu_fmt(pix_mp->pixelformat, VPU_FMT_TYPE_RAW);

[Severity: High]
Should there be a vb2_is_busy() check here to prevent userspace from changing
the format mid-stream?

Without this check, the format and inst->dst_fmt properties can be modified
during streaming without reallocating buffers. This could cause the driver to
calculate out-of-bounds DMA addresses and corrupt memory.

[ ... ]

> @@ -766,6 +766,8 @@ static int wave6_vpu_dec_s_fmt_out(struct file *file, void *fh, struct v4l2_form
>  		__func__, pix_mp->pixelformat, pix_mp->width, pix_mp->height,
>  		pix_mp->num_planes, pix_mp->colorspace);
>  
>  	ret = wave6_vpu_dec_try_fmt_out(file, fh, f);
>  	if (ret)
>  		return ret;
>  
>  	pix_mp->colorspace = in_pix_mp.colorspace;

[Severity: High]
Does this also need a vb2_is_busy() check to prevent mid-stream format changes
on the output queue?

Altering inst->src_fmt while streaming could lead to similar out-of-bounds
DMA operations.

[ ... ]

> @@ -954,6 +954,8 @@ static int wave6_vpu_dec_s_selection(struct file *file, void *fh, struct v4l2_se
>  		scale_height = round_down(scale_height, step);
>  	}
>  
>  	if (scale_width < inst->src_fmt.width ||
>  	    scale_height < inst->src_fmt.height)
>  		inst->scaler_info.enable = true;
>  
>  	if (inst->scaler_info.enable) {

[Severity: Medium]
If a subsequent call requests a 1:1 scale, does inst->scaler_info.enable ever
get reset to false?

It appears the scaler is permanently enabled once requested. If userspace
reverts to a 1:1 scale later, the hardware scaler remains active, which might
introduce unnecessary overhead or logic errors.

[ ... ]

> @@ -1323,6 +1323,8 @@ static int wave6_vpu_dec_prepare_fb(struct vpu_instance *inst)
>  	luma_size = fb_stride * fb_height;
>  	chroma_size = ALIGN(fb_stride / 2, W6_FBC_BUF_ALIGNMENT) * fb_height;
>  
>  	for (i = 0; i < fb_num; i++) {
>  		struct frame_buffer *frame = &inst->frame_buf[i];
>  		struct vpu_buf *vframe = &inst->frame_vbuf[i];
>  
>  		vframe->size = luma_size + chroma_size;

[Severity: High]
Can fb_num (derived from the hardware register W6_RET_DEC_MIN_FB_CNT) exceed
the WAVE6_MAX_FBS array bounds?

The values min_frame_buffer_count and req_mv_buffer_count are read directly
from hardware in wave6_get_dec_seq_result() without validation. If the
hardware or firmware returns a value larger than the fixed-size array bounds,
this loop could cause out-of-bounds writes to the inst->frame_buf and
inst->frame_vbuf arrays.

[ ... ]

> @@ -1803,6 +1803,8 @@ int wave6_vpu_dec_register_device(struct vpu_core_device *core)
>  	struct video_device *vdev_dec;
>  	int ret;
>  
>  	vdev_dec = devm_kzalloc(core->v4l2_dev.dev, sizeof(*vdev_dec), GFP_KERNEL);
>  	if (!vdev_dec)
>  		return -ENOMEM;
>  
>  	core->video_dev_dec = vdev_dec;

[Severity: High]
Is it safe to allocate the video_device with devm_kzalloc() here?

The V4L2 video_device structure must be freed dynamically in its release
callback (currently assigned video_device_release_empty) when the last file
descriptor is closed. If the device is unbound via sysfs while userspace holds
an open file descriptor, devm_kzalloc will free the memory, but the V4L2 core
will still hold pointers to it. This could result in a Use-After-Free during
subsequent ioctl or close operations.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c
> index 0000000000000..825229f6fb051 100644
> --- a/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-enc.c

[ ... ]

> @@ -422,6 +422,8 @@ static int wave6_allocate_aux_buffer(struct vpu_instance *inst,
>  		return ret;
>  	}
>  
>  	for (i = 0; i < num; i++) {
>  		inst->aux_vbuf[type][i].size = size;
>  		ret = wave6_vdi_alloc_dma(inst->dev->dev, &inst->aux_vbuf[type][i]);
>  		if (ret) {

[Severity: High]
Is the num parameter validated against the array size before this loop?

Unlike the decoder's allocate function which clamps num to WAVE6_MAX_FBS,
the encoder seems to use the unvalidated num (derived from hardware registers)
directly. This could lead to out-of-bounds writes into the aux_vbuf array if
the hardware returns an unexpectedly large value.

[ ... ]

> @@ -783,6 +783,8 @@ static int wave6_vpu_enc_s_selection(struct file *file, void *fh, struct v4l2_se
>  	s->r.width = clamp(s->r.width, W6_MIN_ENC_PIC_WIDTH, max_crop_w);
>  	s->r.height = clamp(s->r.height, W6_MIN_ENC_PIC_HEIGHT, max_crop_h);
>  
>  	wave6_update_pix_fmt(&inst->dst_fmt, s->r.width, s->r.height);
>  	wave6_update_crop_info(inst, s->r.left, s->r.top, s->r.width, s->r.height);
>  
>  	dev_dbg(inst->dev->dev, "V4L2_SEL_TGT_CROP %dx%dx%dx%d\n",

[Severity: High]
Should there be a vb2_is_busy() check before updating the crop dimensions?

Without checking if the queues are busy, userspace can change the crop
dimensions during streaming. If a larger crop selection is set,
inst->codec_rect is updated, and the hardware will be programmed with larger
dimensions than the previously allocated internal FBC backing buffers. This
could result in hardware DMA memory corruption.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c b/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c
> index 0000000000000..a9a168b8bf269 100644
> --- a/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu-v4l2.c

[ ... ]

> @@ -72,6 +72,8 @@ struct vb2_v4l2_buffer *wave6_get_dst_buf_by_addr(struct vpu_instance *inst,
>  	struct v4l2_m2m_buffer *v4l2_m2m_buf;
>  	struct vb2_v4l2_buffer *dst_buf = NULL;
>  
>  	v4l2_m2m_for_each_dst_buf(inst->v4l2_fh.m2m_ctx, v4l2_m2m_buf) {
>  		vb2_v4l2_buf = &v4l2_m2m_buf->vb;
>  		if (addr == wave6_get_dma_addr(vb2_v4l2_buf, 0)) {
>  			dst_buf = vb2_v4l2_buf;

[Severity: High]
Is it safe to iterate over the M2M destination queue without holding the
rdy_spinlock?

The rdy_queue is shared and can be concurrently modified by VIDIOC_QBUF.
Traversing it unprotected could result in following stale list pointers.

[ ... ]

> @@ -312,6 +312,8 @@ int wave6_vpu_wait_interrupt(struct vpu_instance *inst, unsigned int timeout)
>  
>  	ret = wait_for_completion_timeout(&inst->irq_done,
>  					  msecs_to_jiffies(timeout));
>  	if (!ret)
>  		return -ETIMEDOUT;
>  
>  	reinit_completion(&inst->irq_done);

[Severity: High]
Does exiting early on a timeout leave a stray completion signal?

If the timeout expires and the driver returns -ETIMEDOUT without resetting
the hardware or calling reinit_completion(), the VPU might finish the command
later and signal the completion. This stray completion would cause a subsequent
wait_for_completion_timeout() to return immediately, potentially reading result
registers while the hardware is still executing a new command.

[ ... ]

> @@ -419,6 +419,8 @@ static void wave6_vpu_device_run_timeout(struct work_struct *work)
>  	vb2_queue_error(v4l2_m2m_get_src_vq(inst->v4l2_fh.m2m_ctx));
>  	vb2_queue_error(v4l2_m2m_get_dst_vq(inst->v4l2_fh.m2m_ctx));
>  
>  	v4l2_m2m_job_finish(inst->dev->m2m_dev, inst->v4l2_fh.m2m_ctx);
>  }

[Severity: High]
Should the hardware be reset or aborted before finishing the M2M job?

If v4l2_m2m_job_finish() is called while the VPU is actively executing a
timed-out command, an eventual hardware interrupt might corrupt the next job
or trigger a Use-After-Free if the instance has been freed.

[ ... ]

> @@ -442,6 +442,8 @@ static void wave6_vpu_device_run(void *priv)
>  
>  void wave6_vpu_finish_job(struct vpu_instance *inst)
>  {
>  	cancel_delayed_work(&inst->dev->task_timer);
>  	v4l2_m2m_job_finish(inst->dev->m2m_dev, inst->v4l2_fh.m2m_ctx);
>  }

[Severity: High]
Can cancel_delayed_work() leave the timeout handler running concurrently?

Using cancel_delayed_work() instead of cancel_delayed_work_sync() allows the
timeout handler to potentially race with instance destruction or subsequent
jobs.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624072043.238-1-nas.chung@chipsnmedia.com?part=4

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 8/9] media: chips-media: wave6: Add Wave6 control driver
  2026-06-24  7:20 ` [PATCH v6 8/9] media: chips-media: wave6: Add Wave6 control driver Nas Chung
@ 2026-06-24  7:41   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-24  7:41 UTC (permalink / raw)
  To: Nas Chung; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] Concurrent access to work buffer pool without synchronization causes data races and array out-of-bounds access.
- [Critical] Self-deadlock (ABBA) during VPU core unregistration due to holding vpu->lock.
- [Critical] Missing hardware quiescence during device removal allows the VPU to continue DMAing into freed memory.
- [High] Asynchronous firmware loading callback causes a use-after-free if the device is unbound before completion.
- [High] Pointer type mismatch when calling gen_pool_dma_alloc() leads to memory corruption and invalid DMA mapping.
- [High] Disabling PM runtime prematurely leaks clock enable references on the parent device.
- [High] Silent truncation of the firmware code buffer during memory remapping.
- [High] Calling of_irq_get() inside an asynchronous firmware load callback prevents handling -EPROBE_DEFER.
- [High] Failures in wave6_vpu_init_work_buf() are ignored, leading to a broken VPU boot state.
- [High] wave6_vpu_detach_pm_domains() destroys PM domain virtual devices before the devm-managed thermal cooling device is unregistered, creating a UAF window.
- [High] The driver frees the IRQ kfifo before disabling the IRQ during core removal, leading to Use-After-Free.
- [Medium] Conditionally calling pm_runtime_put_sync() based on pm_runtime_suspended() leaks the usage counter.
- [Medium] Checking dma_addr against 0 to determine validity skips unmapping on platforms where 0 is a valid DMA address.
--

commit e4a2fe67f6aa37393c77f5308ac691953a00a6e8
Author: Nas Chung <nas.chung@chipsnmedia.com>
Subject: media: chips-media: wave6: Add Wave6 control driver

This commit adds the control driver for the Chips&Media Wave6 video codec IP.
It handles the control region and manages shared resources such as firmware
loading, memory allocation, and the creation of wave6-core child devices.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpu.c b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
> new file mode 100644
> index 0000000000000..c6efc45781833
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpu.c
[ ... ]
> +static void wave6_vpu_remap_code_buf(struct wave6_vpu_device *vpu)
> +{
> +	dma_addr_t code_base = vpu->code_buf.dma_addr;
> +	u32 i, reg_val;
> +
> +	for (i = 0; i < wave6_vpu_get_code_buf_size(vpu) / W6_MAX_REMAP_PAGE_SIZE; i++) {

[Severity: High]
Does this code silently truncate the firmware code buffer during memory
remapping?

If the buffer size is not an exact multiple of W6_MAX_REMAP_PAGE_SIZE,
the integer division will discard the remainder and the tail end of the
buffer will not be mapped. If the firmware requires the unmapped tail,
could the VPU encounter a memory fault?

[ ... ]
> +static int wave6_vpu_init_vpu(struct wave6_vpu_device *vpu,
> +			      struct vpu_core_device *core)
> +{
[ ... ]
> +init_done:
> +	wave6_vpu_init_work_buf(vpu, core);
> +	wave6_vpu_set_state(vpu, WAVE6_VPU_STATE_ON);
> +
> +	return 0;
> +}

[Severity: High]
Is the return value of wave6_vpu_init_work_buf() intentionally ignored here?

If wave6_vpu_wait_busy() or wave6_vpu_check_result() fails inside
wave6_vpu_init_work_buf(), it will return early without setting
vpu->work_buffers_avail. Because the failure is ignored, the VPU state
transitions to WAVE6_VPU_STATE_ON anyway, and later requests for work
buffers might feed uninitialized addresses to the hardware.

[ ... ]
> +static void wave6_vpu_put(struct wave6_vpu_device *vpu,
> +			  struct vpu_core_device *core)
> +{
[ ... ]
> +	wave6_vpu_sleep(vpu, core);
> +
> +	if (!pm_runtime_suspended(vpu->dev))
> +		pm_runtime_put_sync(vpu->dev);
> +}

[Severity: Medium]
Could this conditional check cause the runtime PM usage counter to leak?

If the device's status is forcefully set to suspended, for example via
pm_runtime_force_suspend() during system sleep, pm_runtime_suspended()
will return true and this check will skip the put operation, leaving the
counter permanently incremented.

[ ... ]
> +static void wave6_vpu_require_work_buffer(struct wave6_vpu_device *vpu,
> +					  struct vpu_core_device *core)
> +{
[ ... ]
> +	if (WARN_ON(vpu->work_buffers_avail == 0))
> +		goto exit;
> +
> +	vpu->work_buffers_avail--;
> +	vb = &vpu->work_buffers[vpu->work_buffers_avail];

[Severity: Critical]
Is there a data race when accessing the work buffers pool?

Since wave6_vpu_require_work_buffer() modifies vpu->work_buffers_avail
without a lock, and wave6_vpu_core_irq() can call this from a hardirq
context across multiple child cores concurrently, could this result in
an array out-of-bounds access or assigning the exact same DMA buffer to
multiple instances?

[ ... ]
> +static int wave6_vpu_create_cores(struct wave6_vpu_device *vpu)
> +{
[ ... ]
> +		irq = of_irq_get(child, 0);
> +		if (irq < 0) {
> +			dev_warn(vpu->dev, "%pOF: missing interrupts property\n", child);
> +			continue;
> +		}

[Severity: High]
Does this error handling prevent the driver from properly handling probe
deferral?

Because wave6_vpu_create_cores() executes in the asynchronous firmware loader
worker context, returning -EPROBE_DEFER here won't defer the probe process.
If the IRQ controller probes later, the core device might be permanently
ignored instead of waiting for the controller.

[ ... ]
> +static void wave6_vpu_release(struct wave6_vpu_device *vpu)
> +{
> +	guard(mutex)(&vpu->lock);
> +
> +	vpu->fw_available = false;
> +	wave6_vpu_destroy_cores(vpu);
> +	wave6_vpu_free_work_buffers(vpu);

[Severity: Critical]
Can holding vpu->lock during core destruction result in a self-deadlock?

When wave6_vpu_destroy_cores() unregisters the core devices, the child
removal triggers pm_runtime_disable() which blocks via __pm_runtime_barrier()
waiting for ongoing PM transitions. If another thread is executing
wave6_vpu_put() or wave6_vpu_get() inside a runtime PM callback, it will
block trying to acquire vpu->lock, causing a deadlock.

[Severity: Critical]
Are the work buffers and SRAM DMA memory freed while the VPU hardware
might still be accessing them?

This function explicitly frees the DMA memory, but it doesn't appear to send
a sleep command, disable the PM runtime properly, or assert a hardware reset
first. If the hardware is still active, could it continue performing DMA into
the kernel memory that was just released?

[Severity: High]
When wave6_vpu_destroy_cores() tears down the core devices, does the
sub-driver wave6-vpu-core.c free the IRQ kfifo before disabling the IRQ?

Looking at wave6_vpu_core_remove(), it calls kfifo_free(&core->irq_fifo)
manually. Since devm_request_threaded_irq() is used, the IRQ is only
disabled after the remove routine completes. If an interrupt fires during
this window, the IRQ handler might call kfifo_in() on a freed structure.

[ ... ]
> +	if (vpu->code_buf.dma_addr)
> +		dma_unmap_resource(vpu->dev,
> +				   vpu->code_buf.dma_addr,

[Severity: Medium]
Is it safe to check the DMA address against 0 to determine if it is valid?

On architectures where 0 is a valid DMA address, this condition will
evaluate to false, which would skip the dma_unmap_resource() call and
permanently leak the mapping.

[ ... ]
> +static void wave6_vpu_load_firmware(const struct firmware *fw, void *context)
> +{
> +	struct wave6_vpu_device *vpu = context;
> +
> +	guard(mutex)(&vpu->lock);

[Severity: High]
Can this asynchronous callback trigger a use-after-free if the device is
unbound?

If the device is unbound via sysfs while the firmware load is still pending,
devres will free the devm-allocated vpu structure. Because there is no
cancellation mechanism on unbind, the callback might execute later and
dereference the freed vpu pointer when trying to acquire the lock.

[ ... ]
> +static int wave6_vpu_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	vpu->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0);
> +	if (vpu->sram_pool) {
> +		vpu->sram_buf.size = vpu->res->sram_size;
> +		vpu->sram_buf.vaddr = gen_pool_dma_alloc(vpu->sram_pool,
> +							 vpu->sram_buf.size,
> +							 &vpu->sram_buf.phys_addr);

[Severity: High]
Is the third argument to gen_pool_dma_alloc() using the correct pointer type?

The function gen_pool_dma_alloc() expects a dma_addr_t pointer for its
third argument, but here it receives a phys_addr_t pointer. On 32-bit
platforms where phys_addr_t and dma_addr_t have different sizes, could this
overwrite adjacent struct members? Furthermore, it returns a device DMA
address which is later passed to dma_map_resource() as if it were a physical
address.

[ ... ]
> +	ret = firmware_request_nowait_nowarn(THIS_MODULE,
> +					     vpu->res->fw_name,
> +					     &pdev->dev,
> +					     GFP_KERNEL,
> +					     vpu,
> +					     wave6_vpu_load_firmware);

[Severity: High]
Does this firmware request need a cancellation mechanism on unbind?

As noted above in wave6_vpu_load_firmware(), without a way to cancel
the pending firmware request or hold a reference to the vpu structure,
an unbind operation could lead to a use-after-free when the callback
eventually runs.

[ ... ]
> +static void wave6_vpu_remove(struct platform_device *pdev)
> +{
> +	struct wave6_vpu_device *vpu = dev_get_drvdata(&pdev->dev);
> +
> +	pm_runtime_disable(vpu->dev);
> +	wave6_vpu_release(vpu);
> +	wave6_vpu_detach_pm_domains(vpu);
> +}

[Severity: High]
Does disabling the PM runtime prematurely here leak clock enable references?

When pm_runtime_disable() is called before the child cores are released via
wave6_vpu_release(), the parent's PM runtime is disabled. When the child
cores subsequently drop their references to the parent device, the parent's
runtime_suspend callback won't execute, which might leave the hardware
clocks running indefinitely.

[Severity: High]
Does detaching the PM domains here create a use-after-free window for
the thermal cooling device?

wave6_vpu_detach_pm_domains() destroys the PM domain virtual devices.
However, the thermal cooling device, which uses one of these virtual devices
as its parent, was registered with devm_ and won't be cleaned up until after
wave6_vpu_remove() returns. If a thermal event fires in that window, could
it access the freed virtual device?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624072043.238-1-nas.chung@chipsnmedia.com?part=8

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 3/9] media: chips-media: wave6: Add Wave6 VPU interface
  2026-06-24  7:20 ` [PATCH v6 3/9] media: chips-media: wave6: Add Wave6 VPU interface Nas Chung
@ 2026-06-24  7:43   ` sashiko-bot
  0 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-06-24  7:43 UTC (permalink / raw)
  To: Nas Chung; +Cc: conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Missing bounds validation on hardware-supplied frame buffer counts causes array buffer overflows and potential infinite loops.
- [Medium] Leaking dynamically allocated `inst->codec_info` when closing decoder/encoder instance fails.
- [Medium] Hardcoded `BIG_ENDIAN` mismatch leads to byte-swapped decoder pixel formats on little-endian hosts.
- [Low] Endianness bug on Big-Endian architectures during product name string extraction.
- [Low] Mixing goto-based cleanup and `guard()` in the same function violates cleanup subsystem guidelines.
--

commit 61aa06e0583bb5f57f21d3d07d1ad5f6245d0508
Author: Nas Chung <nas.chung@chipsnmedia.com>

media: chips-media: wave6: Add Wave6 VPU interface

Add an interface layer to manage hardware register configuration
and communication with the Chips&Media Wave6 video codec IP.
The interface provides low-level helper functions used by the
Wave6 core driver to implement video encoding and decoding operations.

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-hw.c b/drivers/media/platform/chips-media/wave6/wave6-hw.c
> index 0000000000000..b49348d6119c8 100644
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-hw.c
> @@ -230,22 +230,22 @@ int wave6_vpu_get_version(struct vpu_core_device *core)
>  	if (ret)
>  		return ret;
>  
>  	reg_val = vpu_read_reg(core, W6_RET_PRODUCT_NAME);
>  	str = (u8 *)&reg_val;
>  	attr->product_name[0] = str[3];
>  	attr->product_name[1] = str[2];
>  	attr->product_name[2] = str[1];
>  	attr->product_name[3] = str[0];
>  	attr->product_name[4] = 0;

[Severity: Low]
On big-endian architectures, vpu_read_reg() returns the integer in native
byte order. If the bytes are reversed using fixed indices like this, could
it result in the string being stored backwards or reading a null terminator
first?

[ ... ]

> @@ -345,13 +345,13 @@ int wave6_vpu_dec_register_frame_buffer(struct vpu_instance *inst)
>  
>  	fb_arr = &inst->frame_buf[0];
>  	fbc_count = p_dec_info->seq_info.min_frame_buffer_count;
>  	mv_count = p_dec_info->seq_info.req_mv_buffer_count;
>  
>  	for (i = 0; i < fbc_count; i++) {
>  		if (!inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i].daddr)
>  			return -EINVAL;
>  		if (!inst->aux_vbuf[AUX_BUF_FBC_C_TBL][i].daddr)
>  			return -EINVAL;
>  	}

[Severity: High]
If the hardware returns a value for fbc_count that is larger than
WAVE6_MAX_FBS (31), could this loop read past the end of the inst->aux_vbuf
arrays?

[ ... ]

> @@ -377,7 +377,7 @@ int wave6_vpu_dec_register_frame_buffer(struct vpu_instance *inst)
>  
>  	fbc_remain = fbc_count;
>  	mv_remain = mv_count;
>  	group_num = (fbc_count > mv_count) ? ((ALIGN(fbc_count, 16) / 16) - 1) :
>  					     ((ALIGN(mv_count, 16) / 16) - 1);

[Severity: High]
If the hardware unexpectedly returns zero for both fbc_count and mv_count,
would the subtraction of 1 after division underflow to SIZE_MAX and cause
an infinite loop here?

[ ... ]

> @@ -445,9 +445,9 @@ int wave6_vpu_dec_register_display_buffer(struct vpu_instance *inst, struct fram
>  
>  	reg_val = SET_DISP_COMMON_PIC_INFO_BWB_ON |
>  		  SET_DISP_COMMON_PIC_INFO_C_FMT_IDC(c_fmt_idc) |
>  		  SET_DISP_COMMON_PIC_INFO_PIXEL_ORDER(PIXEL_ORDER_INCREASING) |
>  		  SET_DISP_COMMON_PIC_INFO_OUT_MODE(out_mode) |
>  		  SET_DISP_COMMON_PIC_INFO_OUT_FMT(out_fmt) |
>  		  SET_DISP_COMMON_PIC_INFO_STRIDE(fb.stride);
>  	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_COMMON_PIC_INFO, reg_val);
>  	reg_val = SET_DISP_OPTION_ENDIAN(VDI_128BIT_BIG_ENDIAN);
>  	vpu_write_reg(inst->dev, W6_CMD_DEC_SET_DISP_OPTION, reg_val);

[Severity: Medium]
Is it expected to strictly use VDI_128BIT_BIG_ENDIAN here? On little-endian
hosts, enforcing BIG_ENDIAN for output buffers might cause the hardware
state machine to output byte-swapped pixels. Should this use HOST_ENDIAN
instead?

[ ... ]

> @@ -1585,11 +1585,11 @@ static void wave6_gen_set_fb_reg(struct vpu_instance *inst, struct enc_cmd_set_f
>  	reg->num_fb = SET_FB_NUM_FBC_END_IDX(fbc_count - 1) |
>  		      SET_FB_NUM_MV_COL_END_IDX(mv_count - 1);
>  	reg->fbc_stride = SET_FB_FBC_STRIDE_L(fb_arr[0].stride) |
>  			  SET_FB_FBC_STRIDE_C(fb_arr[0].stride_c);
>  	reg->default_cdf = 0;
>  
>  	for (i = 0; i < fbc_count; i++) {
>  		reg->fbc_y[i] = fb_arr[i].buf_y;
>  		reg->fbc_c[i] = fb_arr[i].buf_cb;
>  		reg->fbc_cr[i] = fb_arr[i].buf_cr;
>  		reg->fbc_y_offset[i] = inst->aux_vbuf[AUX_BUF_FBC_Y_TBL][i].daddr;

[Severity: High]
Could this loop overflow the reg->fbc_y array if fbc_count exceeds
WAVE6_MAX_FBS?

[ ... ]

> @@ -1617,11 +1617,11 @@ int wave6_vpu_enc_register_frame_buffer(struct vpu_instance *inst)
>  	struct enc_cmd_set_fb_reg *reg;
>  	struct enc_info *p_enc_info;
>  	u32 fbc_count, mv_count, idx;
>  	int ret;
>  
>  	guard(mutex)(&inst->dev->hw_lock);
>  
>  	p_enc_info = &inst->codec_info->enc_info;

[ ... ]

> @@ -1665,11 +1665,11 @@ int wave6_vpu_enc_register_frame_buffer(struct vpu_instance *inst)
>  		goto exit;
>  	}
>  
>  	if (!vpu_read_reg(inst->dev, W6_RET_SUCCESS)) {
>  		ret = -EIO;
>  		goto exit;
>  	}
>  
>  	ret = 0;
>  
>  exit:
>  	kfree(reg);
>  	return ret;
>  }

[Severity: Low]
The cleanup subsystem guidelines strictly forbid mixing goto-based error
handling with scope-based cleanup helpers like guard() in the same function,
as it can create confusing ownership semantics. Could the dynamically
allocated reg object be managed using __free(kfree) to avoid manual goto
blocks?

> diff --git a/drivers/media/platform/chips-media/wave6/wave6-vpuapi.c b/drivers/media/platform/chips-media/wave6/wave6-vpuapi.c
> index 0000000000000..266885de92afd 100644
> --- /dev/null
> +++ b/drivers/media/platform/chips-media/wave6/wave6-vpuapi.c
> @@ -42,16 +42,16 @@ int wave6_vpu_dec_close(struct vpu_instance *inst, u32 *fail_res)
>  	if (WARN_ON(!inst->codec_info))
>  		return -EINVAL;
>  
>  	guard(mutex)(&inst->dev->hw_lock);
>  
>  	ret = wave6_vpu_dec_fini_seq(inst, fail_res);
>  	if (ret) {
>  		dev_warn(inst->dev->dev, "dec seq end timed out\n");
>  
>  		return ret;
>  	}
>  
>  	dev_dbg(inst->dev->dev, "dec seq end complete\n");
>  
>  	kfree(inst->codec_info);
>  
>  	return 0;
>  }

[Severity: Medium]
If hardware teardown in wave6_vpu_dec_fini_seq() fails and returns an
error, does this skip freeing the inst->codec_info struct?

[ ... ]

> @@ -233,16 +233,16 @@ int wave6_vpu_enc_close(struct vpu_instance *inst, u32 *fail_res)
>  	if (WARN_ON(!inst->codec_info))
>  		return -EINVAL;
>  
>  	guard(mutex)(&inst->dev->hw_lock);
>  
>  	ret = wave6_vpu_enc_fini_seq(inst, fail_res);
>  	if (ret) {
>  		dev_warn(inst->dev->dev, "enc seq end timed out\n");
>  
>  		return ret;
>  	}
>  
>  	dev_dbg(inst->dev->dev, "enc seq end complete\n");
>  	kfree(inst->codec_info);
>  
>  	return 0;
>  }

[Severity: Medium]
Similarly, if wave6_vpu_enc_fini_seq() fails here, will it result in a
memory leak of inst->codec_info?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624072043.238-1-nas.chung@chipsnmedia.com?part=3

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node
  2026-06-24  7:20 ` [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node Nas Chung
@ 2026-06-24 11:50   ` Francesco Dolcini
  0 siblings, 0 replies; 17+ messages in thread
From: Francesco Dolcini @ 2026-06-24 11:50 UTC (permalink / raw)
  To: Nas Chung
  Cc: mchehab, hverkuil, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	linux-media, devicetree, linux-kernel, linux-imx,
	linux-arm-kernel, jackson.lee, lafley.kim, marek.vasut

On Wed, Jun 24, 2026 at 04:20:43PM +0900, Nas Chung wrote:
> Add the Chips and Media wave633 video codec node on IMX95 SoCs.
> 
> Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
> ---
>  .../boot/dts/freescale/imx95-19x19-evk.dts    | 11 ++++++
>  arch/arm64/boot/dts/freescale/imx95.dtsi      | 36 +++++++++++++++++++
>  2 files changed, 47 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
> index 041fd838fabb..7edd1c69966a 100644
> --- a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
> +++ b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
> @@ -76,6 +76,11 @@ linux_cma: linux,cma {
>  			linux,cma-default;
>  			reusable;
>  		};
> +
> +		vpu_boot: memory@a0000000 {
> +			reg = <0 0xa0000000 0 0x100000>;
> +			no-map;
> +		};
>  	};
>  
>  	flexcan1_phy: can-phy0 {
> @@ -1142,3 +1147,9 @@ &tpm6 {
>  	pinctrl-0 = <&pinctrl_tpm6>;
>  	status = "okay";
>  };
> +
> +&vpu {
> +	memory-region = <&vpu_boot>;
> +	sram = <&sram1>;

Can the `sram` node moved to imx95.dtsi or not?

Francesco

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-06-24 11:51 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-24  7:20 [PATCH v6 0/9] Add support for Wave6 video codec driver Nas Chung
2026-06-24  7:20 ` [PATCH v6 1/9] media: v4l2-common: Fix P010 format info Nas Chung
2026-06-24  7:20 ` [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec device Nas Chung
2026-06-24  7:20 ` [PATCH v6 3/9] media: chips-media: wave6: Add Wave6 VPU interface Nas Chung
2026-06-24  7:43   ` sashiko-bot
2026-06-24  7:20 ` [PATCH v6 4/9] media: chips-media: wave6: Add v4l2 m2m driver support Nas Chung
2026-06-24  7:40   ` sashiko-bot
2026-06-24  7:20 ` [PATCH v6 5/9] media: chips-media: wave6: Add Wave6 core driver Nas Chung
2026-06-24  7:38   ` sashiko-bot
2026-06-24  7:20 ` [PATCH v6 6/9] media: chips-media: wave6: Improve debugging capabilities Nas Chung
2026-06-24  7:36   ` sashiko-bot
2026-06-24  7:20 ` [PATCH v6 7/9] media: chips-media: wave6: Add Wave6 thermal cooling device Nas Chung
2026-06-24  7:36   ` sashiko-bot
2026-06-24  7:20 ` [PATCH v6 8/9] media: chips-media: wave6: Add Wave6 control driver Nas Chung
2026-06-24  7:41   ` sashiko-bot
2026-06-24  7:20 ` [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node Nas Chung
2026-06-24 11:50   ` Francesco Dolcini

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox