* [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder.
@ 2026-06-12 19:44 Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
To: linux-media
Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
The goal is to upstream a standards-based JPEG encode path using the V4L2 M2M
framework, with DT-described hardware resources and SoC DTS integration.
Patch layout:
- 1/4: devicetree bindings for the standalone Qualcomm JPEG encoder block
- 2/4: SM8550 devicetree integration
- 3/4: SM8250 devicetree integration
- 4/4: qcom-jenc V4L2 mem2mem encoder driver
Supported compatibles:
- qcom,sm8250-jenc
- qcom,qcm6490-jenc
- qcom,sm8550-jenc
Driver scope and design choices:
- uses the standard V4L2 mem2mem + vb2 workflow
- maps runtime resources from DT (clocks/interconnects/iommu/etc.)
- keeps userspace interface within existing V4L2 JPEG controls/framework
- uses runtime PM for balanced power transitions across stream/probe/remove
Functional validation (hardware):
- platforms (codename / SoC / compatible / board):
- Kona / SM8250 (QRB5165 class) / qcom,sm8250-jenc / RB5
- Kailua / SM8550 / qcom,sm8550-jenc / RB5 Gen2
- Kodiak / QCM6490 (QCS6490 derivative line) / qcom,qcm6490-jenc / RB3 Gen2
- validated SoC coverage: SM8250, SM8550, QCM6490
- tested flows:
- single-frame encode: 8192x8192 NV12 (v4l2-ctl)
- single-frame encode: 1920x1080 NV12 (v4l2-ctl)
- single-frame encode: 1920x1080 GREY (v4l2-ctl)
- GStreamer NV12 pipeline to JPEG files (v4l2jpegenc)
- GStreamer GRAY8 pipeline to MJPEG output (v4l2jpegenc)
Known limitations:
- scaling is not supported
- width and height must be aligned to 16 pixels
Atanas Filipov (4):
dt-bindings: media: qcom: Add JPEG encoder binding
arm64: dts: qcom: sm8550: Add JPEG encoder node
arm64: dts: qcom: sm8250: Add JPEG encoder node
media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
.../bindings/media/qcom,jpeg-encoder.yaml | 142 ++
arch/arm64/boot/dts/qcom/sm8250.dtsi | 35 +
arch/arm64/boot/dts/qcom/sm8550.dtsi | 42 +
drivers/media/platform/qcom/Kconfig | 1 +
drivers/media/platform/qcom/Makefile | 1 +
drivers/media/platform/qcom/jpeg/Kconfig | 18 +
drivers/media/platform/qcom/jpeg/Makefile | 9 +
.../media/platform/qcom/jpeg/qcom_jenc_defs.h | 244 +++
.../media/platform/qcom/jpeg/qcom_jenc_dev.c | 336 ++++
.../media/platform/qcom/jpeg/qcom_jenc_dev.h | 107 ++
.../media/platform/qcom/jpeg/qcom_jenc_hdr.c | 360 ++++
.../media/platform/qcom/jpeg/qcom_jenc_hdr.h | 119 ++
.../media/platform/qcom/jpeg/qcom_jenc_ops.c | 1658 +++++++++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_ops.h | 52 +
.../media/platform/qcom/jpeg/qcom_jenc_res.c | 226 +++
.../media/platform/qcom/jpeg/qcom_jenc_res.h | 54 +
.../qcom/jpeg/qcom_jenc_v420_hw_info.h | 529 ++++++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1109 +++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.h | 25 +
19 files changed, 5067 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
create mode 100644 drivers/media/platform/qcom/jpeg/Kconfig
create mode 100644 drivers/media/platform/qcom/jpeg/Makefile
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding
2026-06-12 19:44 [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder Atanas Filipov
@ 2026-06-12 19:44 ` Atanas Filipov
2026-06-12 19:57 ` sashiko-bot
2026-06-12 20:42 ` Frank Li
2026-06-12 19:44 ` [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node Atanas Filipov
` (2 subsequent siblings)
3 siblings, 2 replies; 10+ messages in thread
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
To: linux-media
Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add device-tree binding for the standalone Qualcomm JPEG encoder
hardware block (separate from CAMSS media pipelines).
Document required resources briefly and scope initial support to
currently used compatibles in this series, including SM8250,
QCM6490, and SM8550 class platforms.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
.../bindings/media/qcom,jpeg-encoder.yaml | 142 ++++++++++++++++++
1 file changed, 142 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
diff --git a/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
new file mode 100644
index 000000000000..83f19719a869
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
@@ -0,0 +1,142 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/qcom,jpeg-encoder.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm JPEG Encoder
+
+maintainers:
+ - Azam Sadiq Pasha Kapatrala Syed <akapatra@quicinc.com>
+ - Hariram Purushothaman <hariramp@quicinc.com>
+
+description: |
+ Qualcomm JPEG Encoder is the JPEG encode hardware present in Qualcomm SoCs.
+
+ The JPEG hardware IP version 4.2.0 is shared across SM8250, QCM6490, SM8550
+ and related derivatives. Platforms based on the SM8550 register layout and
+ interrupt wiring can reuse the qcom,sm8550-jenc fallback compatible, provided
+ the correct register base address and IOMMU stream IDs are supplied in the
+ DTS node.
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - const: qcom,sm8250-jenc
+ - items:
+ - enum:
+ - qcom,qcm6490-jenc
+ - const: qcom,sm8550-jenc
+ - items:
+ - const: qcom,sm8550-jenc
+
+ cell-index:
+ description: Legacy JPEG HW instance index.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ reg:
+ maxItems: 1
+
+ reg-names:
+ oneOf:
+ - items:
+ - const: jpeg
+ - items:
+ - const: jpeg-regs
+ - items:
+ - const: jpeg_regs
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ minItems: 6
+ maxItems: 6
+
+ clock-names:
+ oneOf:
+ - items:
+ - const: gcc_hf_axi
+ - const: gcc_sf_axi
+ - const: core_ahb
+ - const: cpas_ahb
+ - const: camnoc_axi
+ - const: jpeg
+ - items:
+ - const: gcc_hf_axi_clk
+ - const: gcc_sf_axi_clk
+ - const: core_ahb_clk
+ - const: cpas_ahb_clk
+ - const: camnoc_axi_clk
+ - const: jpeg_clk
+
+ power-domains:
+ maxItems: 1
+
+ iommus:
+ maxItems: 2
+
+ interconnects:
+ maxItems: 4
+
+ interconnect-names:
+ items:
+ - const: cam_ahb
+ - const: cam_hf_0_mnoc
+ - const: cam_sf_0_mnoc
+ - const: cam_sf_icp_mnoc
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - interrupts
+ - power-domains
+ - iommus
+ - interconnects
+ - interconnect-names
+
+additionalProperties: false
+
+examples:
+ - |
+ jpeg-encoder@ac53000 {
+ compatible = "qcom,sm8250-jenc";
+ reg = <0xac53000 0x1000>;
+ reg-names = "jpeg";
+
+ interrupts = <0 474 4>;
+ power-domains = <&camcc 0>;
+
+ clock-names =
+ "gcc_hf_axi",
+ "gcc_sf_axi",
+ "core_ahb",
+ "cpas_ahb",
+ "camnoc_axi",
+ "jpeg";
+
+ clocks =
+ <&gcc 0>,
+ <&gcc 1>,
+ <&camcc 0>,
+ <&camcc 1>,
+ <&camcc 2>,
+ <&camcc 3>;
+
+ iommus = <&apps_smmu 0x2040 0x400>,
+ <&apps_smmu 0x2440 0x400>;
+
+ interconnects =
+ <&gem_noc 0 0 &config_noc 8 0>,
+ <&mmss_noc 0 0 &mc_virt 0 0>,
+ <&mmss_noc 1 0 &mc_virt 0 0>,
+ <&mmss_noc 2 0 &mc_virt 0 0>;
+
+ interconnect-names =
+ "cam_ahb",
+ "cam_hf_0_mnoc",
+ "cam_sf_0_mnoc",
+ "cam_sf_icp_mnoc";
+ };
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node
2026-06-12 19:44 [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
@ 2026-06-12 19:44 ` Atanas Filipov
2026-06-12 20:06 ` sashiko-bot
2026-06-12 19:44 ` [PATCH v1 3/4] arm64: dts: qcom: sm8250: " Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
3 siblings, 1 reply; 10+ messages in thread
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
To: linux-media
Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add the missing JPEG encoder hardware node in SM8550 DTS so the
new qcom-jpeg V4L2 encoder driver can bind and operate on this
platform.
The node wires the resources expected by the binding and driver,
including clocks, power domain, IOMMUs and interconnect paths.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/sm8550.dtsi | 42 ++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi
index 912525e9bca6..8090b8b1d7bd 100644
--- a/arch/arm64/boot/dts/qcom/sm8550.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi
@@ -3677,6 +3677,48 @@ port@7 {
};
};
+ qcom_jpeg_enc: jpeg-encoder@ac4e000 {
+ cell-index = <0>;
+ compatible = "qcom,sm8550-jenc";
+
+ reg = <0 0xac4e000 0 0x4000>;
+ reg-names = "jpeg";
+
+ interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING 0>;
+ power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>;
+
+ clocks = <&gcc GCC_CAMERA_HF_AXI_CLK>,
+ <&gcc GCC_CAMERA_SF_AXI_CLK>,
+ <&camcc CAM_CC_CORE_AHB_CLK>,
+ <&camcc CAM_CC_CPAS_AHB_CLK>,
+ <&camcc CAM_CC_CAMNOC_AXI_CLK>,
+ <&camcc CAM_CC_JPEG_CLK>;
+
+ clock-names = "gcc_hf_axi",
+ "gcc_sf_axi",
+ "core_ahb",
+ "cpas_ahb",
+ "camnoc_axi",
+ "jpeg";
+
+ iommus = <&apps_smmu 0x20C0 0x20>,
+ <&apps_smmu 0x20E0 0x20>;
+
+ interconnects = <&gem_noc MASTER_APPSS_PROC 0
+ &config_noc SLAVE_CAMERA_CFG 0>,
+ <&mmss_noc MASTER_CAMNOC_HF 0
+ &mc_virt SLAVE_EBI1 0>,
+ <&mmss_noc MASTER_CAMNOC_SF 0
+ &mc_virt SLAVE_EBI1 0>,
+ <&mmss_noc MASTER_CAMNOC_ICP 0
+ &mc_virt SLAVE_EBI1 0>;
+
+ interconnect-names = "cam_ahb",
+ "cam_hf_0_mnoc",
+ "cam_sf_0_mnoc",
+ "cam_sf_icp_mnoc";
+ };
+
camcc: clock-controller@ade0000 {
compatible = "qcom,sm8550-camcc";
reg = <0 0x0ade0000 0 0x20000>;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v1 3/4] arm64: dts: qcom: sm8250: Add JPEG encoder node
2026-06-12 19:44 [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node Atanas Filipov
@ 2026-06-12 19:44 ` Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
3 siblings, 0 replies; 10+ messages in thread
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
To: linux-media
Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add the missing JPEG encoder hardware node in SM8250 DTS so the
new qcom-jpeg V4L2 encoder driver can bind and operate on this
platform.
The node wires the resources expected by the binding and driver,
including clocks, power domain, IOMMUs and interconnect paths.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/sm8250.dtsi | 35 ++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi
index 7076720413ab..081f172018ae 100644
--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
@@ -4469,6 +4469,41 @@ cci1_i2c1: i2c-bus@1 {
};
};
+ qcom_jpeg_enc: jpeg-encoder@ac53000 {
+ compatible = "qcom,sm8250-jenc";
+
+ reg = <0 0xac53000 0 0x1000>;
+ reg-names = "jpeg";
+
+ interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING>;
+ power-domains = <&camcc TITAN_TOP_GDSC>;
+
+ clocks = <&gcc GCC_CAMERA_HF_AXI_CLK>,
+ <&gcc GCC_CAMERA_SF_AXI_CLK>,
+ <&camcc CAM_CC_CORE_AHB_CLK>,
+ <&camcc CAM_CC_CPAS_AHB_CLK>,
+ <&camcc CAM_CC_CAMNOC_AXI_CLK>,
+ <&camcc CAM_CC_JPEG_CLK>;
+
+ clock-names = "gcc_hf_axi",
+ "gcc_sf_axi",
+ "core_ahb",
+ "cpas_ahb",
+ "camnoc_axi",
+ "jpeg";
+ iommus = <&apps_smmu 0x2040 0x400>,
+ <&apps_smmu 0x2440 0x400>;
+
+ interconnects = <&gem_noc MASTER_AMPSS_M0 0 &config_noc SLAVE_CAMERA_CFG 0>,
+ <&mmss_noc MASTER_CAMNOC_HF 0 &mc_virt SLAVE_EBI_CH0 0>,
+ <&mmss_noc MASTER_CAMNOC_SF 0 &mc_virt SLAVE_EBI_CH0 0>,
+ <&mmss_noc MASTER_CAMNOC_ICP 0 &mc_virt SLAVE_EBI_CH0 0>;
+ interconnect-names = "cam_ahb",
+ "cam_hf_0_mnoc",
+ "cam_sf_0_mnoc",
+ "cam_sf_icp_mnoc";
+ };
+
camss: camss@ac6a000 {
compatible = "qcom,sm8250-camss";
status = "disabled";
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
2026-06-12 19:44 [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder Atanas Filipov
` (2 preceding siblings ...)
2026-06-12 19:44 ` [PATCH v1 3/4] arm64: dts: qcom: sm8250: " Atanas Filipov
@ 2026-06-12 19:44 ` Atanas Filipov
2026-06-12 20:23 ` sashiko-bot
2026-06-12 20:53 ` Frank Li
3 siblings, 2 replies; 10+ messages in thread
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
To: linux-media
Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add a Qualcomm JPEG encoder driver implemented on top of the
V4L2 mem2mem framework.
The driver wires vb2 queue handling, format negotiation, JPEG header
handling, interrupt-driven job completion, and runtime PM/clock/ICC
integration for the standalone JPEG encode hardware block.
Initial support in this series targets SM8250, QCM6490, and SM8550
class platforms.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
drivers/media/platform/qcom/Kconfig | 1 +
drivers/media/platform/qcom/Makefile | 1 +
drivers/media/platform/qcom/jpeg/Kconfig | 18 +
drivers/media/platform/qcom/jpeg/Makefile | 9 +
.../media/platform/qcom/jpeg/qcom_jenc_defs.h | 244 +++
.../media/platform/qcom/jpeg/qcom_jenc_dev.c | 336 ++++
.../media/platform/qcom/jpeg/qcom_jenc_dev.h | 107 ++
.../media/platform/qcom/jpeg/qcom_jenc_hdr.c | 360 ++++
.../media/platform/qcom/jpeg/qcom_jenc_hdr.h | 119 ++
.../media/platform/qcom/jpeg/qcom_jenc_ops.c | 1658 +++++++++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_ops.h | 52 +
.../media/platform/qcom/jpeg/qcom_jenc_res.c | 226 +++
.../media/platform/qcom/jpeg/qcom_jenc_res.h | 54 +
.../qcom/jpeg/qcom_jenc_v420_hw_info.h | 529 ++++++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1109 +++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.h | 25 +
16 files changed, 4848 insertions(+)
create mode 100644 drivers/media/platform/qcom/jpeg/Kconfig
create mode 100644 drivers/media/platform/qcom/jpeg/Makefile
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
diff --git a/drivers/media/platform/qcom/Kconfig b/drivers/media/platform/qcom/Kconfig
index 4f4d3a68e6e5..f33d53a754a0 100644
--- a/drivers/media/platform/qcom/Kconfig
+++ b/drivers/media/platform/qcom/Kconfig
@@ -5,3 +5,4 @@ comment "Qualcomm media platform drivers"
source "drivers/media/platform/qcom/camss/Kconfig"
source "drivers/media/platform/qcom/iris/Kconfig"
source "drivers/media/platform/qcom/venus/Kconfig"
+source "drivers/media/platform/qcom/jpeg/Kconfig"
diff --git a/drivers/media/platform/qcom/Makefile b/drivers/media/platform/qcom/Makefile
index ea2221a202c0..30c94949e9de 100644
--- a/drivers/media/platform/qcom/Makefile
+++ b/drivers/media/platform/qcom/Makefile
@@ -2,3 +2,4 @@
obj-y += camss/
obj-y += iris/
obj-y += venus/
+obj-y += jpeg/
diff --git a/drivers/media/platform/qcom/jpeg/Kconfig b/drivers/media/platform/qcom/jpeg/Kconfig
new file mode 100644
index 000000000000..6df7edc07229
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_QCOM_JENC
+ tristate "Qualcomm V4L2 JPEG Encoder driver"
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST
+ depends on VIDEO_DEV
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_DMA_SG
+ select V4L2_JPEG_HELPER
+ select V4L2_MEM2MEM_DEV
+ help
+ Qualcomm JPEG memory-to-memory V4L2 encoder driver.
+
+ Provides:
+ - qcom-jenc (encode)
+
+ To compile this driver as a module, choose M here: the
+ module will be called qcom-jenc
diff --git a/drivers/media/platform/qcom/jpeg/Makefile b/drivers/media/platform/qcom/jpeg/Makefile
new file mode 100644
index 000000000000..310f6c3c1f19
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_VIDEO_QCOM_JENC) += qcom-jenc.o
+
+qcom-jenc-objs += \
+ qcom_jenc_dev.o \
+ qcom_jenc_v4l2.o \
+ qcom_jenc_ops.o \
+ qcom_jenc_res.o \
+ qcom_jenc_hdr.o
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
new file mode 100644
index 000000000000..00647a5d06ec
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_DEFS_H_
+#define QCOM_JENC_DEFS_H_
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <media/videobuf2-core.h>
+
+/* Offline JPEG encoder constraints */
+#define QCOM_JPEG_HW_MAX_WIDTH 8192
+#define QCOM_JPEG_HW_MAX_HEIGHT 8192
+#define QCOM_JPEG_HW_MIN_WIDTH 128
+#define QCOM_JPEG_HW_MIN_HEIGHT 96
+
+#define QCOM_JPEG_HW_DEF_HSTEP 16
+#define QCOM_JPEG_HW_DEF_VSTEP 16
+
+#define QCOM_JPEG_HW_DEF_WIDTH 1920
+#define QCOM_JPEG_HW_DEF_HEIGHT 1088
+
+#define QCOM_JPEG_MAX_PLANES 3
+
+#define QCOM_JPEG_QUALITY_MIN 1
+#define QCOM_JPEG_QUALITY_DEF 95
+#define QCOM_JPEG_QUALITY_MAX 100
+#define QCOM_JPEG_QUALITY_MID (QCOM_JPEG_QUALITY_MAX / 2)
+#define QCOM_JPEG_QUALITY_UNT 1
+
+enum qcom_soc_perf_level {
+ /* Idle/suspend state, not for active encode performance selection. */
+ QCOM_SOC_PERF_SUSPEND = 0,
+ QCOM_SOC_PERF_LOWSVS,
+ QCOM_SOC_PERF_SVS,
+ QCOM_SOC_PERF_SVS_L1,
+ QCOM_SOC_PERF_NOMINAL,
+ QCOM_SOC_PERF_TURBO,
+ QCOM_SOC_PERF_LEVEL_MAX,
+};
+
+enum qcom_jpeg_mask_id {
+ JMSK_HW_VER_STEP,
+ JMSK_HW_VER_MINOR,
+ JMSK_HW_VER_MAJOR,
+
+ JMSK_HW_CAP_ENCODE,
+ JMSK_HW_CAP_DECODE,
+ JMSK_HW_CAP_UPSCALE,
+ JMSK_HW_CAP_DOWNSCALE,
+
+ JMSK_RST_CMD_COMMON,
+ JMSK_RST_CMD_FE_RESET,
+ JMSK_RST_CMD_WE_RESET,
+ JMSK_RST_CMD_ENCODER_RESET,
+ JMSK_RST_CMD_DECODER_RESET,
+ JMSK_RST_CMD_BLOCK_FORMATTER_RST,
+ JMSK_RST_CMD_SCALE_RESET,
+ JMSK_RST_CMD_REGISTER_RESET,
+ JMSK_RST_CMD_MISR_RESET,
+ JMSK_RST_CMD_CORE_RESET,
+ JMSK_RST_CMD_JMSK_DOMAIN_RESET,
+ JMSK_RST_CMD_RESET_BYPASS,
+
+ JMSK_CMD_HW_START,
+ JMSK_CMD_HW_STOP,
+ JMSK_CMD_CLR_RD_PLNS_QUEUE,
+ JMSK_CMD_CLR_WR_PLNS_QUEUE,
+ JMSK_CMD_APPLY_SWC_RD_PARAMS,
+
+ JMSK_CORE_CFG_FE_ENABLE,
+ JMSK_CORE_CFG_WE_ENABLE,
+ JMSK_CORE_CFG_ENC_ENABLE,
+ JMSK_CORE_CFG_SCALE_ENABLE,
+ JMSK_CORE_CFG_TESTBUS_ENABLE,
+ JMSK_CORE_CFG_MODE,
+ JMSK_CORE_CFG_CGC_DISABLE,
+
+ JMSK_CORE_STATUS_ENCODE_STATE,
+ JMSK_CORE_STATUS_SCALE_STATE,
+ JMSK_CORE_STATUS_RT_STATE,
+ JMSK_CORE_STATUS_BUS_STATE,
+ JMSK_CORE_STATUS_CGC_STATE,
+
+ JMSK_IRQ_STATUS_SESSION_DONE,
+ JMSK_IRQ_STATUS_RD_BUF_PLN0_DONE,
+ JMSK_IRQ_STATUS_RD_BUF_PLN1_DONE,
+ JMSK_IRQ_STATUS_RD_BUF_PLN2_DONE,
+ JMSK_IRQ_STATUS_RD_BUF_PLNS_ATTN,
+ JMSK_IRQ_STATUS_WR_BUF_PLN0_DONE,
+ JMSK_IRQ_STATUS_WR_BUF_PLN1_DONE,
+ JMSK_IRQ_STATUS_WR_BUF_PLN2_DONE,
+ JMSK_IRQ_STATUS_WR_BUF_PLNS_ATTN,
+ JMSK_IRQ_STATUS_SESSION_ERROR,
+ JMSK_IRQ_STATUS_STOP_ACK,
+ JMSK_IRQ_STATUS_RESET_ACK,
+
+ JMSK_IRQ_STATUS_ALL_BITS,
+
+ JMSK_FE_CFG_BYTE_ORDERING,
+ JMSK_FE_CFG_BURST_LENGTH_MAX,
+ JMSK_FE_CFG_MEMORY_FORMAT,
+ JMSK_FE_CFG_CBCR_ORDER,
+ JMSK_FE_CFG_BOTTOM_VPAD_EN,
+ JMSK_FE_CFG_PLN0_EN,
+ JMSK_FE_CFG_PLN1_EN,
+ JMSK_FE_CFG_PLN2_EN,
+ JMSK_FE_CFG_SIXTEEN_MCU_EN,
+ JMSK_FE_CFG_MCUS_PER_BLOCK,
+ JMSK_FE_CFG_MAL_BOUNDARY,
+ JMSK_FE_CFG_MAL_EN,
+
+ JMSK_FE_VBPAD_CFG_BLOCK_ROW,
+
+ JMSK_PLNS_RD_OFFSET,
+ JMSK_PLNS_RD_BUF_SIZE_WIDTH,
+ JMSK_PLNS_RD_BUF_SIZE_HEIGHT,
+ JMSK_PLNS_RD_STRIDE,
+ JMSK_PLNS_RD_HINIT,
+ JMSK_PLNS_RD_VINIT,
+
+ JMSK_WE_CFG_BYTE_ORDERING,
+ JMSK_WE_CFG_BURST_LENGTH_MAX,
+ JMSK_WE_CFG_MEMORY_FORMAT,
+ JMSK_WE_CFG_CBCR_ORDER,
+ JMSK_WE_CFG_PLN0_EN,
+ JMSK_WE_CFG_PLN1_EN,
+ JMSK_WE_CFG_PLN2_EN,
+ JMSK_WE_CFG_MAL_BOUNDARY,
+ JMSK_WE_CFG_MAL_EN,
+ JMSK_WE_CFG_POP_BUFF_ON_EOS,
+
+ JMSK_PLNS_WR_BUF_SIZE_WIDTH,
+ JMSK_PLNS_WR_BUF_SIZE_HEIGHT,
+
+ JMSK_PLNS_WR_STRIDE,
+ JMSK_PLNS_WR_HINIT,
+ JMSK_PLNS_WR_VINIT,
+ JMSK_PLNS_WR_HSTEP,
+ JMSK_PLNS_WR_VSTEP,
+ JMSK_PLNS_WR_BLOCK_CFG_PER_COL,
+ JMSK_PLNS_WR_BLOCK_CFG_PER_RAW,
+
+ JMSK_ENC_CFG_IMAGE_FORMAT,
+ JMSK_ENC_CFG_APPLY_EOI,
+ JMSK_ENC_CFG_HUFFMAN_SEL,
+ JMSK_ENC_CFG_FSC_ENABLE,
+ JMSK_ENC_CFG_OUTPUT_DISABLE,
+ JMSK_ENC_CFG_RST_MARKER_PERIOD,
+ JMSK_ENC_IMAGE_SIZE_WIDTH,
+ JMSK_ENC_IMAGE_SIZE_HEIGHT,
+
+ JMSK_SCALE_CFG_HSCALE_ENABLE,
+ JMSK_SCALE_CFG_VSCALE_ENABLE,
+ JMSK_SCALE_CFG_UPSAMPLE_EN,
+ JMSK_SCALE_CFG_SUBSAMPLE_EN,
+ JMSK_SCALE_CFG_HSCALE_ALGO,
+ JMSK_SCALE_CFG_VSCALE_ALGO,
+ JMSK_SCALE_CFG_H_SCALE_FIR_ALGO,
+ JMSK_SCALE_CFG_V_SCALE_FIR_ALGO,
+
+ JMSK_SCALE_PLNS_OUT_CFG_BLK_WIDTH,
+ JMSK_SCALE_PLNS_OUT_CFG_BLK_HEIGHT,
+
+ JMSK_SCALE_PLNS_HSTEP_FRACTIONAL,
+ JMSK_SCALE_PLNS_HSTEP_INTEGER,
+ JMSK_SCALE_PLNS_VSTEP_FRACTIONAL,
+ JMSK_SCALE_PLNS_VSTEP_INTEGER,
+
+ JMSK_DMI_CFG,
+ JMSK_DMI_ADDR,
+ JMSK_DMI_DATA,
+
+ JMSK_TESTBUS_CFG,
+ JMSK_FE_VBPAD_CFG,
+
+ JMSK_PLN0_RD_HINIT_INT,
+ JMSK_PLN1_RD_HINIT_INT,
+ JMSK_PLN2_RD_HINIT_INT,
+ JMSK_PLN0_RD_VINIT_INT,
+ JMSK_PLN1_RD_VINIT_INT,
+ JMSK_PLN2_RD_VINIT_INT,
+ JMSK_ID_MAX
+};
+
+struct qcom_jpeg_reg_offs {
+ u32 hw_version;
+ u32 hw_capability;
+ u32 reset_cmd;
+ u32 core_cfg;
+ u32 int_mask;
+ u32 int_clr;
+ u32 int_status;
+ u32 hw_cmd;
+ u32 enc_core_state;
+
+ struct {
+ u32 pntr[QCOM_JPEG_MAX_PLANES];
+ u32 offs[QCOM_JPEG_MAX_PLANES];
+ u32 cnsmd[QCOM_JPEG_MAX_PLANES];
+ u32 bsize[QCOM_JPEG_MAX_PLANES];
+ u32 stride[QCOM_JPEG_MAX_PLANES];
+ u32 hinit[QCOM_JPEG_MAX_PLANES];
+ u32 vinit[QCOM_JPEG_MAX_PLANES];
+ u32 pntr_cnt;
+ u32 vbpad_cfg;
+ } fe;
+ u32 fe_cfg;
+
+ struct {
+ u32 pntr[QCOM_JPEG_MAX_PLANES];
+ u32 cnsmd[QCOM_JPEG_MAX_PLANES];
+ u32 bsize[QCOM_JPEG_MAX_PLANES];
+ u32 stride[QCOM_JPEG_MAX_PLANES];
+ u32 hinit[QCOM_JPEG_MAX_PLANES];
+ u32 hstep[QCOM_JPEG_MAX_PLANES];
+ u32 vinit[QCOM_JPEG_MAX_PLANES];
+ u32 vstep[QCOM_JPEG_MAX_PLANES];
+ u32 blocks[QCOM_JPEG_MAX_PLANES];
+ u32 pntr_cnt;
+ } we;
+ u32 we_cfg;
+
+ struct {
+ u32 hstep[QCOM_JPEG_MAX_PLANES];
+ u32 vstep[QCOM_JPEG_MAX_PLANES];
+ } scale;
+ u32 scale_cfg;
+ u32 scale_out_cfg[QCOM_JPEG_MAX_PLANES];
+
+ u32 enc_cfg;
+ u32 enc_img_size;
+ u32 enc_out_size;
+
+ u32 dmi_cfg;
+ u32 dmi_data;
+ u32 dmi_addr;
+};
+
+#endif /* QCOM_JENC_DEFS_H_ */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
new file mode 100644
index 000000000000..c0ea42cc7af7
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/interconnect.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-mem2mem.h>
+
+#include "qcom_jenc_dev.h"
+
+#include "qcom_jenc_defs.h"
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_res.h"
+#include "qcom_jenc_v4l2.h"
+
+static int qcom_jpeg_clk_init(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_dev_resources *res = jenc->res;
+ int c_idx;
+
+ jenc->clks = devm_kcalloc(jenc->dev, ARRAY_SIZE(res->clk_names), sizeof(*jenc->clks),
+ GFP_KERNEL);
+ if (!jenc->clks)
+ return -ENOMEM;
+
+ for (c_idx = 0; c_idx < ARRAY_SIZE(res->clk_names); c_idx++) {
+ if (!res->clk_names[c_idx])
+ break;
+
+ jenc->clks[c_idx].id = res->clk_names[c_idx];
+ jenc->num_clks++;
+ }
+
+ if (!jenc->num_clks)
+ return -EINVAL;
+
+ return devm_clk_bulk_get(jenc->dev, jenc->num_clks, jenc->clks);
+}
+
+static int qcom_jpeg_clk_rate(struct qcom_jenc_dev *jenc, enum qcom_soc_perf_level level)
+{
+ const struct qcom_dev_resources *res = jenc->res;
+ const struct qcom_perf_resource *perf = &res->perf_cfg[level];
+ int c_idx;
+ int rc = 0;
+
+ for (c_idx = 0; c_idx < jenc->num_clks; c_idx++) {
+ /* skip clocks with fixed or default frequency */
+ if (!perf->clk_rate[c_idx])
+ continue;
+
+ /* setup frequency according to performance level */
+ rc = clk_set_rate(jenc->clks[c_idx].clk, perf->clk_rate[c_idx]);
+ if (rc < 0) {
+ dev_err(jenc->dev, "clock set rate failed: %d\n", rc);
+ return rc;
+ }
+
+ dev_dbg(jenc->dev, "clock %s current rate: %ld\n",
+ jenc->clks[c_idx].id, clk_get_rate(jenc->clks[c_idx].clk));
+ }
+
+ return rc;
+}
+
+static int qcom_jpeg_clk_on(struct qcom_jenc_dev *jenc)
+{
+ int rc;
+
+ rc = qcom_jpeg_clk_rate(jenc, jenc->perf);
+ if (rc)
+ return rc;
+
+ rc = clk_bulk_prepare_enable(jenc->num_clks, jenc->clks);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static void qcom_jpeg_clk_off(struct qcom_jenc_dev *jenc)
+{
+ clk_bulk_disable_unprepare(jenc->num_clks, jenc->clks);
+}
+
+static int qcom_jpeg_icc_on(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_dev_resources *res = jenc->res;
+ int p_idx;
+ int rc;
+
+ for (p_idx = 0; p_idx < res->num_of_icc; p_idx++) {
+ rc = icc_set_bw(jenc->icc_paths[p_idx], res->icc_res[p_idx].pair.aggr,
+ res->icc_res[p_idx].pair.peak);
+ if (rc) {
+ dev_err(jenc->dev, "%s failed for path %s: %d\n", __func__,
+ res->icc_res[p_idx].icc_id, rc);
+ goto err_icc_set_bw;
+ }
+ }
+
+ return 0;
+
+err_icc_set_bw:
+ while (--p_idx >= 0)
+ icc_set_bw(jenc->icc_paths[p_idx], 0, 0);
+
+ return rc;
+}
+
+static void qcom_jpeg_icc_off(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_dev_resources *res = jenc->res;
+ int p_idx;
+
+ for (p_idx = 0; p_idx < res->num_of_icc; p_idx++)
+ icc_set_bw(jenc->icc_paths[p_idx], 0, 0);
+}
+
+static int qcom_jpeg_icc_init(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_dev_resources *res = jenc->res;
+ int p_idx;
+
+ jenc->icc_paths = devm_kcalloc(jenc->dev, res->num_of_icc, sizeof(*jenc->icc_paths),
+ GFP_KERNEL);
+ if (!jenc->icc_paths)
+ return -ENOMEM;
+
+ for (p_idx = 0; p_idx < res->num_of_icc; p_idx++) {
+ jenc->icc_paths[p_idx] = devm_of_icc_get(jenc->dev, res->icc_res[p_idx].icc_id);
+ if (IS_ERR(jenc->icc_paths[p_idx])) {
+ return dev_err_probe(jenc->dev, PTR_ERR(jenc->icc_paths[p_idx]),
+ "failed to get ICC path: %pe\n",
+ jenc->icc_paths[p_idx]);
+ }
+ }
+
+ return 0;
+}
+
+static __maybe_unused int qcom_jpeg_pm_suspend(struct device *dev)
+{
+ struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+
+ qcom_jpeg_clk_off(jenc);
+
+ qcom_jpeg_icc_off(jenc);
+
+ return 0;
+}
+
+static __maybe_unused int qcom_jpeg_pm_resume(struct device *dev)
+{
+ struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+ int rc;
+
+ rc = qcom_jpeg_icc_on(jenc);
+ if (rc)
+ return rc;
+
+ rc = qcom_jpeg_clk_on(jenc);
+ if (rc) {
+ qcom_jpeg_icc_off(jenc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static __maybe_unused int qcom_jpeg_suspend(struct device *dev)
+{
+ struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+ int rc;
+
+ v4l2_m2m_suspend(jenc->m2m_dev);
+
+ rc = pm_runtime_force_suspend(dev);
+ if (rc)
+ v4l2_m2m_resume(jenc->m2m_dev);
+
+ return rc;
+}
+
+static __maybe_unused int qcom_jpeg_resume(struct device *dev)
+{
+ struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+ int rc;
+
+ rc = pm_runtime_force_resume(dev);
+ if (rc)
+ return rc;
+
+ v4l2_m2m_resume(jenc->m2m_dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops qcom_jpeg_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(qcom_jpeg_suspend, qcom_jpeg_resume)
+ SET_RUNTIME_PM_OPS(qcom_jpeg_pm_suspend, qcom_jpeg_pm_resume, NULL)
+};
+
+static int qcom_jpeg_probe(struct platform_device *pdev)
+{
+ const struct qcom_dev_resources *res;
+ struct qcom_jenc_dev *jenc;
+ int rc;
+
+ jenc = devm_kzalloc(&pdev->dev, sizeof(*jenc), GFP_KERNEL);
+ if (!jenc)
+ return -ENOMEM;
+
+ jenc->dev = &pdev->dev;
+ mutex_init(&jenc->dev_mutex);
+ spin_lock_init(&jenc->hw_lock);
+ init_completion(&jenc->reset_complete);
+ init_completion(&jenc->stop_complete);
+
+ res = device_get_match_data(jenc->dev);
+ if (!res)
+ return dev_err_probe(jenc->dev, -ENODEV, "unsupported SoC\n");
+ jenc->res = res;
+
+ if (!jenc->res->hw_offs || !jenc->res->hw_ops)
+ return dev_err_probe(jenc->dev, -EINVAL, "missing hw resources\n");
+
+ rc = dma_set_mask_and_coherent(jenc->dev, DMA_BIT_MASK(32));
+ if (rc)
+ return dev_err_probe(jenc->dev, rc, "failed to set DMA mask\n");
+
+ jenc->jpeg_base = devm_platform_ioremap_resource_byname(pdev, "jpeg");
+ if (IS_ERR(jenc->jpeg_base))
+ return dev_err_probe(jenc->dev, PTR_ERR(jenc->jpeg_base),
+ "failed to map JPEG resource\n");
+
+ rc = qcom_jpeg_clk_init(jenc);
+ if (rc)
+ return rc;
+
+ jenc->irq = platform_get_irq(pdev, 0);
+ if (jenc->irq < 0)
+ return dev_err_probe(jenc->dev, jenc->irq, "failed to get IRQ\n");
+
+ rc = devm_request_threaded_irq(jenc->dev, jenc->irq,
+ jenc->res->hw_ops->hw_irq_top,
+ jenc->res->hw_ops->hw_irq_bot,
+ IRQF_ONESHOT, dev_name(jenc->dev), jenc);
+ if (rc)
+ return dev_err_probe(jenc->dev, rc, "failed to request IRQ\n");
+
+ rc = qcom_jpeg_icc_init(jenc);
+ if (rc)
+ return rc;
+
+ rc = v4l2_device_register(jenc->dev, &jenc->v4l2_dev);
+ if (rc) {
+ dev_err(jenc->dev, "failed to register V4L2 device\n");
+ return rc;
+ }
+
+ jenc->perf = QCOM_SOC_PERF_NOMINAL;
+
+ rc = qcom_jpeg_v4l2_register(jenc);
+ if (rc) {
+ dev_err(jenc->dev, "failed to register video device\n");
+ goto err_v4l2_device_unregister;
+ }
+
+ rc = devm_pm_runtime_enable(jenc->dev);
+ if (rc)
+ goto err_v4l2_unregister;
+
+ dev_dbg(jenc->dev, "Qualcomm JPEG encoder registered\n");
+
+ platform_set_drvdata(pdev, jenc);
+
+ return 0;
+
+err_v4l2_unregister:
+ qcom_jpeg_v4l2_unregister(jenc);
+err_v4l2_device_unregister:
+ v4l2_device_unregister(&jenc->v4l2_dev);
+ return rc;
+}
+
+static void qcom_jpeg_remove(struct platform_device *pdev)
+{
+ struct qcom_jenc_dev *jenc = platform_get_drvdata(pdev);
+
+ qcom_jpeg_v4l2_unregister(jenc);
+
+ v4l2_device_unregister(&jenc->v4l2_dev);
+
+ dev_dbg(jenc->dev, "Qualcomm JPEG encoder deregistered\n");
+}
+
+static const struct of_device_id qcom_jpeg_of_match[] = {
+ {
+ .compatible = "qcom,sm8250-jenc",
+ .data = &qcom_t165_t480_jpeg_drvdata
+ },
+ {
+ .compatible = "qcom,qcm6490-jenc",
+ .data = &qcom_t680_jpeg_drvdata
+ },
+ {
+ .compatible = "qcom,sm8550-jenc",
+ .data = &qcom_t780_jpeg_drvdata
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_jpeg_of_match);
+
+static struct platform_driver qcom_jpeg_platform_driver = {
+ .probe = qcom_jpeg_probe,
+ .remove = qcom_jpeg_remove,
+ .driver = {
+ .name = QCOM_JPEG_ENC_NAME,
+ .of_match_table = qcom_jpeg_of_match,
+ .pm = &qcom_jpeg_pm_ops,
+ },
+};
+
+module_platform_driver(qcom_jpeg_platform_driver);
+
+MODULE_DESCRIPTION("QCOM JPEG mem2mem V4L2 encoder");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
new file mode 100644
index 000000000000..151e38c4de82
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_DEV_H
+#define QCOM_JENC_DEV_H
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/irqreturn.h>
+#include <linux/interconnect.h>
+
+#include <media/videobuf2-core.h>
+
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+
+#include "qcom_jenc_res.h"
+#include "qcom_jenc_hdr.h"
+#include "qcom_jenc_defs.h"
+
+#define QCOM_JPEG_ENC_NAME "qcom-jpeg-enc"
+
+#define TYPE2QID(t) \
+ (V4L2_TYPE_IS_OUTPUT(t) ? JENC_SRC_QUEUE : JENC_DST_QUEUE)
+
+enum qcom_enc_qid {
+ JENC_SRC_QUEUE = 0,
+ JENC_DST_QUEUE,
+ JENC_QUEUE_MAX
+};
+
+struct jenc_enc_format {
+ u32 type;
+ u32 fourcc;
+};
+
+struct qcom_jpeg_buff {
+ struct {
+ struct sg_table *sgt;
+ dma_addr_t dma;
+ unsigned long size;
+
+ } plns[QCOM_JPEG_MAX_PLANES];
+};
+
+struct qcom_jenc_queue {
+ struct v4l2_pix_format_mplane vf;
+ u32 sequence;
+ struct qcom_jpeg_buff buff[VB2_MAX_FRAME];
+ int buff_id;
+};
+
+struct qcom_jenc_dev {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct video_device *vdev;
+ const struct qcom_dev_resources *res;
+ enum qcom_soc_perf_level perf;
+ int irq;
+ void __iomem *jpeg_base;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ /* device mutex lock */
+ struct mutex dev_mutex;
+ atomic_t ref_count;
+ struct completion reset_complete;
+ struct completion stop_complete;
+ /* decoder hardware lock */
+ spinlock_t hw_lock;
+ struct jenc_context *actx;
+ struct icc_path **icc_paths;
+
+ u32 pending_irq_status;
+
+ void (*enc_hw_irq_cb)
+ (void *data, enum vb2_buffer_state ev, size_t out_size);
+};
+
+struct jenc_context {
+ struct device *dev;
+ struct qcom_jenc_dev *jenc;
+ struct v4l2_fh fh;
+
+ /* quality update lock */
+ struct mutex quality_mutex;
+ struct v4l2_ctrl *quality_ctl;
+ u32 quality_requested;
+ u32 quality_programmed;
+ struct v4l2_ctrl_handler ctrl_hdl;
+
+ /* session context lock */
+ struct mutex ctx_lock;
+
+ bool is_stopping;
+
+ struct qcom_jenc_queue bufq[JENC_QUEUE_MAX];
+ struct qcom_jenc_header hdr_cache;
+
+ struct work_struct finish_work;
+ struct work_struct stop_work;
+};
+
+#endif
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
new file mode 100644
index 000000000000..63826d5cab56
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <media/jpeg.h>
+#include <media/v4l2-jpeg.h>
+
+#include "qcom_jenc_hdr.h"
+#include "qcom_jenc_dev.h"
+
+/*
+ * The elements defined in this header are specified
+ * in the ITU-T T.81 / JPEG specification.
+ *
+ * https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+
+#define JFIF_HEADER_WIDTH_OFFS 0x07
+#define JFIF_HEADER_HEIGHT_OFFS 0x05
+
+#define JPEG_MARKER_PREFIX 0xff
+
+#define JFIF_APP0_LENGTH_HI 0x00
+#define JFIF_APP0_LENGTH_LO 0x10
+#define JFIF_IDENT_TERM 0x00
+#define JFIF_VERSION_MAJOR 0x01
+#define JFIF_VERSION_MINOR 0x01
+#define JFIF_DENSITY_HI 0x00
+#define JFIF_DENSITY_LO 0x01
+#define JFIF_THUMBNAIL_SIZE 0x00
+
+#define JPEG_SEG_LEN_HI 0x00
+#define JPEG_LEN_DQT_LUMA_LO 0x43
+#define JPEG_LEN_DQT_CHROMA_LO 0x84
+#define JPEG_LEN_SOF0_MONO_LO 0x0b
+#define JPEG_LEN_SOF0_COLOR_LO 0x11
+#define JPEG_LEN_DHT_MONO_LO 0xd2
+#define JPEG_LEN_DHT_COLOR_HI 0x01
+#define JPEG_LEN_DHT_COLOR_LO 0xa2
+#define JPEG_LEN_SOS_MONO_LO 0x08
+#define JPEG_LEN_SOS_COLOR_LO 0x0c
+
+#define JPEG_SAMPLE_PRECISION_BITS 0x08
+#define JPEG_COMP_MONO 1
+#define JPEG_COMP_COLOR 3
+
+#define JPEG_SAMPLING_H1V1 0x11
+#define JPEG_SAMPLING_H2V2 0x22
+
+#define JPEG_QTABLE_LUMA 0
+#define JPEG_QTABLE_CHROMA 1
+
+#define JPEG_DC_HT_INDEX_LUMA 0x00
+#define JPEG_DC_HT_INDEX_CHROMA 0x01
+#define JPEG_AC_HT_INDEX_LUMA 0x10
+#define JPEG_AC_HT_INDEX_CHROMA 0x11
+
+#define JPEG_SPECTRAL_START 0x00
+#define JPEG_SPECTRAL_END 0x3f
+#define JPEG_APPROX_HIGH_LOW 0x00
+#define JPEG_TABLE_SEL_LUMA 0x00
+#define JPEG_TABLE_SEL_CHROMA 0x11
+
+struct jpeg_header_buf {
+ u8 *ptr;
+ u32 size;
+ u32 pos;
+};
+
+static const struct jpeg_soi_app0 soi_app0 = {
+ .soi = { JPEG_MARKER_PREFIX, JPEG_MARKER_SOI },
+ .app0_marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_APP0 },
+ .app0_length = { JFIF_APP0_LENGTH_HI, JFIF_APP0_LENGTH_LO },
+ .jfif_id = { 'J', 'F', 'I', 'F', JFIF_IDENT_TERM },
+ .version = { JFIF_VERSION_MAJOR, JFIF_VERSION_MINOR },
+ .units = 0x00,
+ .density_x = { JFIF_DENSITY_HI, JFIF_DENSITY_LO },
+ .density_y = { JFIF_DENSITY_HI, JFIF_DENSITY_LO },
+ .thumb_x = JFIF_THUMBNAIL_SIZE,
+ .thumb_y = JFIF_THUMBNAIL_SIZE,
+};
+
+static const struct jpeg_record_hdr dqt_luma_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DQT },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_DQT_LUMA_LO }
+};
+
+/* Luminance quantization table */
+static const struct jpeg_dqt_header dqt_luma_data = {
+ .index = 0x00,
+};
+
+static const struct jpeg_record_hdr dqt_chroma_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DQT },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_DQT_CHROMA_LO }
+};
+
+/* Chrominance quantization table */
+static const struct jpeg_dqt_header dqt_chroma_data = {
+ .index = 0x01,
+};
+
+static const struct jpeg_record_hdr sof0_mono_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_SOF0 },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_SOF0_MONO_LO },
+};
+
+static const struct jpeg_sof0_mono sof0_mono_data = {
+ .precision = JPEG_SAMPLE_PRECISION_BITS,
+ .height = { 0x00, 0x00 },
+ .width = { 0x00, 0x00 },
+ .components = JPEG_COMP_MONO,
+ .y_id = 1,
+ .y_sampling = JPEG_SAMPLING_H1V1,
+ .y_qtable = JPEG_QTABLE_LUMA,
+};
+
+static const struct jpeg_record_hdr sof0_color_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_SOF0 },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_SOF0_COLOR_LO },
+};
+
+static const struct jpeg_sof0_color sof0_color_data = {
+ .precision = JPEG_SAMPLE_PRECISION_BITS,
+ .height = { 0x00, 0x00 },
+ .width = { 0x00, 0x00 },
+ .components = JPEG_COMP_COLOR,
+ .y_id = 1,
+ .y_sampling = JPEG_SAMPLING_H2V2,
+ .y_qtable = JPEG_QTABLE_LUMA,
+ .cb_id = 2,
+ .cb_sampling = JPEG_SAMPLING_H1V1,
+ .cb_qtable = JPEG_QTABLE_CHROMA,
+ .cr_id = 3,
+ .cr_sampling = JPEG_SAMPLING_H1V1,
+ .cr_qtable = JPEG_QTABLE_CHROMA,
+};
+
+static const struct jpeg_record_hdr coeff_mono_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DHT },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_DHT_MONO_LO },
+};
+
+static const struct jpeg_record_hdr coeff_color_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DHT },
+ .length = { JPEG_LEN_DHT_COLOR_HI, JPEG_LEN_DHT_COLOR_LO },
+};
+
+static const struct jpeg_record_hdr sos_mono_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_SOS },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_SOS_MONO_LO },
+};
+
+static const struct jpeg_sos_mono sos_mono_data = {
+ .components = JPEG_COMP_MONO,
+ .y_id = 1,
+ .y_tables = JPEG_TABLE_SEL_LUMA,
+ .spectral = { JPEG_SPECTRAL_START, JPEG_SPECTRAL_END },
+ .approx = JPEG_APPROX_HIGH_LOW,
+};
+
+static const struct jpeg_record_hdr sos_color_hdr = {
+ .marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_SOS },
+ .length = { JPEG_SEG_LEN_HI, JPEG_LEN_SOS_COLOR_LO },
+};
+
+static const struct jpeg_sos_color sos_color_data = {
+ .components = JPEG_COMP_COLOR,
+ .y_id = 1,
+ .y_tables = JPEG_TABLE_SEL_LUMA,
+ .cb_id = 2,
+ .cb_tables = JPEG_TABLE_SEL_CHROMA,
+ .cr_id = 3,
+ .cr_tables = JPEG_TABLE_SEL_CHROMA,
+ .spectral = { JPEG_SPECTRAL_START, JPEG_SPECTRAL_END },
+ .approx = JPEG_APPROX_HIGH_LOW,
+};
+
+static inline int jb_put_mem(struct jpeg_header_buf *hdr, const void *src, u32 len)
+{
+ if (len > hdr->size - hdr->pos)
+ return -ENOSPC;
+
+ memcpy(hdr->ptr + hdr->pos, src, len);
+ hdr->pos += len;
+
+ return 0;
+}
+
+static int jb_put_dht(struct jpeg_header_buf *hdr, u8 index, const u8 *table, u32 len)
+{
+ u8 data[1 + V4L2_JPEG_REF_HT_AC_LEN];
+
+ if (len > V4L2_JPEG_REF_HT_AC_LEN)
+ return -EINVAL;
+
+ data[0] = index;
+ memcpy(&data[1], table, len);
+
+ return jb_put_mem(hdr, data, len + 1);
+}
+
+static inline void patch_u16be(u8 *buf, u32 off, u16 v)
+{
+ buf[off] = (v >> 8) & 0xff;
+ buf[off + 1] = v & 0xff;
+}
+
+int qcom_jenc_header_init(struct qcom_jenc_header *c, u32 fourcc)
+{
+ int rc;
+ struct jpeg_header_buf hdr = {
+ .ptr = c->data,
+ .size = sizeof(c->data),
+ .pos = 0,
+ };
+
+ c->sof_offset = 0;
+ c->dqt_one_offs = 0;
+ c->dqt_two_offs = 0;
+
+ rc = jb_put_mem(&hdr, &soi_app0, sizeof(soi_app0));
+ if (rc)
+ return rc;
+
+ if (fourcc != V4L2_PIX_FMT_GREY) {
+ rc = jb_put_mem(&hdr, &dqt_chroma_hdr, sizeof(dqt_chroma_hdr));
+ if (rc)
+ return rc;
+
+ /* Store the offset of the first DQT table for later use. */
+ c->dqt_one_offs = hdr.pos;
+ rc = jb_put_mem(&hdr, &dqt_luma_data, sizeof(dqt_luma_data));
+ if (rc)
+ return rc;
+
+ /* Store the offset of the second DQT table for later use. */
+ c->dqt_two_offs = hdr.pos;
+ rc = jb_put_mem(&hdr, &dqt_chroma_data, sizeof(dqt_chroma_data));
+ if (rc)
+ return rc;
+ } else {
+ rc = jb_put_mem(&hdr, &dqt_luma_hdr, sizeof(dqt_luma_hdr));
+ if (rc)
+ return rc;
+
+ /* Store the offset of the first DQT table for later use. */
+ c->dqt_one_offs = hdr.pos;
+ rc = jb_put_mem(&hdr, &dqt_luma_data, sizeof(dqt_luma_data));
+ if (rc)
+ return rc;
+ }
+
+ /* Store the offset of the SOF record for later use. */
+ c->sof_offset = hdr.pos;
+
+ if (fourcc != V4L2_PIX_FMT_GREY) {
+ rc = jb_put_mem(&hdr, &sof0_color_hdr, sizeof(sof0_color_hdr));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &sof0_color_data, sizeof(sof0_color_data));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &coeff_color_hdr, sizeof(coeff_color_hdr));
+ if (rc)
+ return rc;
+ rc = jb_put_dht(&hdr, JPEG_DC_HT_INDEX_LUMA,
+ v4l2_jpeg_ref_table_luma_dc_ht,
+ ARRAY_SIZE(v4l2_jpeg_ref_table_luma_dc_ht));
+ if (rc)
+ return rc;
+ rc = jb_put_dht(&hdr, JPEG_AC_HT_INDEX_LUMA,
+ v4l2_jpeg_ref_table_luma_ac_ht,
+ ARRAY_SIZE(v4l2_jpeg_ref_table_luma_ac_ht));
+ if (rc)
+ return rc;
+ rc = jb_put_dht(&hdr, JPEG_DC_HT_INDEX_CHROMA,
+ v4l2_jpeg_ref_table_chroma_dc_ht,
+ ARRAY_SIZE(v4l2_jpeg_ref_table_chroma_dc_ht));
+ if (rc)
+ return rc;
+ rc = jb_put_dht(&hdr, JPEG_AC_HT_INDEX_CHROMA,
+ v4l2_jpeg_ref_table_chroma_ac_ht,
+ ARRAY_SIZE(v4l2_jpeg_ref_table_chroma_ac_ht));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &sos_color_hdr, sizeof(sos_color_hdr));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &sos_color_data, sizeof(sos_color_data));
+ if (rc)
+ return rc;
+ } else {
+ rc = jb_put_mem(&hdr, &sof0_mono_hdr, sizeof(sof0_mono_hdr));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &sof0_mono_data, sizeof(sof0_mono_data));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &coeff_mono_hdr, sizeof(coeff_mono_hdr));
+ if (rc)
+ return rc;
+ rc = jb_put_dht(&hdr, JPEG_DC_HT_INDEX_LUMA,
+ v4l2_jpeg_ref_table_luma_dc_ht,
+ ARRAY_SIZE(v4l2_jpeg_ref_table_luma_dc_ht));
+ if (rc)
+ return rc;
+ rc = jb_put_dht(&hdr, JPEG_AC_HT_INDEX_LUMA,
+ v4l2_jpeg_ref_table_luma_ac_ht,
+ ARRAY_SIZE(v4l2_jpeg_ref_table_luma_ac_ht));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &sos_mono_hdr, sizeof(sos_mono_hdr));
+ if (rc)
+ return rc;
+ rc = jb_put_mem(&hdr, &sos_mono_data, sizeof(sos_mono_data));
+ if (rc)
+ return rc;
+ }
+
+ c->size = hdr.pos;
+
+ return 0;
+}
+
+void qcom_jenc_dqts_emit(const struct qcom_jenc_header *c, u8 *dst)
+{
+ /* Propagate DQT tables into the JPEG header */
+ if (c->dqt_one_offs) {
+ u32 one_offs = c->dqt_one_offs + sizeof(dqt_luma_data.index);
+
+ memcpy(dst + one_offs, &c->data[one_offs], sizeof(dqt_luma_data.value));
+ }
+
+ if (c->dqt_two_offs) {
+ u32 two_offs = c->dqt_two_offs + sizeof(dqt_chroma_data.index);
+
+ memcpy(dst + two_offs, &c->data[two_offs], sizeof(dqt_chroma_data.value));
+ }
+}
+
+u32 qcom_jenc_header_emit(const struct qcom_jenc_header *c, u8 *dst, u32 dst_size, u16 width,
+ u16 height)
+{
+ /* Copy JFIF into JPEG header and update actual image size */
+ if (dst_size < c->size)
+ return 0;
+
+ memcpy(dst, c->data, c->size);
+
+ /* Update output image size */
+ patch_u16be(dst, c->sof_offset + JFIF_HEADER_WIDTH_OFFS, width);
+ patch_u16be(dst, c->sof_offset + JFIF_HEADER_HEIGHT_OFFS, height);
+
+ return c->size;
+}
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
new file mode 100644
index 000000000000..90eb4defcef0
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_HDR_H
+#define QCOM_JENC_HDR_H
+
+#include <linux/types.h>
+
+#include <media/v4l2-jpeg.h>
+
+#include "qcom_jenc_defs.h"
+
+#define JPEG_HEADER_MAX 1024
+
+struct qcom_jenc_header {
+ u8 data[JPEG_HEADER_MAX];
+ u32 size;
+ u32 sof_offset;
+ u32 dqt_one_offs;
+ u32 dqt_two_offs;
+};
+
+struct jpeg_record_hdr {
+ u8 marker[2];
+ u8 length[2];
+} __packed;
+
+struct jpeg_dqt_header {
+ u8 index;
+ u8 value[V4L2_JPEG_PIXELS_IN_BLOCK];
+} __packed;
+
+struct jpeg_soi_app0 {
+ u8 soi[2];
+ u8 app0_marker[2];
+ u8 app0_length[2];
+ u8 jfif_id[5];
+ u8 version[2];
+ u8 units;
+ u8 density_x[2];
+ u8 density_y[2];
+ u8 thumb_x;
+ u8 thumb_y;
+} __packed;
+
+struct jpeg_sof0_mono {
+ u8 precision;
+ u8 height[2];
+ u8 width[2];
+ u8 components;
+
+ u8 y_id;
+ u8 y_sampling;
+ u8 y_qtable;
+} __packed;
+
+struct jpeg_sof0_color {
+ u8 precision;
+ u8 height[2];
+ u8 width[2];
+ u8 components;
+
+ u8 y_id;
+ u8 y_sampling;
+ u8 y_qtable;
+
+ u8 cb_id;
+ u8 cb_sampling;
+ u8 cb_qtable;
+
+ u8 cr_id;
+ u8 cr_sampling;
+ u8 cr_qtable;
+} __packed;
+
+struct jpeg_sos_hdr {
+ u8 sos_marker[2];
+ u8 sos_length[2];
+ u8 components;
+} __packed;
+
+struct jpeg_sos_mono {
+ u8 components;
+
+ u8 y_id;
+ u8 y_tables;
+
+ u8 spectral[2];
+ u8 approx;
+} __packed;
+
+struct jpeg_sos_color {
+ u8 components;
+
+ u8 y_id;
+ u8 y_tables;
+
+ u8 cb_id;
+ u8 cb_tables;
+
+ u8 cr_id;
+ u8 cr_tables;
+
+ u8 spectral[2];
+ u8 approx;
+} __packed;
+
+struct jenc_context;
+
+int qcom_jenc_header_init(struct qcom_jenc_header *c, u32 fourcc);
+
+void qcom_jenc_dqts_emit(const struct qcom_jenc_header *c, u8 *dst);
+
+u32 qcom_jenc_header_emit(const struct qcom_jenc_header *c, u8 *dst, u32 dst_size, u16 width,
+ u16 height);
+
+#endif /* QCOM_JENC_HDR_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
new file mode 100644
index 000000000000..acfa24c663fc
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
@@ -0,0 +1,1658 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <asm/div64.h>
+#include <linux/pm_runtime.h>
+#include <linux/scatterlist.h>
+
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "qcom_jenc_dev.h"
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_defs.h"
+
+#define JPEG_RESET_TIMEOUT_MS 300
+#define JPEG_STOP_TIMEOUT_MS 200
+
+#define JPEG_DQT_SHIFT 20
+#define JPEG_Q5_21_SHIFT 21
+
+#define JPEG_MCU_BLOCK_8 8
+#define JPEG_MCU_BLOCK_16 16
+#define JPEG_MCU_BLOCK_128 128
+#define JPEG_MCU_BLOCK_256 256
+
+#define JPEG_DEFAULT_SCALE_STEP 0x200000
+
+#define JPEG_CLR_U32 (0U)
+#define JPEG_SET_U32 (~0U)
+
+/*
+ * JPEG | V4L2
+ * ---- | -------
+ * H1V1 | GREY
+ * H1V2 | YUV422M
+ * H2V1 | NV16M
+ * H2V2 | NV12M
+ */
+enum qcom_jpeg_encode_fmt {
+ JPEG_ENCODE_H1V1 = 0,
+ JPEG_ENCODE_H1V2,
+ JPEG_ENCODE_H2V1,
+ JPEG_ENCODE_H2V2,
+ JPEG_ENCODE_MONO,
+};
+
+enum qcom_jpeg_memory_fmt {
+ JPEG_MEM_FMT_PLANAR = 0x0,
+ JPEG_MEM_FMT_PPLANAR = 0x1,
+ JPEG_MEM_FMT_MONO = 0x2,
+ JPEG_MEM_FMT_COEFFICIENT = 0x3
+};
+
+enum jpeg_mal_bounds {
+ JPEG_CFG_MAL_BOUND_32_BYTES = 0x0,
+ JPEG_CFG_MAL_BOUND_64_BYTES = 0x1,
+ JPEG_CFG_MAL_BOUND_128_BYTES = 0x2,
+ JPEG_CFG_MAL_BOUND_256_BYTES = 0x3,
+ JPEG_CFG_MAL_BOUND_512_BYTES = 0x4,
+ JPEG_CFG_MAL_BOUND_1K_BYTES = 0x5,
+ JPEG_CFG_MAL_BOUND_2K_BYTES = 0x6,
+ JPEG_CFG_MAL_BOUND_4K_BYTES = 0x7
+};
+
+struct qcom_jpeg_scale_blocks {
+ u8 w_block[QCOM_JPEG_MAX_PLANES];
+ u8 h_block[QCOM_JPEG_MAX_PLANES];
+};
+
+struct qcom_jpeg_mal_boundary {
+ u32 bytes;
+ int boundary;
+};
+
+struct qcom_jpeg_formats {
+ u32 fourcc;
+ enum qcom_jpeg_encode_fmt encode;
+ enum qcom_jpeg_memory_fmt memory;
+};
+
+/*
+ * Luminance quantization table defined by CCITT T.81.
+ * See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+static const u8 t81k1_dct_luma_table[V4L2_JPEG_PIXELS_IN_BLOCK] = {
+ 16, 11, 10, 16, 24, 40, 51, 61,
+ 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56,
+ 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68, 109, 103, 77,
+ 24, 35, 55, 64, 81, 104, 113, 92,
+ 49, 64, 78, 87, 103, 121, 120, 101,
+ 72, 92, 95, 98, 112, 100, 103, 99
+};
+
+/*
+ * Chrominance quantization table defined by CCITT T.81.
+ * See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+static const u8 t81k2_dct_chroma_table[V4L2_JPEG_PIXELS_IN_BLOCK] = {
+ 17, 18, 24, 47, 99, 99, 99, 99,
+ 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99,
+ 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99
+};
+
+/*
+ * Zig-zag scan order for quantized DCT coefficients
+ * as defined by CCITT T.81.
+ * See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+static const u8 t81a6_dct_zig_zag_table[] = {
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63
+};
+
+static const u8 jpeg_mcu_per_ratio[] = {
+ 0, /* MCU = 1, Ratio < 2x */
+ 3, /* MCU = 0, 2x <= Ratio < 4x */
+ 2, /* MCU = 0, 4x <= Ratio < 8x */
+ 1, /* MCU = 0, 8x <= Ratio < 16x */
+ 0, /* MCU = 0, Ratio > 16x */
+};
+
+static const struct qcom_jpeg_formats jpeg_encode_fmt[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_GREY,
+ .encode = JPEG_ENCODE_MONO,
+ .memory = JPEG_MEM_FMT_MONO
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_JPEG,
+ .encode = JPEG_ENCODE_H1V1,
+ .memory = JPEG_MEM_FMT_PPLANAR
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV422M,
+ .encode = JPEG_ENCODE_H1V2,
+ .memory = JPEG_MEM_FMT_PLANAR
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YVU422M,
+ .encode = JPEG_ENCODE_H1V2,
+ .memory = JPEG_MEM_FMT_PLANAR
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV16M,
+ .encode = JPEG_ENCODE_H2V1,
+ .memory = JPEG_MEM_FMT_PPLANAR
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV61M,
+ .encode = JPEG_ENCODE_H2V1,
+ .memory = JPEG_MEM_FMT_PPLANAR
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .encode = JPEG_ENCODE_H2V2,
+ .memory = JPEG_MEM_FMT_PPLANAR
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21M,
+ .encode = JPEG_ENCODE_H2V2,
+ .memory = JPEG_MEM_FMT_PPLANAR
+ }
+};
+
+static const struct qcom_jpeg_mal_boundary jpeg_mal_bounds[] = {
+ { .bytes = 32, .boundary = JPEG_CFG_MAL_BOUND_32_BYTES },
+ { .bytes = 64, .boundary = JPEG_CFG_MAL_BOUND_64_BYTES },
+ { .bytes = 128, .boundary = JPEG_CFG_MAL_BOUND_128_BYTES },
+ { .bytes = 256, .boundary = JPEG_CFG_MAL_BOUND_256_BYTES },
+ { .bytes = 512, .boundary = JPEG_CFG_MAL_BOUND_512_BYTES },
+ { .bytes = 1024, .boundary = JPEG_CFG_MAL_BOUND_1K_BYTES },
+ { .bytes = 4096, .boundary = JPEG_CFG_MAL_BOUND_4K_BYTES }
+};
+
+static const struct qcom_jpeg_scale_blocks jpeg_mcu_blocks[] = {
+ [JPEG_ENCODE_H1V1] = {
+ .w_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ .h_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ },
+ [JPEG_ENCODE_H1V2] = {
+ .w_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ .h_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ },
+ [JPEG_ENCODE_H2V1] = {
+ .w_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ .h_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ },
+ [JPEG_ENCODE_H2V2] = {
+ .w_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ .h_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+ },
+ [JPEG_ENCODE_MONO] = {
+ .w_block = { JPEG_MCU_BLOCK_8 },
+ .h_block = { JPEG_MCU_BLOCK_8 }
+ },
+};
+
+static inline int jpeg_get_memory_fmt(u32 fourcc)
+{
+ u32 fi;
+
+ for (fi = 0; fi < ARRAY_SIZE(jpeg_encode_fmt); fi++) {
+ if (jpeg_encode_fmt[fi].fourcc == fourcc)
+ return jpeg_encode_fmt[fi].memory;
+ }
+
+ return -EINVAL;
+}
+
+static inline int jpeg_get_encode_fmt(u32 fourcc)
+{
+ u32 fi;
+
+ for (fi = 0; fi < ARRAY_SIZE(jpeg_encode_fmt); fi++) {
+ if (jpeg_encode_fmt[fi].fourcc == fourcc)
+ return jpeg_encode_fmt[fi].encode;
+ }
+
+ return -EINVAL;
+}
+
+static inline int jpeg_get_mal_boundary(u32 width, const struct qcom_jpeg_mal_boundary *table,
+ u32 count)
+{
+ u32 bi;
+
+ if (WARN_ON_ONCE(!table || !count))
+ return -EINVAL;
+
+ for (bi = 0; bi < count; bi++) {
+ if (table[bi].bytes > width)
+ break;
+ }
+
+ if (!bi)
+ return table[0].boundary;
+
+ if (bi >= count)
+ return table[count - 1].boundary;
+
+ return table[bi - 1].boundary;
+}
+
+static inline u8 jpeg_get_mcu_per_block(u32 src_size, u32 dst_size)
+{
+ u8 h_rto;
+
+ if (WARN_ON_ONCE(!src_size || !dst_size))
+ return 0;
+
+ /* Calculate scale factor */
+ h_rto = max(src_size, dst_size) / min(src_size, dst_size);
+
+ if (h_rto < 2)
+ return jpeg_mcu_per_ratio[0];
+ if (h_rto < 4)
+ return jpeg_mcu_per_ratio[1];
+ if (h_rto < 8)
+ return jpeg_mcu_per_ratio[2];
+ if (h_rto < 16)
+ return jpeg_mcu_per_ratio[3];
+
+ return jpeg_mcu_per_ratio[4];
+}
+
+static inline int jpeg_get_mcu_geometry(enum qcom_jpeg_encode_fmt fmt, u32 width, u32 height,
+ u32 *blk_w, u32 *blk_h, u32 *mcu_cols, u32 *mcu_rows)
+{
+ const struct qcom_jpeg_scale_blocks *blks;
+ u32 bw = 0, bh = 0;
+ u8 pln;
+
+ /*
+ * Dimensions are validated and normalized through V4L2 format negotiation;
+ * this is a defensive guard for unexpected internal callers.
+ */
+ if (WARN_ON_ONCE(!width || !height))
+ return -EINVAL;
+
+ blks = &jpeg_mcu_blocks[fmt];
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ bw = max(bw, blks->w_block[pln]);
+ bh = max(bh, blks->h_block[pln]);
+ }
+
+ if (!bw || !bh)
+ return -EINVAL;
+
+ if (blk_w)
+ *blk_w = bw;
+ if (blk_h)
+ *blk_h = bh;
+
+ if (mcu_cols)
+ *mcu_cols = ALIGN(width, bw) / bw;
+
+ if (mcu_rows)
+ *mcu_rows = ALIGN(height, bh) / bh;
+
+ return 0;
+}
+
+/* Integer part of scale */
+static inline s32 jpeg_calc_scale_int(u32 in_width, u32 out_width)
+{
+ if (WARN_ON_ONCE(!out_width))
+ return 0;
+
+ return (s32)(in_width / out_width);
+}
+
+/* Fractional part of scale */
+static inline u32 jpeg_calc_scale_frac(u32 in_width, u32 out_width)
+{
+ u32 remainder;
+
+ if (WARN_ON_ONCE(!out_width))
+ return 0;
+
+ remainder = in_width % out_width;
+
+ /* 64-bit to avoid overflow during shift */
+ return (u32)(((u64)remainder << JPEG_Q5_21_SHIFT) / out_width);
+}
+
+static inline s32 jpeg_calc_q5_21(s32 int_part, u32 frac_part)
+{
+ return ((s32)((u32)int_part << JPEG_Q5_21_SHIFT)) | (frac_part & ((1u << 21) - 1));
+}
+
+static inline u32 jpeg_io_read(struct qcom_jenc_dev *jenc, u32 offset)
+{
+ return readl(jenc->jpeg_base + offset);
+}
+
+static inline void jpeg_io_write(struct qcom_jenc_dev *jenc, u32 offset, u32 value)
+{
+ writel(value, jenc->jpeg_base + offset);
+}
+
+/*
+ * Runtime bitfield helpers (for non-constant masks).
+ *
+ * Requirements:
+ * - mask must be non-zero
+ * - mask must be contiguous (e.g. 0x7u << n)
+ */
+
+static inline u32 jpeg_bits_get(u32 mask, u32 val)
+{
+ /* __ffs(0) is undefined; fail-safe on invalid masks. */
+ if (WARN_ON_ONCE(!mask))
+ return 0;
+
+ return (val & mask) >> __ffs(mask);
+}
+
+static inline u32 jpeg_bits_set(u32 mask, u32 val)
+{
+ /* __ffs(0) is undefined; fail-safe on invalid masks. */
+ if (WARN_ON_ONCE(!mask))
+ return 0;
+
+ return (val << __ffs(mask)) & mask;
+}
+
+static inline u32 jpeg_rd_bits(struct qcom_jenc_dev *jenc, u32 offs, enum qcom_jpeg_mask_id mid)
+{
+ u32 reg = jpeg_io_read(jenc, offs);
+ u32 mask = jenc->res->hw_mask[mid];
+
+ return jpeg_bits_get(mask, reg);
+}
+
+/*
+ * Read-modify-write (for R/W registers)
+ */
+static inline void jpeg_rw_bits(struct qcom_jenc_dev *jenc, u32 offs, enum qcom_jpeg_mask_id mid,
+ u32 val)
+{
+ u32 reg = jpeg_io_read(jenc, offs);
+ u32 mask = jenc->res->hw_mask[mid];
+
+ reg &= ~mask;
+ reg |= jpeg_bits_set(mask, val);
+
+ jpeg_io_write(jenc, offs, reg);
+}
+
+/*
+ * Write-only variant (for write only registers)
+ */
+static inline void jpeg_wo_bits(struct qcom_jenc_dev *jenc, u32 offs, enum qcom_jpeg_mask_id mid,
+ u32 val)
+{
+ u32 mask = jenc->res->hw_mask[mid];
+
+ jpeg_io_write(jenc, offs, jpeg_bits_set(mask, val));
+}
+
+static u8 jpeg_calculate_dqt(struct jenc_context *ectx, u8 dqt_value)
+{
+ u64 ratio;
+ u8 calc_val;
+
+ ratio = (QCOM_JPEG_QUALITY_MAX - ectx->quality_requested) << JPEG_DQT_SHIFT;
+ ratio = max_t(u64, 1, ratio);
+ do_div(ratio, QCOM_JPEG_QUALITY_MID);
+
+ calc_val = DIV64_U64_ROUND_CLOSEST(ratio * dqt_value, 1LU << JPEG_DQT_SHIFT);
+
+ return max_t(u8, 1, calc_val);
+}
+
+/*
+ * jpeg_update_dqt_cache - compute scaled DQT coefficients and store them in
+ * the software JPEG header cache (hdr_cache). Safe to call from buf_prepare
+ * before the hardware is powered on; no MMIO access is performed here.
+ */
+static void jpeg_update_dqt_cache(struct jenc_context *ectx)
+{
+ u8 *base;
+ u8 dqt_val, idx;
+ int i;
+
+ /* Luma DQT cache update */
+ if (ectx->hdr_cache.dqt_one_offs) {
+ base = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_one_offs + 1];
+ for (i = 0; i < ARRAY_SIZE(t81k1_dct_luma_table); i++) {
+ dqt_val = jpeg_calculate_dqt(ectx, t81k1_dct_luma_table[i]);
+ idx = t81a6_dct_zig_zag_table[i];
+ base[idx] = dqt_val;
+ }
+ }
+
+ /* Chroma DQT cache update */
+ if (ectx->hdr_cache.dqt_two_offs) {
+ base = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_two_offs + 1];
+ for (i = 0; i < ARRAY_SIZE(t81k2_dct_chroma_table); i++) {
+ dqt_val = jpeg_calculate_dqt(ectx, t81k2_dct_chroma_table[i]);
+ idx = t81a6_dct_zig_zag_table[i];
+ base[idx] = dqt_val;
+ }
+ }
+}
+
+/*
+ * jpeg_upload_dmi_table - write the scaled DQT coefficients to the hardware
+ * DMI registers. Must only be called from the job execution path where
+ * runtime PM has already been acquired (pm_runtime_resume_and_get).
+ *
+ * Reads precomputed values from hdr_cache (populated by jpeg_update_dqt_cache)
+ * to avoid redundant per-coefficient recalculation on the hot encode path.
+ */
+static void jpeg_upload_dmi_table(struct jenc_context *ectx)
+{
+ const struct qcom_jpeg_reg_offs *offs = ectx->jenc->res->hw_offs;
+ const u8 *luma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_one_offs + 1];
+ u32 pcfg = { 0x00000011 };
+ u32 addr = { 0x00000000 };
+ u32 reg_val;
+ int i;
+
+ /* DMI upload start sequence */
+ jpeg_io_write(ectx->jenc, offs->dmi_addr, addr);
+ jpeg_io_write(ectx->jenc, offs->dmi_cfg, pcfg);
+
+ /* DMI Luma upload — values are stored in zigzag order in hdr_cache */
+ for (i = 0; i < ARRAY_SIZE(t81k1_dct_luma_table); i++) {
+ reg_val = div_u64(U16_MAX + 1U, luma_qt[i]);
+ reg_val = clamp_t(u32, reg_val, 0, U16_MAX);
+ jpeg_io_write(ectx->jenc, offs->dmi_data, reg_val);
+ }
+
+ /* DMI Chroma upload — only present for color formats */
+ if (ectx->hdr_cache.dqt_two_offs) {
+ const u8 *chroma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_two_offs + 1];
+
+ for (i = 0; i < ARRAY_SIZE(t81k2_dct_chroma_table); i++) {
+ reg_val = div_u64(U16_MAX + 1U, chroma_qt[i]);
+ reg_val = clamp_t(u32, reg_val, 0, U16_MAX);
+ jpeg_io_write(ectx->jenc, offs->dmi_data, reg_val);
+ }
+ }
+
+ /* DMI upload end sequence */
+ jpeg_io_write(ectx->jenc, offs->dmi_cfg, addr);
+
+ ectx->quality_programmed = ectx->quality_requested;
+
+ dev_dbg(ectx->dev, "%s: ctx=%p quality_programmed=%d\n", __func__, ectx,
+ ectx->quality_programmed);
+}
+
+static void jpeg_sync_sg(struct device *dev,
+ struct qcom_jpeg_buff *frame,
+ enum dma_data_direction direction, bool for_device)
+{
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ struct sg_table *sgt = frame->plns[pln].sgt;
+
+ if (!frame->plns[pln].dma || !sgt)
+ break;
+
+ if (for_device)
+ dma_sync_sgtable_for_device(dev, sgt, direction);
+ else
+ dma_sync_sgtable_for_cpu(dev, sgt, direction);
+ }
+}
+
+static int jpeg_init(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_reg_offs *offs;
+ unsigned long rtime;
+ u32 hw_ver;
+
+ if (!jenc || !jenc->dev || !jenc->jpeg_base || !jenc->res->hw_offs) {
+ pr_err("encoder HW init failed\n");
+ return -EINVAL;
+ }
+
+ offs = jenc->res->hw_offs;
+
+ jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+ jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_RESET_ACK, JPEG_SET_U32);
+
+ reinit_completion(&jenc->reset_complete);
+
+ jpeg_wo_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_COMMON, JPEG_SET_U32);
+
+ rtime = wait_for_completion_timeout(&jenc->reset_complete,
+ msecs_to_jiffies(JPEG_RESET_TIMEOUT_MS));
+ if (!rtime) {
+ dev_err(jenc->dev, "encoder HW reset timeout\n");
+ disable_irq(jenc->irq);
+ return -ETIME;
+ }
+
+ hw_ver = jpeg_io_read(jenc, offs->hw_version);
+ dev_dbg(jenc->dev, "JPEG HW encoder version %d.%d.%d\n",
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_VER_MAJOR], hw_ver),
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_VER_MINOR], hw_ver),
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_VER_STEP], hw_ver));
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_SET_U32);
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_CLR_U32);
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_SET_U32);
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_CLR_U32);
+
+ jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+ jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+
+ return 0;
+}
+
+static int jpeg_exec(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_START, 1);
+
+ return 0;
+}
+
+static void jpeg_stop(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_START, 0);
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_SET_U32);
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_CLR_U32);
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_SET_U32);
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_CLR_U32);
+
+ jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+ jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+}
+
+static int jpeg_deinit(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ unsigned long rtime;
+
+ jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+ jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_STOP_ACK, JPEG_SET_U32);
+
+ jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_STOP, 1);
+
+ reinit_completion(&jenc->stop_complete);
+ rtime = wait_for_completion_timeout(&jenc->stop_complete,
+ msecs_to_jiffies(JPEG_STOP_TIMEOUT_MS));
+ if (!rtime) {
+ dev_err(jenc->dev, "encoder HW stop timeout\n");
+ return -ETIME;
+ }
+
+ jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_ALL_BITS, JPEG_CLR_U32);
+ jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+
+ return 0;
+}
+
+static int jpeg_apply_fe_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
+ struct vb2_buffer *vb)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct qcom_jpeg_buff *frame = &q->buff[vb->index];
+ struct v4l2_pix_format_mplane *fmt = &q->vf;
+ unsigned long flags;
+ u8 pln = 0;
+
+ if (WARN_ON_ONCE(!frame->plns[pln].dma))
+ return -EPERM;
+
+ jpeg_sync_sg(jenc->dev, frame, DMA_TO_DEVICE, true);
+
+ for (pln = 0; pln < fmt->num_planes; pln++) {
+ if (!frame->plns[pln].sgt || !frame->plns[pln].sgt->sgl)
+ break;
+
+ jpeg_io_write(jenc, offs->fe.pntr[pln], frame->plns[pln].dma);
+ jpeg_io_write(jenc, offs->fe.offs[pln], 0);
+
+ dev_dbg(jenc->dev, "%s: pln=%d addr=0x%llx idx:%d\n", __func__,
+ pln, frame->plns[pln].dma, vb->index);
+ }
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ q->buff_id = vb->index;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ return 0;
+}
+
+static int jpeg_store_fe_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
+{
+ struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb2->type)];
+ struct qcom_jpeg_buff *buff = &q->buff[vb2->index];
+ u8 pln = 0;
+
+ buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
+ if (!buff->plns[pln].sgt)
+ return -EINVAL;
+
+ if (!buff->plns[pln].sgt->sgl)
+ return -EINVAL;
+
+ buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
+ if (!buff->plns[pln].dma)
+ return -EINVAL;
+
+ buff->plns[pln].size = vb2_plane_size(vb2, pln);
+ if (!buff->plns[pln].size)
+ return -EINVAL;
+
+ for (pln = 1; pln < q->vf.num_planes; pln++) {
+ buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
+ if (!buff->plns[pln].sgt || !buff->plns[pln].sgt->sgl)
+ return -EINVAL;
+
+ buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
+ if (!buff->plns[pln].dma)
+ return -EINVAL;
+
+ buff->plns[pln].size = vb2_plane_size(vb2, pln);
+ if (!buff->plns[pln].size)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int jpeg_setup_fe_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ struct v4l2_pix_format_mplane *sfmt = &q->vf;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_WIDTH, 0);
+ jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_HEIGHT, 0);
+ jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_STRIDE, 0);
+ }
+
+ for (pln = 0; pln < sfmt->num_planes; pln++) {
+ jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_WIDTH,
+ sfmt->width - 1);
+ jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_HEIGHT,
+ sfmt->height - 1);
+ jpeg_rw_bits(jenc, offs->fe.stride[pln], JMSK_PLNS_RD_STRIDE,
+ sfmt->plane_fmt[pln].bytesperline);
+
+ dev_dbg(ectx->dev, "%s: ctx=%p pln=%d width=%d height=%d stride=%d\n",
+ __func__, ectx, pln,
+ jpeg_rd_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_WIDTH),
+ jpeg_rd_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_HEIGHT),
+ jpeg_rd_bits(jenc, offs->fe.stride[pln], JMSK_PLNS_RD_STRIDE));
+ }
+
+ return 0;
+}
+
+static int jpeg_setup_fe_hinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+ jpeg_io_write(jenc, offs->fe.hinit[pln], 0);
+
+ return 0;
+}
+
+static int jpeg_setup_fe_vinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+ jpeg_io_write(jenc, offs->fe.vinit[pln], 0);
+
+ return 0;
+}
+
+static int jpeg_setup_fe_params(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ struct v4l2_pix_format_mplane *sfmt = &q->vf;
+ struct v4l2_pix_format_mplane *dfmt = &ectx->bufq[JENC_DST_QUEUE].vf;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u8 expected_planes, pln;
+ int rval;
+
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_EN, 1);
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_BOTTOM_VPAD_EN, 1);
+
+ rval = jpeg_get_memory_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: invalid memory format for v4l2 format:0x%x\n",
+ __func__, sfmt->pixelformat);
+ return -EINVAL;
+ }
+
+ switch (rval) {
+ case JPEG_MEM_FMT_MONO:
+ expected_planes = 1;
+ break;
+ case JPEG_MEM_FMT_PPLANAR:
+ expected_planes = 2;
+ break;
+ case JPEG_MEM_FMT_PLANAR:
+ expected_planes = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (sfmt->num_planes != expected_planes) {
+ dev_err(ectx->dev, "plane mismatch fmt=%u expected=%u got=%u\n",
+ rval, expected_planes, sfmt->num_planes);
+ return -EINVAL;
+ }
+
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MEMORY_FORMAT, rval);
+
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN0_EN, 0);
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN1_EN, 0);
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN2_EN, 0);
+
+ if (sfmt->width == dfmt->width && sfmt->height == dfmt->height) {
+ /* No scaling */
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_SIXTEEN_MCU_EN, 1);
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MCUS_PER_BLOCK, 0);
+ } else {
+ u8 mcu_per_blks;
+
+ /* Scaling */
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_SIXTEEN_MCU_EN, 0);
+ /* get value according to image width */
+ mcu_per_blks = jpeg_get_mcu_per_block(sfmt->width, dfmt->width);
+ /* get value according to image height assign the bigger */
+ mcu_per_blks = max_t(u8, mcu_per_blks,
+ jpeg_get_mcu_per_block(sfmt->height, dfmt->height));
+
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MCUS_PER_BLOCK, mcu_per_blks);
+ }
+
+ dev_dbg(ectx->dev, "%s: sixteen MCU enabled=%d, %d MCU per blocks\n", __func__,
+ jpeg_rd_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_SIXTEEN_MCU_EN),
+ jpeg_rd_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MCUS_PER_BLOCK));
+
+ rval = jpeg_get_mal_boundary(sfmt->width, jpeg_mal_bounds, ARRAY_SIZE(jpeg_mal_bounds));
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: failed to get FE mal boundary width=%u\n", __func__,
+ sfmt->width);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_BOUNDARY, rval);
+
+ dev_dbg(ectx->dev, "%s: optimal FE mal boundary=%d\n", __func__,
+ jpeg_rd_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_BOUNDARY));
+
+ rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+ __func__, sfmt->pixelformat);
+ return -EINVAL;
+ }
+
+ switch (rval) {
+ case JPEG_ENCODE_MONO:
+ case JPEG_ENCODE_H1V1:
+ case JPEG_ENCODE_H2V1:
+ jpeg_rw_bits(jenc, offs->fe.vbpad_cfg, JMSK_FE_VBPAD_CFG_BLOCK_ROW,
+ DIV_ROUND_UP(sfmt->height, JPEG_MCU_BLOCK_8));
+ break;
+ case JPEG_ENCODE_H1V2:
+ case JPEG_ENCODE_H2V2:
+ jpeg_rw_bits(jenc, offs->fe.vbpad_cfg, JMSK_FE_VBPAD_CFG_BLOCK_ROW,
+ DIV_ROUND_UP(sfmt->height, JPEG_MCU_BLOCK_16));
+ break;
+ default:
+ dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n", __func__, rval);
+ return -EINVAL;
+ }
+
+ dev_dbg(ectx->dev, "%s: FE vpad config=%d\n", __func__,
+ jpeg_rd_bits(jenc, offs->fe.vbpad_cfg, JMSK_FE_VBPAD_CFG_BLOCK_ROW));
+
+ if (sfmt->pixelformat == V4L2_PIX_FMT_NV21M || sfmt->pixelformat == V4L2_PIX_FMT_NV61M)
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_CBCR_ORDER, 1);
+ else
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_CBCR_ORDER, 0);
+
+ for (pln = 0; pln < sfmt->num_planes; pln++) {
+ if (sfmt->width && sfmt->height) {
+ switch (pln) {
+ case 0:
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN0_EN, 1);
+ break;
+ case 1:
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN1_EN, 1);
+ break;
+ case 2:
+ jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN2_EN, 1);
+ break;
+ }
+ }
+ }
+
+ jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_FE_ENABLE, 1);
+
+ return 0;
+}
+
+static int jpeg_setup_fe(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ int rc;
+
+ rc = jpeg_setup_fe_size(ectx, q);
+ if (rc)
+ return rc;
+
+ rc = jpeg_setup_fe_hinit(ectx, q);
+ if (rc)
+ return rc;
+
+ rc = jpeg_setup_fe_vinit(ectx, q);
+ if (rc)
+ return rc;
+
+ rc = jpeg_setup_fe_params(ectx, q);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static int jpeg_ensure_header_cache(struct jenc_context *ectx)
+{
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ int rc;
+
+ if (ectx->hdr_cache.size)
+ return 0;
+
+ rc = qcom_jenc_header_init(&ectx->hdr_cache, sq->vf.pixelformat);
+ if (rc) {
+ dev_err(ectx->dev, "JFIF header lazy init failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int jpeg_apply_we_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
+ struct vb2_buffer *vb)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct qcom_jpeg_buff *frame = &q->buff[vb->index];
+ void *mptr = vb2_plane_vaddr(vb, 0);
+ dma_addr_t dma = frame->plns[0].dma;
+ unsigned long flags;
+ int rc;
+ u8 pln = 0;
+
+ if (WARN_ON_ONCE(!dma))
+ return -EPERM;
+
+ if (WARN_ON_ONCE(!mptr))
+ return -EPERM;
+
+ rc = jpeg_ensure_header_cache(ectx);
+ if (rc)
+ return rc;
+
+ /*
+ * Under quality_mutex: force a DQT refresh if the header was just
+ * (re)created (quality_programmed == 0) or if quality changed since
+ * the last frame. Both the cache update and the HW DMI upload are
+ * done here so that hdr_cache and the hardware are always in sync
+ * before jpeg_exec() fires.
+ */
+ mutex_lock(&ectx->quality_mutex);
+ if (!ectx->hdr_cache.size || ectx->quality_programmed != ectx->quality_requested) {
+ jpeg_update_dqt_cache(ectx);
+ jpeg_upload_dmi_table(ectx);
+ }
+ mutex_unlock(&ectx->quality_mutex);
+
+ /*
+ * Invalidate stale CPU cache lines before writing the JPEG header
+ * with the CPU into the destination buffer.
+ */
+ jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, false);
+
+ dma += qcom_jenc_header_emit(&ectx->hdr_cache, mptr,
+ min_t(size_t, vb->planes[0].length, ectx->hdr_cache.size),
+ q->vf.width, q->vf.height);
+ qcom_jenc_dqts_emit(&ectx->hdr_cache, mptr);
+
+ /*
+ * Flush CPU writes to the header before handing the buffer to the
+ * hardware DMA engine.
+ */
+ jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, true);
+
+ jpeg_io_write(jenc, offs->we.pntr[pln], dma);
+
+ dev_dbg(jenc->dev, "%s: pln=%d addr=0x%llx idx:%d\n", __func__,
+ pln, dma, vb->index);
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ q->buff_id = vb->index;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ return 0;
+}
+
+static int jpeg_store_we_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
+{
+ struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb2->type)];
+ struct qcom_jpeg_buff *frame = &q->buff[vb2->index];
+ struct sg_table *sgt;
+ dma_addr_t dma;
+
+ sgt = vb2_dma_sg_plane_desc(vb2, 0);
+ if (!sgt || !sgt->sgl)
+ return -EINVAL;
+
+ dma = sg_dma_address(sgt->sgl);
+ if (!dma)
+ return -EINVAL;
+
+ if (!vb2_plane_vaddr(vb2, 0))
+ return -EINVAL;
+
+ frame->plns[0].sgt = sgt;
+ frame->plns[0].dma = dma;
+ frame->plns[0].size = vb2_plane_size(vb2, 0);
+
+ return 0;
+}
+
+static int jpeg_setup_we_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+ jpeg_rw_bits(jenc, offs->we.stride[pln], JMSK_PLNS_WR_STRIDE, 0);
+
+ jpeg_io_write(jenc, offs->we.bsize[0], dfmt->plane_fmt[0].sizeimage);
+
+ dev_dbg(ectx->dev, "%s: ctx=%p size=%u\n", __func__,
+ ectx, dfmt->plane_fmt[0].sizeimage);
+
+ return 0;
+}
+
+static int jpeg_setup_we_hinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ u8 pln;
+
+ if (!dfmt->width) {
+ dev_err(ectx->dev, "%s: invalid destination width=%d\n", __func__, dfmt->width);
+ return -EINVAL;
+ }
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_rw_bits(jenc, offs->we.hinit[pln], JMSK_PLNS_WR_HINIT, 0);
+ jpeg_rw_bits(jenc, offs->we.hstep[pln], JMSK_PLNS_WR_HSTEP, 0);
+ }
+
+ jpeg_rw_bits(jenc, offs->we.hstep[0], JMSK_PLNS_WR_HSTEP, dfmt->width);
+
+ dev_dbg(ectx->dev, "%s: ctx=%p hstep=%u\n", __func__, ectx,
+ jpeg_rd_bits(jenc, offs->we.hstep[0], JMSK_PLNS_WR_HSTEP));
+
+ return 0;
+}
+
+static int jpeg_setup_we_vinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ u8 pln;
+
+ if (!dfmt->height) {
+ dev_err(ectx->dev, "%s: invalid destination height=%d\n", __func__, dfmt->height);
+ return -EINVAL;
+ }
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_rw_bits(jenc, offs->we.vinit[pln], JMSK_PLNS_WR_VINIT, 0);
+ jpeg_rw_bits(jenc, offs->we.vstep[pln], JMSK_PLNS_WR_VSTEP, 0);
+ }
+
+ jpeg_rw_bits(jenc, offs->we.vstep[0], JMSK_PLNS_WR_VSTEP, dfmt->height);
+
+ dev_dbg(ectx->dev, "%s: ctx=%p vstep=%u\n", __func__, ectx,
+ jpeg_rd_bits(jenc, offs->we.vstep[0], JMSK_PLNS_WR_VSTEP));
+
+ return 0;
+}
+
+static int jpeg_setup_we_params(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ u32 blk_w, blk_h, mcu_cols, mcu_rows;
+ int rval;
+
+ rval = jpeg_get_memory_fmt(dfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: invalid memory format for v4l2 format:0x%x\n",
+ __func__, dfmt->pixelformat);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MEMORY_FORMAT, rval);
+
+ rval = jpeg_get_mal_boundary(dfmt->width, jpeg_mal_bounds, ARRAY_SIZE(jpeg_mal_bounds));
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: failed to get WE mal boundary width=%u\n",
+ __func__, dfmt->width);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_BOUNDARY, rval);
+
+ dev_dbg(ectx->dev, "%s: optimal WE mal boundary=%d\n", __func__,
+ jpeg_rd_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_BOUNDARY));
+
+ rval = jpeg_get_encode_fmt(dfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+ __func__, dfmt->pixelformat);
+ return rval;
+ }
+
+ rval = jpeg_get_mcu_geometry(rval, dfmt->width, dfmt->height, &blk_w, &blk_h,
+ &mcu_cols, &mcu_rows);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+ __func__, mcu_cols, mcu_rows);
+ return rval;
+ }
+
+ dev_dbg(ectx->dev, "%s blk_w=%u blk_h=%u cols=%u rows=%u\n", __func__,
+ blk_w, blk_h, mcu_cols, mcu_rows);
+
+ jpeg_rw_bits(jenc, offs->we.blocks[0], JMSK_PLNS_WR_BLOCK_CFG_PER_RAW, mcu_rows - 1);
+ jpeg_rw_bits(jenc, offs->we.blocks[0], JMSK_PLNS_WR_BLOCK_CFG_PER_COL, mcu_cols - 1);
+
+ jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_CBCR_ORDER, 1);
+ jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_EN, 1);
+ jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_POP_BUFF_ON_EOS, 1);
+ jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_PLN0_EN, 1);
+
+ jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_MODE, 1);
+ jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_WE_ENABLE, 1);
+
+ return 0;
+}
+
+static int jpeg_setup_we(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ int rc;
+
+ rc = jpeg_setup_we_size(ectx, q);
+ if (rc)
+ return rc;
+
+ rc = jpeg_setup_we_hinit(ectx, q);
+ if (rc)
+ return rc;
+
+ rc = jpeg_setup_we_vinit(ectx, q);
+ if (rc)
+ return rc;
+
+ return jpeg_setup_we_params(ectx, q);
+}
+
+static int jpeg_setup_scale(struct jenc_context *ectx)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+ struct v4l2_pix_format_mplane *sfmt = &sq->vf;
+ struct v4l2_pix_format_mplane *dfmt = &dq->vf;
+ u32 blk_w, blk_h, mcu_cols, mcu_rows;
+ int rval;
+ u8 pln;
+
+ jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_SCALE_RESET, 1);
+
+ /* explicit no scaling */
+ jpeg_rw_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_HSCALE_ENABLE, 0);
+ jpeg_rw_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_VSCALE_ENABLE, 0);
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_io_write(jenc, offs->scale.hstep[pln], JPEG_DEFAULT_SCALE_STEP);
+ jpeg_io_write(jenc, offs->scale.vstep[pln], JPEG_DEFAULT_SCALE_STEP);
+ }
+
+ if (jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_HSCALE_ENABLE)) {
+ for (pln = 0; pln < sq->vf.num_planes; pln++) {
+ jpeg_rw_bits(jenc, offs->scale.hstep[pln],
+ JMSK_SCALE_PLNS_HSTEP_INTEGER,
+ jpeg_calc_scale_int(sfmt->width, dfmt->width));
+ jpeg_rw_bits(jenc, offs->scale.hstep[pln],
+ JMSK_SCALE_PLNS_HSTEP_FRACTIONAL,
+ jpeg_calc_scale_frac(sfmt->width, dfmt->width));
+
+ dev_dbg(ectx->dev, "%s: ctx=%p hint=%d hfrac=%d\n",
+ __func__, ectx,
+ jpeg_rd_bits(jenc, offs->scale.hstep[pln],
+ JMSK_SCALE_PLNS_HSTEP_INTEGER),
+ jpeg_rd_bits(jenc, offs->scale.hstep[pln],
+ JMSK_SCALE_PLNS_HSTEP_FRACTIONAL));
+ }
+ }
+
+ if (jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_VSCALE_ENABLE)) {
+ for (pln = 0; pln < sq->vf.num_planes; pln++) {
+ jpeg_rw_bits(jenc, offs->scale.vstep[pln],
+ JMSK_SCALE_PLNS_VSTEP_INTEGER,
+ jpeg_calc_scale_int(sfmt->height, dfmt->height));
+ jpeg_rw_bits(jenc, offs->scale.vstep[pln],
+ JMSK_SCALE_PLNS_VSTEP_FRACTIONAL,
+ jpeg_calc_scale_frac(sfmt->height, dfmt->height));
+
+ dev_dbg(ectx->dev, "%s: ctx=%p vint=%d vfrac=%d\n",
+ __func__, ectx,
+ jpeg_rd_bits(jenc, offs->scale.vstep[pln],
+ JMSK_SCALE_PLNS_VSTEP_INTEGER),
+ jpeg_rd_bits(jenc, offs->scale.vstep[pln],
+ JMSK_SCALE_PLNS_VSTEP_FRACTIONAL));
+ }
+ }
+
+ rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+ __func__, sfmt->pixelformat);
+ return -EINVAL;
+ }
+
+ rval = jpeg_get_mcu_geometry(rval, dfmt->width, dfmt->height, &blk_w, &blk_h,
+ &mcu_cols, &mcu_rows);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: invalid MCU geometry blk_w=%d blk_h=%d\n",
+ __func__, blk_w, blk_h);
+ return -EINVAL;
+ }
+
+ dev_dbg(ectx->dev, "%s blk_w=%u blk_h=%u cols=%u rows=%u\n", __func__, blk_w, blk_h,
+ mcu_cols, mcu_rows);
+
+ for (pln = 0; pln < sq->vf.num_planes; pln++) {
+ jpeg_rw_bits(jenc, offs->scale_out_cfg[pln],
+ JMSK_SCALE_PLNS_OUT_CFG_BLK_WIDTH, mcu_cols - 1);
+ jpeg_rw_bits(jenc, offs->scale_out_cfg[pln],
+ JMSK_SCALE_PLNS_OUT_CFG_BLK_HEIGHT, mcu_rows - 1);
+ }
+
+ dev_dbg(ectx->dev, "%s: ctx=%p scale src=%ux%u dst=%ux%u enable=%d/%d\n",
+ __func__, ectx, sfmt->width, sfmt->height, dfmt->width, dfmt->height,
+ jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_HSCALE_ENABLE),
+ jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_VSCALE_ENABLE));
+
+ /* Disabled, but must be configured */
+ jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_SCALE_ENABLE, 0);
+
+ return 0;
+}
+
+static int jpeg_setup_encode(struct jenc_context *ectx)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct v4l2_pix_format_mplane *sfmt = &sq->vf;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u32 blk_w, blk_h, mcu_cols, mcu_rows;
+ int rval;
+
+ if (!sfmt->width || !sfmt->height)
+ return -EINVAL;
+
+ jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_ENCODER_RESET, 1);
+
+ rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+ __func__, sfmt->pixelformat);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, offs->enc_cfg, JMSK_ENC_CFG_IMAGE_FORMAT, rval);
+
+ rval = jpeg_get_mcu_geometry(rval, sfmt->width, sfmt->height, &blk_w, &blk_h,
+ &mcu_cols, &mcu_rows);
+ if (rval < 0) {
+ dev_err(ectx->dev, "%s: invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+ __func__, mcu_cols, mcu_rows);
+ return -EINVAL;
+ }
+
+ dev_dbg(ectx->dev, "%s blk_w=%u blk_h=%u cols=%u rows=%u\n", __func__,
+ blk_w, blk_h, mcu_cols, mcu_rows);
+
+ jpeg_rw_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_WIDTH, mcu_cols - 1);
+ jpeg_rw_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_HEIGHT, mcu_rows - 1);
+
+ dev_dbg(ectx->dev, "%s: ctx=%p width=%d height=%d\n", __func__, ectx,
+ jpeg_rd_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_WIDTH),
+ jpeg_rd_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_HEIGHT));
+
+ jpeg_rw_bits(jenc, offs->enc_cfg, JMSK_ENC_CFG_APPLY_EOI, 1);
+ jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_ENC_ENABLE, 1);
+
+ return 0;
+}
+
+static irqreturn_t op_jpeg_irq_bot(int irq, void *data)
+{
+ struct qcom_jenc_dev *jenc = data;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u32 irq_status;
+ u32 irq_mask;
+ unsigned long flags;
+
+ irq_status = READ_ONCE(jenc->pending_irq_status);
+
+ irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_SESSION_DONE];
+ if (jpeg_bits_get(irq_mask, irq_status)) {
+ struct jenc_context *ctx = jenc->actx;
+ struct qcom_jenc_queue *dq;
+ size_t out_size;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ if (!ctx)
+ return IRQ_HANDLED;
+
+ dq = &ctx->bufq[JENC_DST_QUEUE];
+ if (dq->buff_id >= 0) {
+ struct qcom_jpeg_buff *frame;
+ unsigned long flags;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ frame = &dq->buff[dq->buff_id];
+ out_size = jpeg_io_read(jenc, offs->enc_out_size);
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ dev_dbg(jenc->dev, "complete idx:%d addr=0x%llx size=%zu\n",
+ dq->buff_id, frame->plns[0].dma, out_size);
+
+ jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_DONE,
+ out_size + JPEG_HEADER_MAX);
+ jpeg_stop(jenc);
+ }
+ }
+
+ irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_SESSION_ERROR];
+ if (jpeg_bits_get(irq_mask, irq_status)) {
+ struct jenc_context *ctx = jenc->actx;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ dev_err(jenc->dev, "encoder hardware failure=0x%x\n",
+ jpeg_bits_get(irq_mask, irq_status));
+ if (ctx)
+ jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_ERROR, 0);
+
+ jpeg_stop(jenc);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t op_jpeg_irq_top(int irq, void *data)
+{
+ struct qcom_jenc_dev *jenc = data;
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u32 irq_status;
+ u32 irq_mask;
+ unsigned long flags;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+
+ irq_status = jpeg_io_read(jenc, offs->int_status);
+ jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, irq_status);
+
+ irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_RESET_ACK];
+ if (jpeg_bits_get(irq_mask, irq_status)) {
+ complete(&jenc->reset_complete);
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_STOP_ACK];
+ if (jpeg_bits_get(irq_mask, irq_status)) {
+ complete(&jenc->stop_complete);
+ dev_dbg(jenc->dev, "hardware stop acknowledged\n");
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ WRITE_ONCE(jenc->pending_irq_status, irq_status);
+
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static void op_jpeg_get_hw_caps(struct qcom_jenc_dev *jenc, u32 *caps)
+{
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+ u32 hw_caps;
+
+ hw_caps = jpeg_io_read(jenc, offs->hw_capability);
+ dev_dbg(jenc->dev, "CAPS: encode=%d decode=%d upscale=%d downscale=%d\n",
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_ENCODE], hw_caps),
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_DECODE], hw_caps),
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_UPSCALE], hw_caps),
+ jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_DOWNSCALE], hw_caps));
+
+ *caps = hw_caps;
+}
+
+static struct qcom_jenc_queue *op_jpeg_get_buff_queue(struct jenc_context *ectx,
+ enum qcom_enc_qid id)
+{
+ return &ectx->bufq[id];
+}
+
+static int op_jpeg_queue_setup(struct jenc_context *ectx, enum qcom_enc_qid id)
+{
+ int rc;
+
+ if (id == JENC_SRC_QUEUE) {
+ struct qcom_jenc_queue *q = &ectx->bufq[id];
+
+ rc = qcom_jenc_header_init(&ectx->hdr_cache, q->vf.pixelformat);
+ if (rc) {
+ dev_err(ectx->dev, "JFIF header init failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int op_jpeg_src_fmt_update(struct jenc_context *ectx, u32 old_fourcc, u32 new_fourcc)
+{
+ bool old_is_mono = (old_fourcc == V4L2_PIX_FMT_GREY);
+ bool new_is_mono = (new_fourcc == V4L2_PIX_FMT_GREY);
+ int rc;
+
+ /* Header layout changes only for mono <-> color source format switch. */
+ if (old_is_mono == new_is_mono)
+ return 0;
+
+ rc = qcom_jenc_header_init(&ectx->hdr_cache, new_fourcc);
+ if (rc) {
+ dev_err(ectx->dev, "JFIF header reinit failed\n");
+ return rc;
+ }
+
+ /* Force DQT upload after source profile switch. */
+ ectx->quality_programmed = 0;
+
+ return 0;
+}
+
+static int op_jpeg_buffer_prepare(struct jenc_context *ectx, struct vb2_buffer *vb2)
+{
+ int rc;
+
+ if (V4L2_TYPE_IS_OUTPUT(vb2->type)) {
+ rc = jpeg_store_fe_next(ectx, vb2);
+ if (rc)
+ dev_err(ectx->dev, "%s: cannot set up fetch addr\n", __func__);
+ } else {
+ rc = jpeg_store_we_next(ectx, vb2);
+ if (rc)
+ dev_err(ectx->dev, "%s: cannot set up write addr\n", __func__);
+ }
+
+ return rc;
+}
+
+static int op_jpeg_process_exec(struct qcom_jenc_dev *jenc, struct jenc_context *ectx,
+ struct vb2_buffer *vb)
+{
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ jenc->actx = ectx;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ if (V4L2_TYPE_IS_OUTPUT(vb->type)) {
+ rc = jpeg_setup_fe(ectx, sq);
+ if (rc)
+ goto err_clear_ctx;
+
+ rc = jpeg_apply_fe_addr(ectx, sq, vb);
+ if (rc)
+ goto err_clear_ctx;
+ } else {
+ rc = jpeg_setup_we(ectx, dq);
+ if (rc)
+ goto err_clear_ctx;
+
+ rc = jpeg_apply_we_addr(ectx, dq, vb);
+ if (rc)
+ goto err_clear_ctx;
+ }
+
+ if (sq->sequence == dq->sequence) {
+ rc = jpeg_setup_scale(ectx);
+ if (rc)
+ goto err_clear_ctx;
+
+ rc = jpeg_setup_encode(ectx);
+ if (rc)
+ goto err_clear_ctx;
+
+ jpeg_exec(jenc);
+ }
+
+ return 0;
+
+err_clear_ctx:
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ if (jenc->actx == ectx)
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ return rc;
+}
+
+static int op_jpeg_prepare(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+
+ jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_DECODER_RESET, 1);
+ jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_BLOCK_FORMATTER_RST, 1);
+ jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_CORE_RESET, 1);
+
+ return 0;
+}
+
+static enum qcom_soc_perf_level jpeg_select_perf_level(struct jenc_context *ectx)
+{
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ u64 pixels, load;
+ u32 quality;
+ u32 qscale;
+
+ if (!sq->vf.width || !sq->vf.height)
+ return QCOM_SOC_PERF_NOMINAL;
+
+ mutex_lock(&ectx->quality_mutex);
+ quality = ectx->quality_requested;
+ mutex_unlock(&ectx->quality_mutex);
+
+ /* Minimal v1 quality weighting: low/medium/high quality buckets. */
+ if (quality <= 70)
+ qscale = 90;
+ else if (quality <= 90)
+ qscale = 100;
+ else
+ qscale = 115;
+
+ pixels = (u64)sq->vf.width * sq->vf.height;
+ load = pixels * qscale;
+
+ /* Keep a conservative floor for active encode sessions. */
+ if (load <= (u64)1920 * 1088 * 100)
+ return QCOM_SOC_PERF_SVS_L1;
+
+ if (load <= (u64)3840 * 2160 * 100)
+ return QCOM_SOC_PERF_NOMINAL;
+
+ return QCOM_SOC_PERF_TURBO;
+}
+
+static int op_jpeg_acquire(struct jenc_context *ectx, struct vb2_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+ int rc;
+
+ /* Reset per-context stream state for each (re)acquire. */
+ sq->sequence = 0;
+ sq->buff_id = -1;
+ dq->sequence = 0;
+ dq->buff_id = -1;
+ /*
+ * Recreate JPEG header lazily per destination buffer to tolerate
+ * different valid V4L2 call orders (e.g. STREAMON before first QBUF).
+ */
+ ectx->hdr_cache.size = 0;
+ /* Force DQT upload on first frame after (re)acquire. */
+ ectx->quality_programmed = 0;
+
+ if (atomic_inc_return(&jenc->ref_count) == 1) {
+ jenc->perf = jpeg_select_perf_level(ectx);
+ dev_dbg(jenc->dev, "%s: perf=%u src=%ux%u\n", __func__, jenc->perf,
+ sq->vf.width, sq->vf.height);
+
+ rc = pm_runtime_resume_and_get(jenc->dev);
+ if (rc < 0) {
+ dev_err(jenc->dev, "PM runtime get failed\n");
+ atomic_dec(&jenc->ref_count);
+ return rc;
+ }
+
+ rc = jpeg_init(jenc);
+ if (rc) {
+ dev_err(jenc->dev, "hardware init failed\n");
+ atomic_dec(&jenc->ref_count);
+ pm_runtime_put(jenc->dev);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int op_jpeg_release(struct jenc_context *ectx, struct vb2_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ int rc = 0;
+ int pm_rc;
+ int ref;
+
+ ref = atomic_dec_if_positive(&jenc->ref_count);
+ if (ref < 0) {
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ if (!ref) {
+ rc = jpeg_deinit(jenc);
+ if (rc)
+ dev_err(jenc->dev, "hardware exit failed\n");
+
+ pm_rc = pm_runtime_put_sync(jenc->dev);
+ if (pm_rc < 0) {
+ dev_err(jenc->dev, "PM runtime put failed\n");
+ if (!rc)
+ rc = pm_rc;
+ }
+
+ dev_dbg(jenc->dev, "JPEG HW encoder released\n");
+ }
+
+ return rc;
+}
+
+const struct qcom_jpeg_hw_ops qcom_jpeg_default_ops = {
+ .hw_get_cap = op_jpeg_get_hw_caps,
+ .hw_acquire = op_jpeg_acquire,
+ .hw_release = op_jpeg_release,
+ .hw_prepare = op_jpeg_prepare,
+ .get_queue = op_jpeg_get_buff_queue,
+ .queue_setup = op_jpeg_queue_setup,
+ .src_fmt_update = op_jpeg_src_fmt_update,
+ .buf_prepare = op_jpeg_buffer_prepare,
+ .process_exec = op_jpeg_process_exec,
+ .hw_irq_top = op_jpeg_irq_top,
+ .hw_irq_bot = op_jpeg_irq_bot
+};
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
new file mode 100644
index 000000000000..857af4a24794
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_OPS_H
+#define QCOM_JENC_OPS_H
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <media/videobuf2-core.h>
+
+#include "qcom_jenc_dev.h"
+
+/*
+ * JENC encoder hardware operations.
+ */
+struct qcom_jpeg_hw_ops {
+ void (*hw_get_cap)
+ (struct qcom_jenc_dev *jenc_dev, u32 *hw_caps);
+
+ int (*hw_acquire)
+ (struct jenc_context *ectx, struct vb2_queue *queue);
+
+ int (*hw_release)
+ (struct jenc_context *ectx, struct vb2_queue *queue);
+
+ int (*hw_prepare)
+ (struct qcom_jenc_dev *jenc);
+
+ struct qcom_jenc_queue * (*get_queue)
+ (struct jenc_context *ectx, enum qcom_enc_qid id);
+
+ int (*queue_setup)
+ (struct jenc_context *ectx, enum qcom_enc_qid id);
+
+ int (*src_fmt_update)
+ (struct jenc_context *ectx, u32 old_fourcc, u32 new_fourcc);
+
+ int (*buf_prepare)
+ (struct jenc_context *ectx, struct vb2_buffer *vb2);
+
+ int (*process_exec)
+ (struct qcom_jenc_dev *jenc, struct jenc_context *ectx, struct vb2_buffer *vb2);
+
+ irqreturn_t (*hw_irq_top)(int irq_num, void *data);
+ irqreturn_t (*hw_irq_bot)(int irq_num, void *data);
+};
+
+extern const struct qcom_jpeg_hw_ops qcom_jpeg_default_ops;
+
+#endif /* QCOM_JENC_OPS_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
new file mode 100644
index 000000000000..bfe60187caa4
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_res.h"
+
+#include "qcom_jenc_v420_hw_info.h"
+
+#define QCOM_PERF_ROW(_axi_rate, _jpeg_rate) \
+{ \
+ .clk_rate = { \
+ [JPEG_CAMNOC_AXI_CLK] = (_axi_rate), \
+ [JPEG_CORE_CLK] = (_jpeg_rate), \
+ }, \
+}
+
+/*
+ * Baseline AXI clock rates shared across t165/t480, reused by later
+ * derivatives where the hardware does not change these domains.
+ */
+static const u64 cnoc_axi_clk_t165_t480[] = {
+ [QCOM_SOC_PERF_SUSPEND] = 19200000,
+ [QCOM_SOC_PERF_LOWSVS] = 300000000,
+ [QCOM_SOC_PERF_SVS] = 300000000,
+ [QCOM_SOC_PERF_SVS_L1] = 300000000,
+ [QCOM_SOC_PERF_NOMINAL] = 400000000,
+ [QCOM_SOC_PERF_TURBO] = 400000000,
+};
+
+/*
+ * Derivative with an improved CAMNOC AXI frequency range
+ */
+static const u64 cnoc_axi_clk_t680[] = {
+ [QCOM_SOC_PERF_SUSPEND] = 19200000,
+ [QCOM_SOC_PERF_LOWSVS] = 150000000,
+ [QCOM_SOC_PERF_SVS] = 240000000,
+ [QCOM_SOC_PERF_SVS_L1] = 320000000,
+ [QCOM_SOC_PERF_NOMINAL] = 400000000,
+ [QCOM_SOC_PERF_TURBO] = 480000000,
+};
+
+/*
+ * Baseline JPEG clock rates shared across t165/t480, reused by later
+ * derivatives where the hardware does not change these domains.
+ */
+static const u64 qcom_jpeg_clk_t165_t480[] = {
+ [QCOM_SOC_PERF_SUSPEND] = 19200000,
+ [QCOM_SOC_PERF_LOWSVS] = 300000000,
+ [QCOM_SOC_PERF_SVS] = 400000000,
+ [QCOM_SOC_PERF_SVS_L1] = 480000000,
+ [QCOM_SOC_PERF_NOMINAL] = 600000000,
+ [QCOM_SOC_PERF_TURBO] = 600000000,
+};
+
+/*
+ * Derivative with an improved maximum JPEG frequency
+ */
+static const u64 qcom_jpeg_clk_t780[] = {
+ [QCOM_SOC_PERF_SUSPEND] = 19200000,
+ [QCOM_SOC_PERF_LOWSVS] = 200000000,
+ [QCOM_SOC_PERF_SVS] = 200000000,
+ [QCOM_SOC_PERF_SVS_L1] = 400000000,
+ [QCOM_SOC_PERF_NOMINAL] = 480000000,
+ [QCOM_SOC_PERF_TURBO] = 785000000,
+};
+
+static const struct qcom_perf_resource qcom_perf_rates_t165_t480[] = {
+ [QCOM_SOC_PERF_SUSPEND] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SUSPEND],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SUSPEND]),
+
+ [QCOM_SOC_PERF_LOWSVS] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_LOWSVS],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_LOWSVS]),
+
+ [QCOM_SOC_PERF_SVS] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS]),
+
+ [QCOM_SOC_PERF_SVS_L1] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS_L1],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS_L1]),
+
+ [QCOM_SOC_PERF_NOMINAL] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_NOMINAL],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_NOMINAL]),
+
+ [QCOM_SOC_PERF_TURBO] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_TURBO],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_TURBO]),
+};
+
+static const struct qcom_perf_resource qcom_perf_rates_v680[] = {
+ [QCOM_SOC_PERF_SUSPEND] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_SUSPEND],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SUSPEND]),
+
+ [QCOM_SOC_PERF_LOWSVS] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_LOWSVS],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_LOWSVS]),
+
+ [QCOM_SOC_PERF_SVS] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_SVS],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS]),
+
+ [QCOM_SOC_PERF_SVS_L1] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_SVS_L1],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS_L1]),
+
+ [QCOM_SOC_PERF_NOMINAL] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_NOMINAL],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_NOMINAL]),
+
+ [QCOM_SOC_PERF_TURBO] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_TURBO],
+ qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_TURBO]),
+};
+
+static const struct qcom_perf_resource qcom_perf_rates_v780[] = {
+ [QCOM_SOC_PERF_SUSPEND] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SUSPEND],
+ qcom_jpeg_clk_t780[QCOM_SOC_PERF_SUSPEND]),
+
+ [QCOM_SOC_PERF_LOWSVS] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_LOWSVS],
+ qcom_jpeg_clk_t780[QCOM_SOC_PERF_LOWSVS]),
+
+ [QCOM_SOC_PERF_SVS] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS],
+ qcom_jpeg_clk_t780[QCOM_SOC_PERF_SVS]),
+
+ [QCOM_SOC_PERF_SVS_L1] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS_L1],
+ qcom_jpeg_clk_t780[QCOM_SOC_PERF_SVS_L1]),
+
+ [QCOM_SOC_PERF_NOMINAL] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_NOMINAL],
+ qcom_jpeg_clk_t780[QCOM_SOC_PERF_NOMINAL]),
+
+ [QCOM_SOC_PERF_TURBO] =
+ QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_TURBO],
+ qcom_jpeg_clk_t780[QCOM_SOC_PERF_TURBO]),
+};
+
+static const struct qcom_icc_resource qcom_jpeg_default_icc[] = {
+ {
+ .icc_id = "cam_ahb",
+ .pair = { 38400, 76800 }
+ },
+ {
+ .icc_id = "cam_hf_0_mnoc",
+ .pair = { 2097152, 2097152 }
+ },
+ {
+ .icc_id = "cam_sf_0_mnoc",
+ .pair = { 0, 2097152 }
+ },
+ {
+ .icc_id = "cam_sf_icp_mnoc",
+ .pair = { 2097152, 2097152 }
+ },
+};
+
+/*
+ * Resources for T165, T170, T480 JPEG version and derivatives
+ */
+const struct qcom_dev_resources qcom_t165_t480_jpeg_drvdata = {
+ .hw_ops = &qcom_jpeg_default_ops,
+ .hw_offs = &qcom_jpeg_v420_hw_reg_offs,
+ .hw_mask = &qcom_jpeg_v420_hw_reg_mask[0],
+ .icc_res = qcom_jpeg_default_icc,
+ .num_of_icc = ARRAY_SIZE(qcom_jpeg_default_icc),
+ .perf_cfg = qcom_perf_rates_t165_t480,
+ .clk_names = {
+ [JPEG_CAMNOC_AXI_CLK] = "camnoc_axi",
+ [JPEG_CORE_CLK] = "jpeg",
+ [JPEG_CORE_AHB_CLK] = "core_ahb",
+ [JPEG_CPAS_AHB_CLK] = "cpas_ahb",
+ [JPEG_GCC_HF_AXI] = "gcc_hf_axi",
+ [JPEG_GCC_SF_AXI] = "gcc_sf_axi",
+ }
+};
+
+/*
+ * Resources for T680 JPEG version and derivatives
+ */
+const struct qcom_dev_resources qcom_t680_jpeg_drvdata = {
+ .hw_ops = &qcom_jpeg_default_ops,
+ .hw_offs = &qcom_jpeg_v420_hw_reg_offs,
+ .hw_mask = &qcom_jpeg_v420_hw_reg_mask[0],
+ .icc_res = qcom_jpeg_default_icc,
+ .num_of_icc = ARRAY_SIZE(qcom_jpeg_default_icc),
+ .perf_cfg = qcom_perf_rates_v680,
+ .clk_names = {
+ [JPEG_CAMNOC_AXI_CLK] = "camnoc_axi",
+ [JPEG_CORE_CLK] = "jpeg",
+ [JPEG_CORE_AHB_CLK] = "core_ahb",
+ [JPEG_CPAS_AHB_CLK] = "cpas_ahb",
+ [JPEG_GCC_HF_AXI] = "gcc_hf_axi",
+ [JPEG_GCC_SF_AXI] = "gcc_sf_axi",
+ }
+};
+
+/*
+ * Resources for T780 JPEG version and derivatives
+ */
+const struct qcom_dev_resources qcom_t780_jpeg_drvdata = {
+ .hw_ops = &qcom_jpeg_default_ops,
+ .hw_offs = &qcom_jpeg_v420_hw_reg_offs,
+ .hw_mask = &qcom_jpeg_v420_hw_reg_mask[0],
+ .icc_res = qcom_jpeg_default_icc,
+ .num_of_icc = ARRAY_SIZE(qcom_jpeg_default_icc),
+ .perf_cfg = qcom_perf_rates_v780,
+ .clk_names = {
+ [JPEG_CAMNOC_AXI_CLK] = "camnoc_axi",
+ [JPEG_CORE_CLK] = "jpeg",
+ [JPEG_CORE_AHB_CLK] = "core_ahb",
+ [JPEG_CPAS_AHB_CLK] = "cpas_ahb",
+ [JPEG_GCC_HF_AXI] = "gcc_hf_axi",
+ [JPEG_GCC_SF_AXI] = "gcc_sf_axi",
+ }
+};
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
new file mode 100644
index 000000000000..cdff41dbb10c
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_RES_H
+#define QCOM_JENC_RES_H
+
+#include "qcom_jenc_defs.h"
+
+/*
+ * clk_rate == 0 means: do not change this clock rate.
+ * Clock is still enabled/disabled normally.
+ */
+enum qcom_jpeg_clock_ids {
+ JPEG_CAMNOC_AXI_CLK,
+ JPEG_CORE_CLK,
+ JPEG_CORE_AHB_CLK,
+ JPEG_CPAS_AHB_CLK,
+ JPEG_GCC_HF_AXI,
+ JPEG_GCC_SF_AXI,
+ JPEG_MAX_CLOCKS
+};
+
+struct qcom_icc_resource {
+ const char *icc_id;
+ struct {
+ u32 aggr;
+ u32 peak;
+ } pair;
+};
+
+struct qcom_perf_resource {
+ u64 clk_rate[JPEG_MAX_CLOCKS];
+};
+
+struct qcom_dev_resources {
+ const struct qcom_jpeg_hw_ops *hw_ops;
+ const struct qcom_jpeg_reg_offs *hw_offs;
+ const u32 *hw_mask;
+
+ const struct qcom_icc_resource *icc_res;
+ unsigned int num_of_icc;
+ const struct qcom_perf_resource *perf_cfg;
+ const char *clk_names[JPEG_MAX_CLOCKS];
+};
+
+extern const struct qcom_dev_resources qcom_t165_t480_jpeg_drvdata;
+
+extern const struct qcom_dev_resources qcom_t680_jpeg_drvdata;
+
+extern const struct qcom_dev_resources qcom_t780_jpeg_drvdata;
+
+#endif /* QCOM_JENC_RES_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
new file mode 100644
index 000000000000..3de747b0ec3d
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
@@ -0,0 +1,529 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_V420_HW_INFO_H
+#define QCOM_JENC_V420_HW_INFO_H
+
+#include "qcom_jenc_defs.h"
+
+#define JPEG_V420_HW_VER_STEP_POS 0
+#define JPEG_V420_HW_VER_STEP_MSK \
+ (0xffffu << JPEG_V420_HW_VER_STEP_POS)
+
+#define JPEG_V420_HW_VER_MINOR_POS 16
+#define JPEG_V420_HW_VER_MINOR_MSK \
+ (0x0fffu << JPEG_V420_HW_VER_MINOR_POS)
+
+#define JPEG_V420_HW_VER_MAJOR_POS 28
+#define JPEG_V420_HW_VER_MAJOR_MSK \
+ (0xfu << JPEG_V420_HW_VER_MAJOR_POS)
+
+#define JPEG_V420_HW_CAP_ENCODE_MSK BIT_U32(0)
+#define JPEG_V420_HW_CAP_DECODE_MSK BIT_U32(1)
+
+#define JPEG_V420_HW_CAP_UPSCALE_POS 4
+#define JPEG_V420_HW_CAP_UPSCALE_MSK \
+ (0x7u << JPEG_V420_HW_CAP_UPSCALE_POS)
+
+#define JPEG_V420_HW_CAP_DOWNSCALE_POS 8
+#define JPEG_V420_HW_CAP_DOWNSCALE_MSK \
+ (0x7u << JPEG_V420_HW_CAP_DOWNSCALE_POS)
+
+#define JPEG_V420_RST_CMD_FE_RESET_MSK BIT_U32(0)
+#define JPEG_V420_RST_CMD_WE_RESET_MSK BIT_U32(1)
+#define JPEG_V420_RST_CMD_ENCODER_RESET_MSK BIT_U32(4)
+#define JPEG_V420_RST_CMD_DECODER_RESET_MSK BIT_U32(5)
+#define JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST_MSK BIT_U32(6)
+#define JPEG_V420_RST_CMD_SCALE_RESET_MSK BIT_U32(7)
+#define JPEG_V420_RST_CMD_REGISTER_RESET_MSK BIT_U32(13)
+#define JPEG_V420_RST_CMD_MISR_RESET_MSK BIT_U32(16)
+#define JPEG_V420_RST_CMD_CORE_RESET_MSK BIT_U32(17)
+#define JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET_MSK BIT_U32(29)
+#define JPEG_V420_RST_CMD_RESET_BYPASS_MSK BIT_U32(31)
+
+#define JPEG_V420_CORE_CFG_FE_ENABLE_MSK BIT_U32(0)
+#define JPEG_V420_CORE_CFG_WE_ENABLE_MSK BIT_U32(1)
+#define JPEG_V420_CORE_CFG_ENC_ENABLE_MSK BIT_U32(4)
+#define JPEG_V420_CORE_CFG_SCALE_ENABLE_MSK BIT_U32(7)
+#define JPEG_V420_CORE_CFG_TESTBUS_ENABLE_MSK BIT_U32(19)
+#define JPEG_V420_CORE_CFG_MODE_MSK BIT_U32(24)
+#define JPEG_V420_CORE_CFG_CGC_DISABLE_MSK BIT_U32(31)
+
+#define JPEG_V420_CMD_HW_START_MSK BIT_U32(0)
+#define JPEG_V420_CMD_HW_STOP_MSK BIT_U32(1)
+#define JPEG_V420_CMD_CLR_RD_PLN0_QUEUE_MSK BIT_U32(4)
+#define JPEG_V420_CMD_CLR_RD_PLN1_QUEUE_MSK BIT_U32(5)
+#define JPEG_V420_CMD_CLR_RD_PLN2_QUEUE_MSK BIT_U32(6)
+#define JPEG_V420_CMD_CLR_WR_PLN0_QUEUE_MSK BIT_U32(8)
+#define JPEG_V420_CMD_CLR_WR_PLN1_QUEUE_MSK BIT_U32(9)
+#define JPEG_V420_CMD_CLR_WR_PLN2_QUEUE_MSK BIT_U32(10)
+#define JPEG_V420_CMD_APPLY_SWC_RD_PARAMS_MSK BIT_U32(11)
+
+#define JPEG_V420_CORE_STATE_STATUS_ENCODE_STATE_MSK BIT_U32(0)
+#define JPEG_V420_CORE_STATE_STATUS_SCALE_STATE_MSK BIT_U32(2)
+#define JPEG_V420_CORE_STATE_STATUS_REALTIME_STATE_MSK BIT_U32(4)
+#define JPEG_V420_CORE_STATE_STATUS_BUS_STATE_MSK BIT_U32(8)
+#define JPEG_V420_CORE_STATE_STATUS_CGC_STATE_MSK BIT_U32(9)
+
+#define JPEG_V420_FE_CFG_BYTE_ORDERING_POS 0
+#define JPEG_V420_FE_CFG_BYTE_ORDERING_MSK \
+ (0xfu << JPEG_V420_FE_CFG_BYTE_ORDERING_POS)
+
+#define JPEG_V420_FE_CFG_BURST_LENGTH_MAX_POS 4
+#define JPEG_V420_FE_CFG_BURST_LENGTH_MAX_MSK \
+ (0xfu << JPEG_V420_FE_CFG_BURST_LENGTH_MAX_POS)
+
+#define JPEG_V420_FE_CFG_MEMORY_FORMAT_POS 8
+#define JPEG_V420_FE_CFG_MEMORY_FORMAT_MSK \
+ (0x3u << JPEG_V420_FE_CFG_MEMORY_FORMAT_POS)
+
+#define JPEG_V420_FE_CFG_CBCR_ORDER_MSK BIT_U32(12)
+#define JPEG_V420_FE_CFG_BOTTOM_VPAD_EN_MSK BIT_U32(13)
+#define JPEG_V420_FE_CFG_PLN0_EN_MSK BIT_U32(16)
+#define JPEG_V420_FE_CFG_PLN1_EN_MSK BIT_U32(17)
+#define JPEG_V420_FE_CFG_PLN2_EN_MSK BIT_U32(18)
+#define JPEG_V420_FE_CFG_SIXTEEN_MCU_EN_MSK BIT_U32(21)
+#define JPEG_V420_FE_CFG_MCUS_PER_BLOCK_MSK BIT_U32(22)
+#define JPEG_V420_FE_CFG_MAL_BOUNDARY_MSK BIT_U32(24)
+#define JPEG_V420_FE_CFG_MAL_EN_MSK BIT_U32(27)
+
+#define JPEG_V420_PLN_RD_OFFS_OFFSET_POS 0
+#define JPEG_V420_PLN_RD_OFFS_OFFSET_MSK \
+ (0x1fffffffu << JPEG_V420_PLN_RD_OFFS_OFFSET_POS)
+
+#define JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_POS 0
+#define JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK \
+ (0xffffu << JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_POS)
+
+#define JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_POS 16
+#define JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK \
+ (0xffffu << JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_POS)
+
+#define JPEG_V420_PLN_RD_STRIDE_STRIDE_POS 0
+#define JPEG_V420_PLN_RD_STRIDE_STRIDE_MSK \
+ (0xffffu << JPEG_V420_PLN_RD_STRIDE_STRIDE_POS)
+
+#define JPEG_V420_PLN_RD_HINIT_FRACTIONAL_POS 0
+#define JPEG_V420_PLN_RD_HINIT_FRACTIONAL_MSK \
+ (0x1fffffu << JPEG_V420_PLN_RD_HINIT_FRACTIONAL_POS)
+
+#define JPEG_V420_PLN_RD_VINIT_FRACTIONAL_POS 0
+#define JPEG_V420_PLN_RD_VINIT_FRACTIONAL_MSK \
+ (0x1fffffu << JPEG_V420_PLN_RD_VINIT_FRACTIONAL_POS)
+
+#define JPEG_V420_WE_CFG_BYTE_ORDERING_POS 0
+#define JPEG_V420_WE_CFG_BYTE_ORDERING_MSK \
+ (0xfu << JPEG_V420_WE_CFG_BYTE_ORDERING_POS)
+
+#define JPEG_V420_WE_CFG_BURST_LENGTH_MAX_POS 4
+#define JPEG_V420_WE_CFG_BURST_LENGTH_MAX_MSK \
+ (0xfu << JPEG_V420_WE_CFG_BURST_LENGTH_MAX_POS)
+
+#define JPEG_V420_WE_CFG_MEMORY_FORMAT_POS 8
+#define JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK \
+ (0x3u << JPEG_V420_WE_CFG_MEMORY_FORMAT_POS)
+
+#define JPEG_V420_WE_CFG_CBCR_ORDER_MSK BIT_U32(12)
+#define JPEG_V420_WE_CFG_PLN0_EN_MSK BIT_U32(16)
+#define JPEG_V420_WE_CFG_PLN1_EN_MSK BIT_U32(17)
+#define JPEG_V420_WE_CFG_PLN2_EN_MSK BIT_U32(18)
+#define JPEG_V420_WE_CFG_MAL_BOUNDARY_MSK BIT_U32(24)
+#define JPEG_V420_WE_CFG_MAL_EN_MSK BIT_U32(27)
+#define JPEG_V420_WE_CFG_POP_BUFF_ON_EOS_MSK BIT_U32(28)
+
+#define JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_POS 0
+#define JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_POS)
+
+#define JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_POS 16
+#define JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_POS)
+
+#define JPEG_V420_PLN_WR_STRIDE_STRIDE_POS 0
+#define JPEG_V420_PLN_WR_STRIDE_STRIDE_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_STRIDE_STRIDE_POS)
+
+#define JPEG_V420_PLN_WR_HINIT_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_HINIT_INTEGER_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_HINIT_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_VINIT_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_VINIT_INTEGER_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_VINIT_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_HSTEP_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK \
+ (0x1ffffu << JPEG_V420_PLN_WR_HSTEP_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_VSTEP_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK \
+ (0x1ffffu << JPEG_V420_PLN_WR_VSTEP_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_POS 0
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_POS)
+
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_POS 16
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_MSK \
+ (0xffffu << JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_POS)
+
+#define JPEG_V420_ENC_CFG_IMAGE_FORMAT_POS 0
+#define JPEG_V420_ENC_CFG_IMAGE_FORMAT_MSK \
+ (0x7u << JPEG_V420_ENC_CFG_IMAGE_FORMAT_POS)
+
+#define JPEG_V420_ENC_CFG_APPLY_EOI_MSK BIT_U32(7)
+#define JPEG_V420_ENC_CFG_HUFFMAN_SEL_MSK BIT_U32(8)
+#define JPEG_V420_ENC_CFG_FSC_ENABLE_MSK BIT_U32(11)
+#define JPEG_V420_ENC_CFG_OUTPUT_DISABLE_MSK BIT_U32(15)
+#define JPEG_V420_ENC_CFG_RST_MARKER_PERIOD_MSK BIT_U32(16)
+
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_POS 0u
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK \
+ (0x1fffu << JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_POS)
+
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_POS 16u
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK \
+ (0x1fffu << JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_POS)
+
+#define JPEG_V420_OUTPUT_SIZE_STATUS_OUT_SIZE_BYTES_POS 0
+#define JPEG_V420_OUTPUT_SIZE_STATUS_OUT_SIZE_BYTES_MSK \
+ (0x1fffffff << JPEG_V420_OUTPUT_SIZE_STATUS_OUT_SIZE_BYTES_POS)
+
+#define JPEG_V420_SCALE_CFG_HSCALE_ENABLE_MSK BIT_U32(4)
+#define JPEG_V420_SCALE_CFG_VSCALE_ENABLE_MSK BIT_U32(5)
+#define JPEG_V420_SCALE_CFG_UPSAMPLE_EN_MSK BIT_U32(6)
+#define JPEG_V420_SCALE_CFG_SUBSAMPLE_EN_MSK BIT_U32(7)
+#define JPEG_V420_SCALE_CFG_HSCALE_ALGO_MSK BIT_U32(8)
+#define JPEG_V420_SCALE_CFG_VSCALE_ALGO_MSK BIT_U32(9)
+
+#define JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_POS 12u
+#define JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_MSK \
+ (0x3u << JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_POS)
+
+#define JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_POS 16u
+#define JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_MSK \
+ (0x3u << JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_POS)
+
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_POS 0
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_MSK \
+ (0xffu << JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_POS)
+
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_POS 16
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_MSK \
+ (0xfu << JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_POS)
+
+#define JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_POS 0
+#define JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_MSK \
+ (0x1fffffu << JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_POS)
+
+#define JPEG_V420_SCALE_PLN_HSTEP_INTEGER_POS 21
+#define JPEG_V420_SCALE_PLN_HSTEP_INTEGER_MSK \
+ (0x3fu << JPEG_V420_SCALE_PLN_HSTEP_INTEGER_POS)
+
+#define JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_POS 0
+#define JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_MSK \
+ (0x1fffffu << JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_POS)
+
+#define JPEG_V420_SCALE_PLN_VSTEP_INTEGER_POS 21
+#define JPEG_V420_SCALE_PLN_VSTEP_INTEGER_MSK \
+ (0x3fu << JPEG_V420_SCALE_PLN_VSTEP_INTEGER_POS)
+
+#define JPEG_V420_DMI_CFG_MEM_SEL_POS 0
+#define JPEG_V420_DMI_CFG_MEM_SEL_MSK \
+ (0x7u << JPEG_V420_DMI_CFG_MEM_SEL_POS)
+
+#define JPEG_V420_DMI_CFG_AUTO_INC_EN_MSK BIT_U32(4)
+
+#define JPEG_V420_DMI_ADDR_ADDR_POS 0
+#define JPEG_V420_DMI_ADDR_ADDR_MSK \
+ (0x3ffu << JPEG_V420_DMI_ADDR_ADDR_POS)
+
+#define JPEG_V420_TESTBUS_CFG_BUS_SEL_POS 0
+#define JPEG_V420_TESTBUS_CFG_BUS_SEL_MSK \
+ (0x3fu << JPEG_V420_TESTBUS_CFG_BUS_SEL_POS)
+
+#define JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_POS 0
+#define JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK \
+ (0x1fffu << JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_POS)
+
+#define JPEG_V420_PLN_RD_HINIT_INT_INTEGER_POS 0
+#define JPEG_V420_PLN_RD_HINIT_INT_INTEGER_MSK \
+ (0x1ffffu << JPEG_V420_PLN_RD_HINIT_INT_INTEGER_POS)
+
+#define JPEG_V420_PLN_RD_VINIT_INT_INTEGER_POS 0
+#define JPEG_V420_PLN_RD_VINIT_INT_INTEGER_MSK \
+ (0x1ffffu << JPEG_V420_PLN_RD_VINIT_INT_INTEGER_POS)
+
+#define JPEG_V420_IRQ_STATUS_SESSION_DONE_MSK BIT_U32(0)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_DONE_MSK BIT_U32(4)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_DONE_MSK BIT_U32(5)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_DONE_MSK BIT_U32(6)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_REQ_ATTN_MSK BIT_U32(7)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_REQ_ATTN_MSK BIT_U32(8)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_REQ_ATTN_MSK BIT_U32(9)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK BIT_U32(10)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_DONE_MSK BIT_U32(11)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_DONE_MSK BIT_U32(12)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_REQ_ATTN_MSK BIT_U32(13)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_REQ_ATTN_MSK BIT_U32(14)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_REQ_ATTN_MSK BIT_U32(15)
+#define JPEG_V420_IRQ_STATUS_DCD_UNESCAPED_FF_MSK BIT_U32(19)
+#define JPEG_V420_IRQ_STATUS_DCD_HUFFMAN_ERROR_MSK BIT_U32(20)
+#define JPEG_V420_IRQ_STATUS_DCD_COEFF_ERROR_MSK BIT_U32(21)
+#define JPEG_V420_IRQ_STATUS_DCD_MISSING_BITSTUFF_MSK BIT_U32(22)
+#define JPEG_V420_IRQ_STATUS_DCD_SCAN_UNDERFLOW_MSK BIT_U32(23)
+#define JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_MSK BIT_U32(24)
+#define JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_SEQ_MSK BIT_U32(25)
+#define JPEG_V420_IRQ_STATUS_DCD_MISSING_RSM_MSK BIT_U32(26)
+#define JPEG_V420_IRQ_STATUS_STOP_ACK_MSK BIT_U32(27)
+#define JPEG_V420_IRQ_STATUS_RESET_ACK_MSK BIT_U32(28)
+
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_DONE_MSK \
+ (JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_DONE_MSK)
+
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_DONE_MSK \
+ (JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_DONE_MSK)
+
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK \
+ (JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_REQ_ATTN_MSK | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_REQ_ATTN_MSK | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_REQ_ATTN_MSK)
+
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK \
+ (JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_REQ_ATTN_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_REQ_ATTN_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_REQ_ATTN_MSK)
+
+#define JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK \
+ (JPEG_V420_IRQ_STATUS_DCD_UNESCAPED_FF_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_HUFFMAN_ERROR_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_COEFF_ERROR_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_MISSING_BITSTUFF_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_SCAN_UNDERFLOW_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_SEQ_MSK | \
+ JPEG_V420_IRQ_STATUS_DCD_MISSING_RSM_MSK)
+
+#define JPEG_V420_IRQ_STATUS_ALL_BITS \
+ (JPEG_V420_IRQ_STATUS_SESSION_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK | \
+ JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK | \
+ JPEG_V420_IRQ_STATUS_STOP_ACK_MSK | \
+ JPEG_V420_IRQ_STATUS_RESET_ACK_MSK)
+
+const u32 qcom_jpeg_v420_hw_reg_mask[] = {
+ [JMSK_HW_VER_STEP] = JPEG_V420_HW_VER_STEP_MSK,
+ [JMSK_HW_VER_MINOR] = JPEG_V420_HW_VER_MINOR_MSK,
+ [JMSK_HW_VER_MAJOR] = JPEG_V420_HW_VER_MAJOR_MSK,
+
+ [JMSK_HW_CAP_ENCODE] = JPEG_V420_HW_CAP_ENCODE_MSK,
+ [JMSK_HW_CAP_DECODE] = JPEG_V420_HW_CAP_DECODE_MSK,
+ [JMSK_HW_CAP_UPSCALE] = JPEG_V420_HW_CAP_UPSCALE_MSK,
+ [JMSK_HW_CAP_DOWNSCALE] = JPEG_V420_HW_CAP_DOWNSCALE_MSK,
+
+ [JMSK_RST_CMD_COMMON] =
+ (JPEG_V420_RST_CMD_FE_RESET_MSK |
+ JPEG_V420_RST_CMD_WE_RESET_MSK |
+ JPEG_V420_RST_CMD_ENCODER_RESET_MSK |
+ JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST_MSK |
+ JPEG_V420_RST_CMD_SCALE_RESET_MSK |
+ JPEG_V420_RST_CMD_REGISTER_RESET_MSK |
+ JPEG_V420_RST_CMD_MISR_RESET_MSK |
+ JPEG_V420_RST_CMD_CORE_RESET_MSK |
+ JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET_MSK),
+
+ [JMSK_RST_CMD_FE_RESET] = JPEG_V420_RST_CMD_FE_RESET_MSK,
+ [JMSK_RST_CMD_WE_RESET] = JPEG_V420_RST_CMD_WE_RESET_MSK,
+ [JMSK_RST_CMD_ENCODER_RESET] = JPEG_V420_RST_CMD_ENCODER_RESET_MSK,
+ [JMSK_RST_CMD_DECODER_RESET] = JPEG_V420_RST_CMD_DECODER_RESET_MSK,
+ [JMSK_RST_CMD_BLOCK_FORMATTER_RST] = JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST_MSK,
+ [JMSK_RST_CMD_SCALE_RESET] = JPEG_V420_RST_CMD_SCALE_RESET_MSK,
+ [JMSK_RST_CMD_REGISTER_RESET] = JPEG_V420_RST_CMD_REGISTER_RESET_MSK,
+ [JMSK_RST_CMD_MISR_RESET] = JPEG_V420_RST_CMD_MISR_RESET_MSK,
+ [JMSK_RST_CMD_CORE_RESET] = JPEG_V420_RST_CMD_CORE_RESET_MSK,
+ [JMSK_RST_CMD_JMSK_DOMAIN_RESET] = JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET_MSK,
+ [JMSK_RST_CMD_RESET_BYPASS] = JPEG_V420_RST_CMD_RESET_BYPASS_MSK,
+
+ [JMSK_CORE_CFG_FE_ENABLE] = JPEG_V420_CORE_CFG_FE_ENABLE_MSK,
+ [JMSK_CORE_CFG_WE_ENABLE] = JPEG_V420_CORE_CFG_WE_ENABLE_MSK,
+ [JMSK_CORE_CFG_ENC_ENABLE] = JPEG_V420_CORE_CFG_ENC_ENABLE_MSK,
+ [JMSK_CORE_CFG_SCALE_ENABLE] = JPEG_V420_CORE_CFG_SCALE_ENABLE_MSK,
+ [JMSK_CORE_CFG_TESTBUS_ENABLE] = JPEG_V420_CORE_CFG_TESTBUS_ENABLE_MSK,
+ [JMSK_CORE_CFG_MODE] = JPEG_V420_CORE_CFG_MODE_MSK,
+ [JMSK_CORE_CFG_CGC_DISABLE] = JPEG_V420_CORE_CFG_CGC_DISABLE_MSK,
+
+ [JMSK_CMD_HW_START] = JPEG_V420_CMD_HW_START_MSK,
+ [JMSK_CMD_HW_STOP] = JPEG_V420_CMD_HW_STOP_MSK,
+
+ [JMSK_CMD_CLR_RD_PLNS_QUEUE] =
+ (JPEG_V420_CMD_CLR_RD_PLN0_QUEUE_MSK |
+ JPEG_V420_CMD_CLR_RD_PLN1_QUEUE_MSK |
+ JPEG_V420_CMD_CLR_RD_PLN2_QUEUE_MSK),
+ [JMSK_CMD_CLR_WR_PLNS_QUEUE] =
+ (JPEG_V420_CMD_CLR_WR_PLN0_QUEUE_MSK |
+ JPEG_V420_CMD_CLR_WR_PLN1_QUEUE_MSK |
+ JPEG_V420_CMD_CLR_WR_PLN2_QUEUE_MSK),
+
+ [JMSK_CMD_APPLY_SWC_RD_PARAMS] = JPEG_V420_CMD_APPLY_SWC_RD_PARAMS_MSK,
+
+ [JMSK_CORE_STATUS_ENCODE_STATE] = JPEG_V420_CORE_STATE_STATUS_ENCODE_STATE_MSK,
+ [JMSK_CORE_STATUS_SCALE_STATE] = JPEG_V420_CORE_STATE_STATUS_SCALE_STATE_MSK,
+ [JMSK_CORE_STATUS_RT_STATE] = JPEG_V420_CORE_STATE_STATUS_REALTIME_STATE_MSK,
+ [JMSK_CORE_STATUS_BUS_STATE] = JPEG_V420_CORE_STATE_STATUS_BUS_STATE_MSK,
+ [JMSK_CORE_STATUS_CGC_STATE] = JPEG_V420_CORE_STATE_STATUS_CGC_STATE_MSK,
+
+ [JMSK_IRQ_STATUS_SESSION_DONE] = JPEG_V420_IRQ_STATUS_SESSION_DONE_MSK,
+
+ [JMSK_IRQ_STATUS_RD_BUF_PLN0_DONE] = JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_DONE_MSK,
+ [JMSK_IRQ_STATUS_RD_BUF_PLN1_DONE] = JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_DONE_MSK,
+ [JMSK_IRQ_STATUS_RD_BUF_PLN2_DONE] = JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_DONE_MSK,
+ [JMSK_IRQ_STATUS_RD_BUF_PLNS_ATTN] = JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK,
+
+ [JMSK_IRQ_STATUS_WR_BUF_PLN0_DONE] = JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK,
+ [JMSK_IRQ_STATUS_WR_BUF_PLN1_DONE] = JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_DONE_MSK,
+ [JMSK_IRQ_STATUS_WR_BUF_PLN2_DONE] = JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_DONE_MSK,
+ [JMSK_IRQ_STATUS_WR_BUF_PLNS_ATTN] = JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK,
+
+ [JMSK_IRQ_STATUS_SESSION_ERROR] = JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK,
+
+ [JMSK_IRQ_STATUS_STOP_ACK] = JPEG_V420_IRQ_STATUS_STOP_ACK_MSK,
+ [JMSK_IRQ_STATUS_RESET_ACK] = JPEG_V420_IRQ_STATUS_RESET_ACK_MSK,
+
+ [JMSK_IRQ_STATUS_ALL_BITS] = JPEG_V420_IRQ_STATUS_ALL_BITS,
+
+ [JMSK_FE_CFG_BYTE_ORDERING] = JPEG_V420_FE_CFG_BYTE_ORDERING_MSK,
+ [JMSK_FE_CFG_BURST_LENGTH_MAX] = JPEG_V420_FE_CFG_BURST_LENGTH_MAX_MSK,
+ [JMSK_FE_CFG_MEMORY_FORMAT] = JPEG_V420_FE_CFG_MEMORY_FORMAT_MSK,
+ [JMSK_FE_CFG_CBCR_ORDER] = JPEG_V420_FE_CFG_CBCR_ORDER_MSK,
+ [JMSK_FE_CFG_BOTTOM_VPAD_EN] = JPEG_V420_FE_CFG_BOTTOM_VPAD_EN_MSK,
+ [JMSK_FE_CFG_PLN0_EN] = JPEG_V420_FE_CFG_PLN0_EN_MSK,
+ [JMSK_FE_CFG_PLN1_EN] = JPEG_V420_FE_CFG_PLN1_EN_MSK,
+ [JMSK_FE_CFG_PLN2_EN] = JPEG_V420_FE_CFG_PLN2_EN_MSK,
+ [JMSK_FE_CFG_SIXTEEN_MCU_EN] = JPEG_V420_FE_CFG_SIXTEEN_MCU_EN_MSK,
+ [JMSK_FE_CFG_MCUS_PER_BLOCK] = JPEG_V420_FE_CFG_MCUS_PER_BLOCK_MSK,
+ [JMSK_FE_CFG_MAL_BOUNDARY] = JPEG_V420_FE_CFG_MAL_BOUNDARY_MSK,
+ [JMSK_FE_CFG_MAL_EN] = JPEG_V420_FE_CFG_MAL_EN_MSK,
+
+ [JMSK_FE_VBPAD_CFG_BLOCK_ROW] = JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK,
+
+ [JMSK_PLNS_RD_OFFSET] = JPEG_V420_PLN_RD_OFFS_OFFSET_MSK,
+ [JMSK_PLNS_RD_BUF_SIZE_WIDTH] = JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK,
+ [JMSK_PLNS_RD_BUF_SIZE_HEIGHT] = JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK,
+ [JMSK_PLNS_RD_STRIDE] = JPEG_V420_PLN_RD_STRIDE_STRIDE_MSK,
+ [JMSK_PLNS_RD_HINIT] = JPEG_V420_PLN_RD_HINIT_FRACTIONAL_MSK,
+ [JMSK_PLNS_RD_VINIT] = JPEG_V420_PLN_RD_VINIT_FRACTIONAL_MSK,
+
+ [JMSK_WE_CFG_BYTE_ORDERING] = JPEG_V420_WE_CFG_BYTE_ORDERING_MSK,
+ [JMSK_WE_CFG_BURST_LENGTH_MAX] = JPEG_V420_WE_CFG_BURST_LENGTH_MAX_MSK,
+ [JMSK_WE_CFG_MEMORY_FORMAT] = JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK,
+ [JMSK_WE_CFG_CBCR_ORDER] = JPEG_V420_WE_CFG_CBCR_ORDER_MSK,
+ [JMSK_WE_CFG_PLN0_EN] = JPEG_V420_WE_CFG_PLN0_EN_MSK,
+ [JMSK_WE_CFG_PLN1_EN] = JPEG_V420_WE_CFG_PLN1_EN_MSK,
+ [JMSK_WE_CFG_PLN2_EN] = JPEG_V420_WE_CFG_PLN2_EN_MSK,
+ [JMSK_WE_CFG_MAL_BOUNDARY] = JPEG_V420_WE_CFG_MAL_BOUNDARY_MSK,
+ [JMSK_WE_CFG_MAL_EN] = JPEG_V420_WE_CFG_MAL_EN_MSK,
+ [JMSK_WE_CFG_POP_BUFF_ON_EOS] = JPEG_V420_WE_CFG_POP_BUFF_ON_EOS_MSK,
+
+ [JMSK_PLNS_WR_BUF_SIZE_WIDTH] = JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_MSK,
+ [JMSK_PLNS_WR_BUF_SIZE_HEIGHT] = JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_MSK,
+
+ [JMSK_PLNS_WR_STRIDE] = JPEG_V420_PLN_WR_STRIDE_STRIDE_MSK,
+ [JMSK_PLNS_WR_HINIT] = JPEG_V420_PLN_WR_HINIT_INTEGER_MSK,
+ [JMSK_PLNS_WR_VINIT] = JPEG_V420_PLN_WR_VINIT_INTEGER_MSK,
+ [JMSK_PLNS_WR_HSTEP] = JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK,
+ [JMSK_PLNS_WR_VSTEP] = JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK,
+
+ [JMSK_PLNS_WR_BLOCK_CFG_PER_COL] = JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_MSK,
+ [JMSK_PLNS_WR_BLOCK_CFG_PER_RAW] = JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_MSK,
+
+ [JMSK_SCALE_CFG_HSCALE_ENABLE] = JPEG_V420_SCALE_CFG_HSCALE_ENABLE_MSK,
+ [JMSK_SCALE_CFG_VSCALE_ENABLE] = JPEG_V420_SCALE_CFG_VSCALE_ENABLE_MSK,
+ [JMSK_SCALE_CFG_UPSAMPLE_EN] = JPEG_V420_SCALE_CFG_UPSAMPLE_EN_MSK,
+ [JMSK_SCALE_CFG_SUBSAMPLE_EN] = JPEG_V420_SCALE_CFG_SUBSAMPLE_EN_MSK,
+ [JMSK_SCALE_CFG_HSCALE_ALGO] = JPEG_V420_SCALE_CFG_HSCALE_ALGO_MSK,
+ [JMSK_SCALE_CFG_VSCALE_ALGO] = JPEG_V420_SCALE_CFG_VSCALE_ALGO_MSK,
+ [JMSK_SCALE_CFG_H_SCALE_FIR_ALGO] = JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_MSK,
+ [JMSK_SCALE_CFG_V_SCALE_FIR_ALGO] = JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_MSK,
+
+ [JMSK_SCALE_PLNS_OUT_CFG_BLK_WIDTH] = JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_MSK,
+ [JMSK_SCALE_PLNS_OUT_CFG_BLK_HEIGHT] = JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_MSK,
+
+ [JMSK_SCALE_PLNS_HSTEP_FRACTIONAL] = JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_MSK,
+ [JMSK_SCALE_PLNS_HSTEP_INTEGER] = JPEG_V420_SCALE_PLN_HSTEP_INTEGER_MSK,
+ [JMSK_SCALE_PLNS_VSTEP_FRACTIONAL] = JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_MSK,
+ [JMSK_SCALE_PLNS_VSTEP_INTEGER] = JPEG_V420_SCALE_PLN_VSTEP_INTEGER_MSK,
+
+ [JMSK_ENC_CFG_IMAGE_FORMAT] = JPEG_V420_ENC_CFG_IMAGE_FORMAT_MSK,
+ [JMSK_ENC_CFG_APPLY_EOI] = JPEG_V420_ENC_CFG_APPLY_EOI_MSK,
+ [JMSK_ENC_CFG_HUFFMAN_SEL] = JPEG_V420_ENC_CFG_HUFFMAN_SEL_MSK,
+ [JMSK_ENC_CFG_FSC_ENABLE] = JPEG_V420_ENC_CFG_FSC_ENABLE_MSK,
+ [JMSK_ENC_CFG_OUTPUT_DISABLE] = JPEG_V420_ENC_CFG_OUTPUT_DISABLE_MSK,
+ [JMSK_ENC_CFG_RST_MARKER_PERIOD] = JPEG_V420_ENC_CFG_RST_MARKER_PERIOD_MSK,
+ [JMSK_ENC_IMAGE_SIZE_WIDTH] = JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK,
+ [JMSK_ENC_IMAGE_SIZE_HEIGHT] = JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK,
+};
+
+const struct qcom_jpeg_reg_offs qcom_jpeg_v420_hw_reg_offs = {
+ .hw_version = 0x000,
+ .hw_capability = 0x004,
+ .reset_cmd = 0x008,
+ .core_cfg = 0x00c,
+ .hw_cmd = 0x010,
+ .int_mask = 0x018,
+ .int_clr = 0x01c,
+ .int_status = 0x020,
+ .enc_core_state = 0x014,
+
+ .fe = {
+ .pntr = { 0x038, 0x044, 0x050 },
+ .offs = { 0x03c, 0x048, 0x054 },
+ .cnsmd = { 0x040, 0x04c, 0x058 },
+ .bsize = { 0x060, 0x068, 0x070 },
+ .stride = { 0x064, 0x06c, 0x08c },
+ .hinit = { 0x074, 0x078, 0x07c },
+ .vinit = { 0x080, 0x084, 0x088 },
+ .pntr_cnt = 0x05c,
+ .vbpad_cfg = 0x2e8
+ },
+ .fe_cfg = 0x024,
+
+ .we = {
+ .pntr = { 0x0cc, 0x0d0, 0x0d4 },
+ .cnsmd = { 0x0d8, 0x0dc, 0x0e0 },
+ .bsize = { 0x0e8, 0x0ec, 0x0f0 },
+ .stride = { 0x0f4, 0x0f8, 0x0fc },
+ .hinit = { 0x100, 0x104, 0x108 },
+ .hstep = { 0x118, 0x11c, 0x120 },
+ .vinit = { 0x10c, 0x110, 0x114 },
+ .vstep = { 0x124, 0x128, 0x12c },
+ .blocks = { 0x130, 0x134, 0x138 },
+ .pntr_cnt = 0x0e4
+ },
+ .we_cfg = 0x0c0,
+
+ .scale = {
+ .hstep = { 0x27c, 0x280, 0x284 },
+ .vstep = { 0x28c, 0x290, 0x294 },
+ },
+ .scale_cfg = 0x26c,
+ .scale_out_cfg = { 0x270, 0x274, 0x278 },
+
+ .enc_cfg = 0x13c,
+ .enc_img_size = 0x140,
+ .enc_out_size = 0x180,
+
+ .dmi_cfg = 0x298,
+ .dmi_data = 0x2a0,
+ .dmi_addr = 0x29c,
+};
+
+#endif /* QCOM_JENC_V420_HW_INFO_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
new file mode 100644
index 000000000000..cfaa2aede965
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
@@ -0,0 +1,1109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-sg.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "qcom_jenc_dev.h"
+#include "qcom_jenc_v4l2.h"
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_defs.h"
+
+static const struct v4l2_frmsizeenum jpeg_def_frmsize = {
+ .stepwise = {
+ .min_width = QCOM_JPEG_HW_MIN_WIDTH,
+ .max_width = QCOM_JPEG_HW_MAX_WIDTH,
+ .step_width = QCOM_JPEG_HW_DEF_HSTEP,
+ .min_height = QCOM_JPEG_HW_MIN_HEIGHT,
+ .max_height = QCOM_JPEG_HW_MAX_HEIGHT,
+ .step_height = QCOM_JPEG_HW_DEF_VSTEP,
+ },
+ .type = V4L2_FRMSIZE_TYPE_STEPWISE
+};
+
+static const struct jenc_enc_format jpeg_src_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21M,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_GREY,
+ .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+ },
+};
+
+#define JPEG_SRC_FMT_COUNT ARRAY_SIZE(jpeg_src_formats)
+
+static const struct jenc_enc_format jpeg_dst_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_JPEG,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ }
+};
+
+#define JPEG_DST_FMT_COUNT ARRAY_SIZE(jpeg_dst_formats)
+
+static inline struct jenc_context *jpeg_file2ctx(struct file *file)
+{
+ struct v4l2_fh *fh = (file_to_v4l2_fh(file));
+
+ if (!fh)
+ return NULL;
+
+ return container_of(fh, struct jenc_context, fh);
+}
+
+static struct qcom_jenc_queue *jpeg_get_bufq(struct jenc_context *ectx, enum qcom_enc_qid id)
+{
+ return &ectx->bufq[id];
+}
+
+static bool jpeg_v4l2_queues_busy(struct jenc_context *ctx)
+{
+ struct vb2_queue *out_q;
+ struct vb2_queue *cap_q;
+
+ out_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+
+ cap_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+ return vb2_is_busy(out_q) || vb2_is_busy(cap_q);
+}
+
+static bool jpeg_is_invalid_src(struct jenc_context *ectx, u32 type)
+{
+ bool is_invalid = (type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+
+ if (is_invalid)
+ dev_err(ectx->dev, "invalid src type or format\n");
+
+ return is_invalid;
+}
+
+static bool jpeg_is_invalid_dst(struct jenc_context *ectx, u32 type)
+{
+ bool is_invalid = (type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+ if (is_invalid)
+ dev_err(ectx->dev, "invalid dst type or format\n");
+
+ return is_invalid;
+}
+
+static const struct jenc_enc_format *jpeg_find_pix_format(enum qcom_enc_qid id, u32 fourcc)
+{
+ const struct jenc_enc_format *efmt;
+ unsigned int i, count;
+
+ if (id == JENC_SRC_QUEUE) {
+ count = JPEG_SRC_FMT_COUNT;
+ efmt = &jpeg_src_formats[0];
+ } else {
+ count = JPEG_DST_FMT_COUNT;
+ efmt = &jpeg_dst_formats[0];
+ }
+
+ for (i = 0; i < count; i++) {
+ if (efmt[i].fourcc == fourcc)
+ return &efmt[i];
+ }
+
+ return NULL;
+}
+
+static const struct jenc_enc_format *jpeg_get_format(struct jenc_context *ectx,
+ enum qcom_enc_qid qid, u32 pixelformat)
+{
+ const struct jenc_enc_format *efmt = jpeg_find_pix_format(qid, pixelformat);
+
+ if (!efmt)
+ efmt = (qid == JENC_SRC_QUEUE) ? &jpeg_src_formats[0] : &jpeg_dst_formats[0];
+
+ return efmt;
+}
+
+static int jpeg_update_src_planes(const struct jenc_enc_format *ef, struct v4l2_format *v4f)
+{
+ struct v4l2_pix_format_mplane *f = &v4f->fmt.pix_mp;
+ const struct v4l2_format_info *info = v4l2_format_info(ef->fourcc);
+ int rc;
+
+ if (!info)
+ return -EINVAL;
+
+ f->pixelformat = ef->fourcc;
+
+ f->field = V4L2_FIELD_NONE;
+ f->colorspace = V4L2_COLORSPACE_SRGB;
+ f->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(f->colorspace);
+ f->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(f->colorspace);
+ f->quantization =
+ V4L2_MAP_QUANTIZATION_DEFAULT(f->ycbcr_enc == V4L2_YCBCR_ENC_601,
+ f->colorspace, f->ycbcr_enc);
+
+ v4l2_apply_frmsize_constraints(&f->width, &f->height, &jpeg_def_frmsize.stepwise);
+
+ rc = v4l2_fill_pixfmt_mp(f, ef->fourcc, f->width, f->height);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static void jpeg_update_dst_plane(const struct jenc_enc_format *ef, struct v4l2_format *v4f)
+{
+ struct v4l2_pix_format_mplane *f = &v4f->fmt.pix_mp;
+ u64 size;
+ u32 nbx, nby;
+
+ f->pixelformat = ef->fourcc;
+ f->field = V4L2_FIELD_NONE;
+ f->colorspace = V4L2_COLORSPACE_SRGB;
+ f->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(f->colorspace);
+ f->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(f->colorspace);
+ f->quantization =
+ V4L2_MAP_QUANTIZATION_DEFAULT(f->ycbcr_enc == V4L2_YCBCR_ENC_601,
+ f->colorspace, f->ycbcr_enc);
+
+ v4l2_apply_frmsize_constraints(&f->width, &f->height, &jpeg_def_frmsize.stepwise);
+
+ /*
+ * JPEG is a variable-size format. The output size cannot be derived
+ * from bits per point or line stride.
+ *
+ * Provide a conservative upper bound based on worst-case entropy
+ * coding of 8x8 DCT blocks:
+ *
+ * - Each 8x8 block has 64 coefficients (1 DC + 63 AC).
+ * - In worst-case (high-entropy input, low quantization), all
+ * coefficients may be non-zero.
+ * - Huffman coding then emits (code + magnitude bits) per coefficient,
+ * which can approach ~2 bytes per coefficient in the worst case.
+ *
+ * => Worst-case is 64 coefficients * 2 bytes = 128 bytes per 8x8 block
+ * => approximately 2 bytes per point
+ *
+ * This bound implicitly covers byte stuffing (0xFF escaping) and is
+ * conservative with respect to subsampled formats (e.g. 4:2:0).
+ *
+ * Additional margin is added for headers and alignment.
+ *
+ * Note: This is a conservative upper bound, not an exact size.
+ */
+
+ nbx = DIV_ROUND_UP(f->width, 8);
+ nby = DIV_ROUND_UP(f->height, 8);
+
+ size = nbx * nby * 128;
+ size += SZ_4K; /* header + safety margin */
+
+ f->plane_fmt[0].bytesperline = 0;
+ f->plane_fmt[0].sizeimage = ALIGN(size, SZ_4K);
+}
+
+static int jpeg_enum_fmt_src(struct v4l2_fmtdesc *f)
+{
+ if (f->index >= JPEG_SRC_FMT_COUNT)
+ return -EINVAL;
+
+ f->pixelformat = jpeg_src_formats[f->index].fourcc;
+
+ return 0;
+}
+
+static int jpeg_enum_fmt_dst(struct v4l2_fmtdesc *f)
+{
+ if (f->index >= JPEG_DST_FMT_COUNT)
+ return -EINVAL;
+
+ f->pixelformat = jpeg_dst_formats[f->index].fourcc;
+
+ return 0;
+}
+
+static int jpeg_v4l2_try_format(struct jenc_context *ectx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pm = &f->fmt.pix_mp;
+ const struct jenc_enc_format *ef;
+ int rc;
+
+ /* The function always returns valid driver format */
+ ef = jpeg_get_format(ectx, TYPE2QID(f->type), pm->pixelformat);
+
+ dev_dbg(ectx->dev, "type=%d %c%c%c%c\n",
+ TYPE2QID(f->type),
+ (ef->fourcc >> 0) & 0xff,
+ (ef->fourcc >> 8) & 0xff,
+ (ef->fourcc >> 16) & 0xff,
+ (ef->fourcc >> 24) & 0xff);
+
+ if (V4L2_TYPE_IS_CAPTURE(f->type)) {
+ f->fmt.pix_mp.num_planes = 1;
+
+ jpeg_update_dst_plane(ef, f);
+
+ dev_dbg(ectx->dev, "\tImage: %dx%d Size:%9d\n", f->fmt.pix_mp.width,
+ f->fmt.pix_mp.height, f->fmt.pix_mp.plane_fmt[0].sizeimage);
+ } else {
+ /*
+ * The used format is an internal driver format that must be
+ * present in the V4L2 common formats; therefore, the errors
+ * below should never occur.
+ */
+ const struct v4l2_format_info *info = v4l2_format_info(ef->fourcc);
+ u8 pln = 0;
+
+ if (!info)
+ return -EINVAL;
+
+ f->fmt.pix_mp.num_planes = info->comp_planes;
+
+ rc = jpeg_update_src_planes(ef, f);
+ if (rc)
+ return rc;
+
+ for (pln = 0; pln < f->fmt.pix_mp.num_planes; pln++)
+ dev_dbg(ectx->dev, "\tImage: %dx%d BPL:%5d Size:%9d\n",
+ pm->width, pm->height, pm->plane_fmt[pln].bytesperline,
+ pm->plane_fmt[pln].sizeimage);
+ }
+
+ return 0;
+}
+
+static int jpeg_v4l2_set_defaults(struct jenc_context *ectx)
+{
+ struct qcom_jenc_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+ struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+ struct v4l2_format f = {0};
+ int rc;
+
+ f.type = jpeg_src_formats->type;
+ f.fmt.pix_mp.pixelformat = jpeg_src_formats->fourcc;
+ f.fmt.pix_mp.width = QCOM_JPEG_HW_DEF_WIDTH;
+ f.fmt.pix_mp.height = QCOM_JPEG_HW_DEF_HEIGHT;
+
+ rc = jpeg_v4l2_try_format(ectx, &f);
+ if (rc)
+ return rc;
+
+ sq->vf = f.fmt.pix_mp;
+
+ f.type = jpeg_dst_formats->type;
+ f.fmt.pix_mp.pixelformat = jpeg_dst_formats->fourcc;
+ f.fmt.pix_mp.width = QCOM_JPEG_HW_DEF_WIDTH;
+ f.fmt.pix_mp.height = QCOM_JPEG_HW_DEF_HEIGHT;
+
+ rc = jpeg_v4l2_try_format(ectx, &f);
+ if (rc)
+ return rc;
+
+ dq->vf = f.fmt.pix_mp;
+
+ return 0;
+}
+
+static int jpeg_v4l2_set_format(struct jenc_context *ectx, struct v4l2_format *f)
+{
+ const struct qcom_jpeg_hw_ops *hw = ectx->jenc->res->hw_ops;
+ struct qcom_jenc_queue *q = jpeg_get_bufq(ectx, TYPE2QID(f->type));
+ struct qcom_jenc_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+ struct v4l2_pix_format_mplane *pm = &f->fmt.pix_mp;
+ u32 old_src_fourcc = sq->vf.pixelformat;
+ int rc;
+
+ if (jpeg_v4l2_queues_busy(ectx))
+ return -EBUSY;
+
+ if (!v4l2_m2m_get_vq(ectx->fh.m2m_ctx, f->type)) {
+ dev_err(ectx->dev, "cannot get video queue\n");
+ return -EINVAL;
+ }
+
+ rc = jpeg_v4l2_try_format(ectx, f);
+ if (rc)
+ return rc;
+
+ /*
+ * Because scaling is not supported, source and destination image
+ * sizes must be equal.
+ */
+ if (V4L2_TYPE_IS_CAPTURE(f->type)) {
+ /* Adjust source size to match capture size */
+ if (pm->width != sq->vf.width || pm->height != sq->vf.height) {
+ struct v4l2_format nf = {0};
+
+ nf.type = jpeg_src_formats->type;
+ nf.fmt.pix_mp.pixelformat = sq->vf.pixelformat;
+ nf.fmt.pix_mp.width = pm->width;
+ nf.fmt.pix_mp.height = pm->height;
+
+ rc = jpeg_v4l2_try_format(ectx, &nf);
+ if (rc)
+ return rc;
+
+ sq->vf = nf.fmt.pix_mp;
+ }
+
+ } else {
+ struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+ struct v4l2_format nf = {0};
+
+ /* Adjust destination size to match source size */
+ if (pm->width != dq->vf.width || pm->height != dq->vf.height) {
+ nf.type = jpeg_dst_formats->type;
+ nf.fmt.pix_mp.pixelformat = dq->vf.pixelformat;
+ nf.fmt.pix_mp.width = pm->width;
+ nf.fmt.pix_mp.height = pm->height;
+
+ rc = jpeg_v4l2_try_format(ectx, &nf);
+ if (rc)
+ return rc;
+
+ dq->vf = nf.fmt.pix_mp;
+
+ /*
+ * The horizontal alignment of the destination is larger, and the
+ * result after adjustment may still differ. In this case, the
+ * requested image size should also be modified.
+ */
+ if (pm->width != nf.fmt.pix_mp.width ||
+ pm->height != nf.fmt.pix_mp.height) {
+ pm->width = nf.fmt.pix_mp.width;
+ pm->height = nf.fmt.pix_mp.height;
+ }
+ }
+ }
+
+ q->vf = *pm;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type) && hw->src_fmt_update) {
+ rc = hw->src_fmt_update(ectx, old_src_fourcc, q->vf.pixelformat);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static void jpeg_v4l2_get_format(struct jenc_context *ectx, struct v4l2_format *f)
+{
+ struct qcom_jenc_queue *q = jpeg_get_bufq(ectx, TYPE2QID(f->type));
+
+ f->fmt.pix_mp = q->vf;
+}
+
+static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state);
+
+static void jpeg_finish_work(struct work_struct *work)
+{
+ struct jenc_context *ctx = container_of(work, struct jenc_context, finish_work);
+
+ v4l2_m2m_job_finish(ctx->jenc->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static void jpeg_stop_work(struct work_struct *work)
+{
+ struct jenc_context *ctx = container_of(work, struct jenc_context, stop_work);
+ struct qcom_jenc_dev *jenc = ctx->jenc;
+
+ mutex_lock(&jenc->dev_mutex);
+ jpeg_v4l2_work_stop(ctx, VB2_BUF_STATE_ERROR);
+ mutex_unlock(&jenc->dev_mutex);
+}
+
+static void jpeg_v4l2_work_done(struct jenc_context *ctx, size_t out_size)
+{
+ struct vb2_v4l2_buffer *vb;
+
+ vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ if (vb)
+ v4l2_m2m_buf_done(vb, VB2_BUF_STATE_DONE);
+
+ vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+ if (vb) {
+ vb2_set_plane_payload(&vb->vb2_buf, 0, out_size);
+ v4l2_m2m_buf_done(vb, VB2_BUF_STATE_DONE);
+ }
+
+ schedule_work(&ctx->finish_work);
+}
+
+static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
+{
+ struct vb2_v4l2_buffer *vb;
+
+ while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
+ if (!v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {
+ if (ctx->is_stopping) {
+ ctx->is_stopping = false;
+ vb2_set_plane_payload(&vb->vb2_buf, 0, 0);
+ }
+ v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vb);
+ } else {
+ v4l2_m2m_buf_done(vb, buff_state);
+ }
+ }
+
+ while ((vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(vb, buff_state);
+
+ schedule_work(&ctx->finish_work);
+}
+
+static void jpeg_v4l2_process_cb(void *priv, enum vb2_buffer_state ev, size_t out_size)
+{
+ struct jenc_context *ctx = priv;
+ struct qcom_jenc_dev *jenc = ctx->jenc;
+
+ /* Threaded IRQ path. */
+ mutex_lock(&jenc->dev_mutex);
+
+ if (ev == VB2_BUF_STATE_DONE && out_size)
+ jpeg_v4l2_work_done(ctx, out_size);
+ else
+ jpeg_v4l2_work_stop(ctx, ev);
+
+ mutex_unlock(&jenc->dev_mutex);
+}
+
+static int cop_jpeg_v4l2_set_ctrls(struct v4l2_ctrl *ctrl)
+{
+ struct jenc_context *ectx = container_of(ctrl->handler, struct jenc_context, ctrl_hdl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+ mutex_lock(&ectx->quality_mutex);
+ ectx->quality_requested = ctrl->val;
+ mutex_unlock(&ectx->quality_mutex);
+ break;
+ default:
+ dev_err(ectx->dev, "%s: invalid control=%#x\n", __func__, ctrl->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops qcom_jpeg_v4l2_ctrl_ops = {
+ .s_ctrl = cop_jpeg_v4l2_set_ctrls,
+};
+
+static int bop_jpeg_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+ unsigned int *plns_per_buff, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct jenc_context *ectx = vb2_get_drv_priv(vq);
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+ struct qcom_jenc_queue *q;
+ int pln;
+
+ q = hw->get_queue(ectx, TYPE2QID(vq->type));
+ if (!q || !q->vf.num_planes)
+ return -EINVAL;
+
+ if (*plns_per_buff) {
+ if (*plns_per_buff != q->vf.num_planes)
+ return -EINVAL;
+
+ for (pln = 0; pln < q->vf.num_planes; ++pln) {
+ if (sizes[pln] < q->vf.plane_fmt[pln].sizeimage)
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ *plns_per_buff = q->vf.num_planes;
+ for (pln = 0; pln < q->vf.num_planes; ++pln) {
+ sizes[pln] = q->vf.plane_fmt[pln].sizeimage;
+ dev_dbg(ectx->dev, "%s: queue=%d size[%d]=%d\n", __func__, TYPE2QID(vq->type),
+ pln, sizes[pln]);
+ }
+
+ if (V4L2_TYPE_IS_CAPTURE(vq->type))
+ sizes[0] += JPEG_HEADER_MAX;
+
+ return hw->queue_setup(ectx, TYPE2QID(vq->type));
+}
+
+static int bop_jpeg_vb2_buf_out_validate(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ if (vbuf->field == V4L2_FIELD_ANY)
+ vbuf->field = V4L2_FIELD_NONE;
+
+ if (vbuf->field != V4L2_FIELD_NONE)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int bop_jpeg_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+ struct jenc_context *ectx = vb2_get_drv_priv(vb->vb2_queue);
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+ struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb->type)];
+ int pln;
+ int rc;
+
+ if (vb->num_planes != q->vf.num_planes)
+ return -EINVAL;
+
+ for (pln = 0; pln < q->vf.num_planes; pln++) {
+ if (q->vf.plane_fmt[pln].sizeimage == 0)
+ return -EINVAL;
+
+ if (vb2_plane_size(vb, pln) < q->vf.plane_fmt[pln].sizeimage)
+ return -EINVAL;
+ }
+
+ rc = hw->buf_prepare(ectx, vb);
+ if (rc) {
+ dev_err_ratelimited(ectx->dev, "buffer prepare failed\n");
+ jpeg_v4l2_process_cb(ectx, VB2_BUF_STATE_ERROR, 0);
+ return rc;
+ }
+
+ return 0;
+}
+
+static void bop_jpeg_vb2_buf_queue(struct vb2_buffer *vb)
+{
+ struct jenc_context *ectx = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ v4l2_m2m_buf_queue(ectx->fh.m2m_ctx, vbuf);
+}
+
+static int bop_jpeg_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct jenc_context *ectx = vb2_get_drv_priv(q);
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+ struct qcom_jenc_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+ struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+ u32 hw_caps;
+ u8 pln;
+ int rc;
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+ dev_dbg(ectx->dev, "%c%c%c%c %dx%d\n",
+ (sq->vf.pixelformat >> 0) & 0xff,
+ (sq->vf.pixelformat >> 8) & 0xff,
+ (sq->vf.pixelformat >> 16) & 0xff,
+ (sq->vf.pixelformat >> 24) & 0xff,
+ sq->vf.width, sq->vf.height);
+
+ for (pln = 0; pln < sq->vf.num_planes; pln++) {
+ dev_dbg(ectx->dev, "\tpln=%d %dx%d bpl:%d size:%d\n", pln,
+ sq->vf.width, sq->vf.height,
+ sq->vf.plane_fmt[pln].bytesperline,
+ sq->vf.plane_fmt[pln].sizeimage);
+ }
+ } else {
+ dev_dbg(ectx->dev, "%c%c%c%c %dx%d\n",
+ (dq->vf.pixelformat >> 0) & 0xff,
+ (dq->vf.pixelformat >> 8) & 0xff,
+ (dq->vf.pixelformat >> 16) & 0xff,
+ (dq->vf.pixelformat >> 24) & 0xff,
+ dq->vf.width, dq->vf.height);
+ }
+
+ mutex_lock(&jenc->dev_mutex);
+
+ /*
+ * Header cache is initialized lazily on CAPTURE QBUF, so valid V4L2
+ * orders like STREAMON before first QBUF still get a proper JPEG header.
+ */
+ rc = hw->hw_acquire(ectx, q);
+ if (!rc) {
+ hw->hw_get_cap(jenc, &hw_caps);
+ dev_dbg(ectx->dev, "hw_caps=0x%x\n", hw_caps);
+ }
+
+ mutex_unlock(&jenc->dev_mutex);
+
+ return rc;
+}
+
+static void bop_jpeg_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct jenc_context *ectx = vb2_get_drv_priv(q);
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+
+ mutex_lock(&jenc->dev_mutex);
+
+ jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
+
+ hw->hw_release(ectx, q);
+
+ mutex_unlock(&jenc->dev_mutex);
+}
+
+static const struct vb2_ops qcom_jpeg_v4l2_vb2_ops = {
+ .queue_setup = bop_jpeg_vb2_queue_setup,
+ .buf_out_validate = bop_jpeg_vb2_buf_out_validate,
+ .buf_prepare = bop_jpeg_vb2_buf_prepare,
+ .buf_queue = bop_jpeg_vb2_buf_queue,
+ .start_streaming = bop_jpeg_vb2_start_streaming,
+ .stop_streaming = bop_jpeg_vb2_stop_streaming,
+};
+
+static void mop_jpeg_m2m_job_abort(void *priv)
+{
+ struct jenc_context *ectx = priv;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+
+ mutex_lock(&jenc->dev_mutex);
+
+ jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
+
+ mutex_unlock(&jenc->dev_mutex);
+}
+
+static void mop_jpeg_m2m_job_run(void *priv)
+{
+ struct jenc_context *ectx = priv;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+ struct qcom_jenc_queue *sq, *dq;
+
+ mutex_lock(&jenc->dev_mutex);
+
+ src_vb = v4l2_m2m_next_src_buf(ectx->fh.m2m_ctx);
+ dst_vb = v4l2_m2m_next_dst_buf(ectx->fh.m2m_ctx);
+
+ if (!src_vb || !dst_vb)
+ goto err_stop;
+
+ if (hw->hw_prepare(jenc))
+ goto err_stop;
+
+ sq = hw->get_queue(ectx, TYPE2QID(src_vb->vb2_buf.type));
+ src_vb->sequence = sq->sequence++;
+ if (hw->process_exec(jenc, ectx, &src_vb->vb2_buf))
+ goto err_stop;
+
+ dq = hw->get_queue(ectx, TYPE2QID(dst_vb->vb2_buf.type));
+ dst_vb->sequence = dq->sequence++;
+ if (hw->process_exec(jenc, ectx, &dst_vb->vb2_buf))
+ goto err_stop;
+
+ v4l2_m2m_buf_copy_metadata(src_vb, dst_vb);
+
+ mutex_unlock(&jenc->dev_mutex);
+ return;
+
+err_stop:
+ jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
+ mutex_unlock(&jenc->dev_mutex);
+}
+
+static const struct v4l2_m2m_ops qcom_jpeg_v4l2_m2m_ops = {
+ .device_run = mop_jpeg_m2m_job_run,
+ .job_abort = mop_jpeg_m2m_job_abort,
+};
+
+static int iop_jpeg_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, QCOM_JPEG_ENC_NAME, sizeof(cap->driver));
+ strscpy(cap->card, QCOM_JPEG_ENC_NAME, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", QCOM_JPEG_ENC_NAME);
+
+ return 0;
+}
+
+static int iop_jpeg_enum_fmt_vid_dst(struct file *file, void *priv, struct v4l2_fmtdesc *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_dst(ectx, f->type))
+ return -EINVAL;
+
+ return jpeg_enum_fmt_dst(f);
+}
+
+static int iop_jpeg_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize)
+{
+ const struct jenc_enc_format *efmt;
+
+ if (fsize->index != 0)
+ return -EINVAL;
+
+ efmt = jpeg_find_pix_format(JENC_SRC_QUEUE, fsize->pixel_format);
+ if (efmt) {
+ fsize->type = jpeg_def_frmsize.type;
+ fsize->stepwise = jpeg_def_frmsize.stepwise;
+ return 0;
+ }
+
+ efmt = jpeg_find_pix_format(JENC_DST_QUEUE, fsize->pixel_format);
+ if (efmt) {
+ fsize->type = jpeg_def_frmsize.type;
+ fsize->stepwise = jpeg_def_frmsize.stepwise;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int iop_jpeg_enum_fmt_vid_src(struct file *file, void *priv, struct v4l2_fmtdesc *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_src(ectx, f->type))
+ return -EINVAL;
+
+ return jpeg_enum_fmt_src(f);
+}
+
+static int iop_jpeg_get_fmt_vid_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_dst(ectx, f->type))
+ return -EINVAL;
+
+ jpeg_v4l2_get_format(ectx, f);
+
+ return 0;
+}
+
+static int iop_jpeg_try_fmt_vid_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_dst(ectx, f->type))
+ return -EINVAL;
+
+ return jpeg_v4l2_try_format(ectx, f);
+}
+
+static int iop_jpeg_set_fmt_vid_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_dst(ectx, f->type))
+ return -EINVAL;
+
+ return jpeg_v4l2_set_format(ectx, f);
+}
+
+static int iop_jpeg_get_fmt_vid_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_src(ectx, f->type))
+ return -EINVAL;
+
+ jpeg_v4l2_get_format(ectx, f);
+
+ return 0;
+}
+
+static int iop_jpeg_try_fmt_vid_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_src(ectx, f->type))
+ return -EINVAL;
+
+ return jpeg_v4l2_try_format(ectx, f);
+}
+
+static int iop_jpeg_set_fmt_vid_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+
+ if (jpeg_is_invalid_src(ectx, f->type))
+ return -EINVAL;
+
+ return jpeg_v4l2_set_format(ectx, f);
+}
+
+static int iop_jpeg_encoder_command(struct file *file, void *priv, struct v4l2_encoder_cmd *ec)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+ struct vb2_queue *vq;
+ int rc;
+
+ if (ec->cmd == V4L2_ENC_CMD_STOP) {
+ vq = v4l2_m2m_get_src_vq(ectx->fh.m2m_ctx);
+ if (!vb2_is_streaming(vq))
+ return 0;
+
+ vq = v4l2_m2m_get_dst_vq(ectx->fh.m2m_ctx);
+ if (!vb2_is_streaming(vq))
+ return 0;
+
+ rc = v4l2_m2m_ioctl_encoder_cmd(file, priv, ec);
+ if (rc)
+ return rc;
+
+ ectx->is_stopping = true;
+ schedule_work(&ectx->stop_work);
+
+ return 0;
+ }
+
+ return v4l2_m2m_ioctl_encoder_cmd(file, priv, ec);
+}
+
+static const struct v4l2_ioctl_ops qcom_jpeg_v4l2_ioctl_ops = {
+ .vidioc_querycap = iop_jpeg_querycap,
+ .vidioc_enum_fmt_vid_cap = iop_jpeg_enum_fmt_vid_dst,
+ .vidioc_enum_fmt_vid_out = iop_jpeg_enum_fmt_vid_src,
+ .vidioc_enum_framesizes = iop_jpeg_enum_framesizes,
+
+ .vidioc_g_fmt_vid_cap_mplane = iop_jpeg_get_fmt_vid_dst,
+ .vidioc_try_fmt_vid_cap_mplane = iop_jpeg_try_fmt_vid_dst,
+ .vidioc_s_fmt_vid_cap_mplane = iop_jpeg_set_fmt_vid_dst,
+ .vidioc_g_fmt_vid_out_mplane = iop_jpeg_get_fmt_vid_src,
+ .vidioc_try_fmt_vid_out_mplane = iop_jpeg_try_fmt_vid_src,
+ .vidioc_s_fmt_vid_out_mplane = iop_jpeg_set_fmt_vid_src,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+
+ .vidioc_encoder_cmd = iop_jpeg_encoder_command,
+ .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd,
+};
+
+static int jpeg_v4l2_init_queue(void *priv, struct vb2_queue *sq, struct vb2_queue *dq)
+{
+ struct jenc_context *ectx = priv;
+ int rc;
+
+ sq->drv_priv = ectx;
+ sq->dev = ectx->dev;
+ sq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ sq->io_modes = VB2_MMAP | VB2_DMABUF | VB2_USERPTR;
+ sq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ sq->ops = &qcom_jpeg_v4l2_vb2_ops;
+ sq->mem_ops = &vb2_dma_sg_memops;
+ sq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ sq->lock = &ectx->ctx_lock;
+ sq->min_queued_buffers = 1;
+
+ rc = vb2_queue_init(sq);
+ if (rc)
+ return rc;
+
+ dq->drv_priv = ectx;
+ dq->dev = ectx->dev;
+ dq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dq->io_modes = VB2_MMAP | VB2_DMABUF | VB2_USERPTR;
+ dq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dq->ops = &qcom_jpeg_v4l2_vb2_ops;
+ dq->mem_ops = &vb2_dma_sg_memops;
+ dq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dq->lock = &ectx->ctx_lock;
+ dq->min_queued_buffers = 1;
+
+ rc = vb2_queue_init(dq);
+ if (rc) {
+ vb2_queue_release(sq);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int fop_jpeg_file_open(struct file *file)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct qcom_jenc_dev *jenc = video_drvdata(file);
+ struct jenc_context *ectx;
+ int rc;
+
+ ectx = kzalloc_obj(*ectx, GFP_KERNEL);
+ if (!ectx)
+ return -ENOMEM;
+
+ ectx->dev = jenc->dev;
+ ectx->jenc = jenc;
+
+ /* Default quality if userspace does not set the control explicitly. */
+ ectx->quality_requested = QCOM_JPEG_QUALITY_DEF;
+ ectx->quality_programmed = 0;
+
+ mutex_init(&ectx->ctx_lock);
+ mutex_init(&ectx->quality_mutex);
+
+ INIT_WORK(&ectx->finish_work, jpeg_finish_work);
+ INIT_WORK(&ectx->stop_work, jpeg_stop_work);
+
+ rc = jpeg_v4l2_set_defaults(ectx);
+ if (rc)
+ goto err_unlock_free;
+
+ v4l2_fh_init(&ectx->fh, vdev);
+ v4l2_fh_add(&ectx->fh, file);
+
+ v4l2_ctrl_handler_init(&ectx->ctrl_hdl, 1);
+ ectx->quality_ctl = v4l2_ctrl_new_std(&ectx->ctrl_hdl,
+ &qcom_jpeg_v4l2_ctrl_ops,
+ V4L2_CID_JPEG_COMPRESSION_QUALITY,
+ QCOM_JPEG_QUALITY_MIN,
+ QCOM_JPEG_QUALITY_MAX,
+ QCOM_JPEG_QUALITY_UNT,
+ QCOM_JPEG_QUALITY_DEF);
+ if (ectx->ctrl_hdl.error) {
+ rc = ectx->ctrl_hdl.error;
+ goto err_fh_exit;
+ }
+
+ ectx->fh.ctrl_handler = &ectx->ctrl_hdl;
+
+ rc = v4l2_ctrl_handler_setup(&ectx->ctrl_hdl);
+ if (rc)
+ goto err_ctrl_handler_free;
+
+ v4l2_m2m_get(jenc->m2m_dev);
+ ectx->fh.m2m_ctx = v4l2_m2m_ctx_init(jenc->m2m_dev, ectx, &jpeg_v4l2_init_queue);
+ if (IS_ERR(ectx->fh.m2m_ctx)) {
+ rc = PTR_ERR(ectx->fh.m2m_ctx);
+ v4l2_m2m_put(jenc->m2m_dev);
+ goto err_ctrl_handler_free;
+ }
+
+ return 0;
+
+err_ctrl_handler_free:
+ v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
+err_fh_exit:
+ v4l2_fh_del(&ectx->fh, file);
+ v4l2_fh_exit(&ectx->fh);
+err_unlock_free:
+
+ kfree(ectx);
+
+ return rc;
+}
+
+static int fop_jpeg_file_release(struct file *file)
+{
+ struct jenc_context *ectx = jpeg_file2ctx(file);
+ struct v4l2_m2m_dev *m2m_dev = ectx->fh.m2m_ctx->m2m_dev;
+
+ cancel_work_sync(&ectx->stop_work);
+ cancel_work_sync(&ectx->finish_work);
+
+ v4l2_m2m_ctx_release(ectx->fh.m2m_ctx);
+ v4l2_m2m_put(m2m_dev);
+ v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
+ v4l2_fh_del(&ectx->fh, file);
+ v4l2_fh_exit(&ectx->fh);
+ kfree(ectx);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations qcom_jpeg_v4l2_file_ops = {
+ .owner = THIS_MODULE,
+ .open = fop_jpeg_file_open,
+ .release = fop_jpeg_file_release,
+ .poll = v4l2_m2m_fop_poll,
+ .mmap = v4l2_m2m_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+int qcom_jpeg_v4l2_register(struct qcom_jenc_dev *jenc)
+{
+ int rc;
+
+ mutex_lock(&jenc->dev_mutex);
+
+ jenc->enc_hw_irq_cb = jpeg_v4l2_process_cb;
+
+ jenc->m2m_dev = v4l2_m2m_init(&qcom_jpeg_v4l2_m2m_ops);
+ if (IS_ERR(jenc->m2m_dev)) {
+ dev_err(jenc->dev, "failed to init mem2mem device\n");
+ rc = PTR_ERR(jenc->m2m_dev);
+ goto err_mutex_unlock;
+ }
+
+ jenc->vdev = video_device_alloc();
+ if (!jenc->vdev) {
+ rc = -ENOMEM;
+ goto err_video_device_release;
+ }
+
+ snprintf(jenc->vdev->name, sizeof(jenc->vdev->name), "%s", QCOM_JPEG_ENC_NAME);
+ jenc->vdev->fops = &qcom_jpeg_v4l2_file_ops;
+ jenc->vdev->ioctl_ops = &qcom_jpeg_v4l2_ioctl_ops;
+ jenc->vdev->minor = -1;
+ jenc->vdev->release = video_device_release;
+ jenc->vdev->lock = &jenc->dev_mutex;
+ jenc->vdev->v4l2_dev = &jenc->v4l2_dev;
+ jenc->vdev->vfl_dir = VFL_DIR_M2M;
+ jenc->vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+
+ rc = video_register_device(jenc->vdev, VFL_TYPE_VIDEO, -1);
+ if (rc) {
+ dev_err(jenc->dev, "failed to register video device\n");
+ goto err_video_device_release;
+ }
+
+ video_set_drvdata(jenc->vdev, jenc);
+
+ mutex_unlock(&jenc->dev_mutex);
+
+ dev_dbg(jenc->dev, "device registered as /dev/video%d\n", jenc->vdev->num);
+
+ return rc;
+
+err_video_device_release:
+ if (jenc->vdev)
+ video_device_release(jenc->vdev);
+ v4l2_m2m_release(jenc->m2m_dev);
+err_mutex_unlock:
+ mutex_unlock(&jenc->dev_mutex);
+
+ return rc;
+}
+
+void qcom_jpeg_v4l2_unregister(struct qcom_jenc_dev *jenc)
+{
+ mutex_lock(&jenc->dev_mutex);
+
+ video_unregister_device(jenc->vdev);
+
+ v4l2_m2m_release(jenc->m2m_dev);
+
+ mutex_unlock(&jenc->dev_mutex);
+}
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
new file mode 100644
index 000000000000..06af818e4ac9
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_V4L2_H
+#define QCOM_JENC_V4L2_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+
+struct qcom_jenc_dev;
+
+int qcom_jpeg_v4l2_register(struct qcom_jenc_dev *jenc);
+
+void qcom_jpeg_v4l2_unregister(struct qcom_jenc_dev *jenc);
+
+#endif /* QCOM_JENC_V4L2_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding
2026-06-12 19:44 ` [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
@ 2026-06-12 19:57 ` sashiko-bot
2026-06-12 20:42 ` Frank Li
1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-12 19:57 UTC (permalink / raw)
To: Atanas Filipov; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Schema validation weakness and missing required properties cause driver probe failure.
- [Medium] Chronologically backward compatible fallback with mismatched hardware constraints.
- [Low] The binding introduces the deprecated `cell-index` property.
--
commit 8d581d21294da3512a77773ccfed01692993fa7b
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
dt-bindings: media: qcom: Add JPEG encoder binding
Add device-tree binding for the standalone Qualcomm JPEG encoder hardware
block (separate from CAMSS media pipelines). Document required resources
briefly and scope initial support to currently used compatibles in this
series, including SM8250, QCM6490, and SM8550 class platforms.
> diff --git a/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> new file mode 100644
> index 0000000000000..83f19719a8693
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> @@ -0,0 +1,142 @@
[ ... ]
> +properties:
> + compatible:
> + oneOf:
> + - items:
> + - const: qcom,sm8250-jenc
> + - items:
> + - enum:
> + - qcom,qcm6490-jenc
> + - const: qcom,sm8550-jenc
[Severity: Medium]
Is it safe to use qcom,sm8550-jenc as a fallback for qcom,qcm6490-jenc?
It looks like the driver uses different performance and clock configuration
data for these two SoCs (qcom_t680_jpeg_drvdata vs qcom_t780_jpeg_drvdata
in drivers/media/platform/qcom/jpeg/qcom_jenc_res.c).
If a generic driver relies on this fallback, it would apply SM8550 clock
frequencies to QCM6490 hardware, which might cause instability. Also, newer
hardware falling back to older hardware is usually the standard devicetree
convention.
> + - items:
> + - const: qcom,sm8550-jenc
> +
> + cell-index:
> + description: Legacy JPEG HW instance index.
> + $ref: /schemas/types.yaml#/definitions/uint32
[Severity: Low]
Is this deprecated cell-index property still required?
The introduced Linux driver does not appear to read or use this property.
Could instance identification be handled dynamically or via standardized
devicetree aliases nodes instead?
> + reg:
> + maxItems: 1
> +
> + reg-names:
> + oneOf:
> + - items:
> + - const: jpeg
> + - items:
> + - const: jpeg-regs
> + - items:
> + - const: jpeg_regs
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + minItems: 6
> + maxItems: 6
> +
> + clock-names:
> + oneOf:
> + - items:
> + - const: gcc_hf_axi
> + - const: gcc_sf_axi
> + - const: core_ahb
> + - const: cpas_ahb
> + - const: camnoc_axi
> + - const: jpeg
> + - items:
> + - const: gcc_hf_axi_clk
> + - const: gcc_sf_axi_clk
> + - const: core_ahb_clk
> + - const: cpas_ahb_clk
> + - const: camnoc_axi_clk
> + - const: jpeg_clk
[Severity: High]
Should the binding strictly enforce the exact resource names the driver expects?
The schema allows variations like "jpeg-regs" and "_clk" suffixes, but the
driver probe in qcom_jpeg_probe() strictly calls:
devm_platform_ioremap_resource_byname(pdev, "jpeg")
and expects non-suffix clock names (e.g., "core_ahb"). If a devicetree uses
the alternative schema-approved names, won't the driver probe fail?
[ ... ]
> +required:
> + - compatible
> + - reg
> + - clocks
> + - interrupts
> + - power-domains
> + - iommus
> + - interconnects
> + - interconnect-names
[Severity: High]
Should reg-names and clock-names be added to this required list?
Since the driver strictly relies on these names to find its resources during
probe, omitting them from the devicetree will cause probe failures.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node
2026-06-12 19:44 ` [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node Atanas Filipov
@ 2026-06-12 20:06 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-12 20:06 UTC (permalink / raw)
To: Atanas Filipov; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Low] Unnecessary presence of the deprecated `cell-index` property in the newly added JPEG encoder node.
- [Low] The `interconnects` property uses the hardcoded magic number `0` for path tags instead of semantic macros.
- [Low] Device tree node `jpeg-encoder@ac4e000` is placed out of order based on its unit-address.
--
commit 7c79d5580891bdc3ade6113afec02f777f964fdb
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
arm64: dts: qcom: sm8550: Add JPEG encoder node
This patch adds the JPEG encoder hardware node to the SM8550 device tree.
It configures resources like clocks, power domains, IOMMUs, and interconnects
to allow the qcom-jpeg V4L2 encoder driver to bind and operate.
> diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi
> index 912525e9bca6f..8090b8b1d7bda 100644
> --- a/arch/arm64/boot/dts/qcom/sm8550.dtsi
> +++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi
[ ... ]
> @@ -3677,6 +3677,48 @@ port@7 {
> };
> };
>
> + qcom_jpeg_enc: jpeg-encoder@ac4e000 {
[Severity: Low]
Is this node placed out of order according to its unit-address?
Usually, nodes within the soc container are strictly sorted by their unit
address. Since this is at 0xac4e000, should it be placed before
isp@acb7000 rather than appended after it?
> + cell-index = <0>;
[Severity: Low]
Is the cell-index property still needed here?
The device tree binding typically describes this as a legacy hardware instance
index, and modern upstream drivers usually do not rely on it. Could we remove
it to align with modern device tree conventions?
> + compatible = "qcom,sm8550-jenc";
> +
> + reg = <0 0xac4e000 0 0x4000>;
[ ... ]
> + iommus = <&apps_smmu 0x20C0 0x20>,
> + <&apps_smmu 0x20E0 0x20>;
> +
> + interconnects = <&gem_noc MASTER_APPSS_PROC 0
> + &config_noc SLAVE_CAMERA_CFG 0>,
> + <&mmss_noc MASTER_CAMNOC_HF 0
> + &mc_virt SLAVE_EBI1 0>,
> + <&mmss_noc MASTER_CAMNOC_SF 0
> + &mc_virt SLAVE_EBI1 0>,
> + <&mmss_noc MASTER_CAMNOC_ICP 0
> + &mc_virt SLAVE_EBI1 0>;
[Severity: Low]
Would it be better to use the standard semantic macros for the interconnect
path tags instead of hardcoding 0?
While the Qualcomm RPMh driver might map 0 to QCOM_ICC_TAG_ALWAYS internally,
using the macro explicitly would maintain consistency with the other
interconnect nodes in the tree and clarify the intended power and bandwidth
configuration.
> +
> + interconnect-names = "cam_ahb",
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
2026-06-12 19:44 ` [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
@ 2026-06-12 20:23 ` sashiko-bot
2026-06-12 20:53 ` Frank Li
1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-12 20:23 UTC (permalink / raw)
To: Atanas Filipov; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [High] The hardware is programmed with a single DMA base address per plane, silently assuming the mapping is always contiguous, but the driver utilizes `VIDEOBUF2_DMA_SG`.
- [High] Active DMA buffers are unconditionally returned to userspace during stop or abort operations without halting the hardware, causing use-after-free and DMA memory corruption.
- [High] The IRQ handler accesses hardware registers without verifying the device's power state, triggering a fatal bus error upon receiving a spurious interrupt during runtime suspend.
- [High] Synchronizing capture buffers with `DMA_BIDIRECTIONAL` when they are mapped as `DMA_FROM_DEVICE` violates the DMA API rules.
- [High] The hardware DMA buffer size is programmed improperly, enabling writes past the allocation boundary.
- [High] The main device structure is allocated using devm_kzalloc but its lifetime is decoupled from the video_device file descriptors, resulting in a Use-After-Free upon device unbind.
- [High] Multiple self-deadlocks and mismatched lock ordering issues related to `dev_mutex` in V4L2 callbacks.
- [High] A race condition between the IRQ bottom half and a worker thread can erroneously halt a newly started hardware job.
- [High] Unconditional `schedule_work` during file release after cancellation barriers leads to a use-after-free of `ectx`.
- [Medium] The driver reports an overly large payload size for the encoded JPEG, leaking uninitialized buffer contents.
- [Medium] `V4L2_ENC_CMD_STOP` incorrectly flushes all pending buffers immediately instead of draining them.
- [Medium] Hardware teardown hangs due to incorrect `reinit_completion` ordering in `jpeg_deinit()`.
- [Medium] Failure to allocate the V4L2 control in `fop_jpeg_file_open` bypasses handler cleanup, leaking bucket memory.
- [Low] Unconditional assignment of `dev_pm_ops` structure without wrapper macros produces dead code when `CONFIG_PM` is disabled.
--
commit 1ef0c6ce9870449657c8ef931f50d481ec34b483
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
Add a Qualcomm JPEG encoder driver implemented on top of the
V4L2 mem2mem framework. The driver handles vb2 queue, formats, header,
interrupts, and runtime PM/ICC for SM8250, QCM6490, and SM8550.
> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
[ ... ]
> +static int qcom_jpeg_probe(struct platform_device *pdev)
> +{
> + const struct qcom_dev_resources *res;
> + struct qcom_jenc_dev *jenc;
> + int rc;
> +
> + jenc = devm_kzalloc(&pdev->dev, sizeof(*jenc), GFP_KERNEL);
[Severity: High]
Does allocating this structure with devm_kzalloc() decouple its lifetime
from the video_device file descriptors? Later in qcom_jpeg_v4l2_register(),
jenc->dev_mutex is used as the vdev->lock. When the device unbinds, devres
frees jenc, but userspace may still hold open file descriptors that keep
the video_device alive, leading to a use-after-free when ioctls try to take
the freed lock.
[ ... ]
> +static struct platform_driver qcom_jpeg_platform_driver = {
> + .probe = qcom_jpeg_probe,
> + .remove = qcom_jpeg_remove,
> + .driver = {
> + .name = QCOM_JPEG_ENC_NAME,
> + .of_match_table = qcom_jpeg_of_match,
> + .pm = &qcom_jpeg_pm_ops,
[Severity: Low]
Could this unconditional assignment produce dead code when CONFIG_PM is
disabled? Using the pm_ptr() wrapper macro here is usually recommended to
allow the compiler to remove unused power management structures.
> + },
> +};
> +
> +module_platform_driver(qcom_jpeg_platform_driver);
> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
[ ... ]
> +static int jpeg_deinit(struct qcom_jenc_dev *jenc)
> +{
> + const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
> + unsigned long rtime;
> +
> + jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
> + jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_STOP_ACK, JPEG_SET_U32);
> +
> + jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_STOP, 1);
> +
> + reinit_completion(&jenc->stop_complete);
[Severity: Medium]
Is there a race condition here if the hardware stops extremely quickly?
The completion is re-initialized after the STOP command has been written,
so if the interrupt fires before reinit_completion(), the completion state
might be incorrectly cleared, causing a timeout.
> + rtime = wait_for_completion_timeout(&jenc->stop_complete,
[ ... ]
> +static int jpeg_store_fe_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
> +{
> + struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb2->type)];
> + struct qcom_jpeg_buff *buff = &q->buff[vb2->index];
> + u8 pln = 0;
> +
> + buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
> + if (!buff->plns[pln].sgt)
> + return -EINVAL;
> +
> + if (!buff->plns[pln].sgt->sgl)
> + return -EINVAL;
> +
> + buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
[Severity: High]
Does this safely handle fragmented scatterlists? Since the queues are
configured to use vb2_dma_sg_memops, userspace might provide DMABUF or
USERPTR buffers mapped into multiple non-contiguous IOVA segments. Just
extracting the first segment's IOVA without checking contiguousness
(sg_nents == 1) might cause the hardware to write past the first segment into
unmapped memory.
> + if (!buff->plns[pln].dma)
> + return -EINVAL;
[ ... ]
> +static int jpeg_apply_we_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
> + struct vb2_buffer *vb)
> +{
> + struct qcom_jenc_dev *jenc = ectx->jenc;
> + const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
> + struct qcom_jpeg_buff *frame = &q->buff[vb->index];
> + void *mptr = vb2_plane_vaddr(vb, 0);
> + dma_addr_t dma = frame->plns[0].dma;
> + unsigned long flags;
> + int rc;
> + u8 pln = 0;
> +
> + if (WARN_ON_ONCE(!dma))
> + return -EPERM;
> +
> + if (WARN_ON_ONCE(!mptr))
> + return -EPERM;
> +
> + rc = jpeg_ensure_header_cache(ectx);
> + if (rc)
> + return rc;
> +
> + /*
> + * Under quality_mutex: force a DQT refresh if the header was just
> + * (re)created (quality_programmed == 0) or if quality changed since
> + * the last frame. Both the cache update and the HW DMI upload are
> + * done here so that hdr_cache and the hardware are always in sync
> + * before jpeg_exec() fires.
> + */
> + mutex_lock(&ectx->quality_mutex);
> + if (!ectx->hdr_cache.size || ectx->quality_programmed != ectx->quality_requested) {
> + jpeg_update_dqt_cache(ectx);
> + jpeg_upload_dmi_table(ectx);
> + }
> + mutex_unlock(&ectx->quality_mutex);
> +
> + /*
> + * Invalidate stale CPU cache lines before writing the JPEG header
> + * with the CPU into the destination buffer.
> + */
> + jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, false);
[Severity: High]
Does this violate the DMA API rules? The capture buffers are typically
mapped as DMA_FROM_DEVICE. Syncing them with DMA_BIDIRECTIONAL when the
mapped direction differs can trigger debug warnings or coherency issues.
> +
> + dma += qcom_jenc_header_emit(&ectx->hdr_cache, mptr,
> + min_t(size_t, vb->planes[0].length, ectx->hdr_cache.size),
> + q->vf.width, q->vf.height);
[Severity: High]
Can this advancement cause a DMA buffer overflow? The hardware limit (bsize)
is configured to the full sizeimage in jpeg_setup_we_size(). If the base
address is shifted forward by the header size here, the hardware will attempt
to write sizeimage bytes starting from this shifted address, running past the
end of the allocated buffer.
> + qcom_jenc_dqts_emit(&ectx->hdr_cache, mptr);
[ ... ]
> +static irqreturn_t op_jpeg_irq_bot(int irq, void *data)
> +{
> + struct qcom_jenc_dev *jenc = data;
> + const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
> + u32 irq_status;
> + u32 irq_mask;
> + unsigned long flags;
> +
> + irq_status = READ_ONCE(jenc->pending_irq_status);
> +
> + irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_SESSION_DONE];
> + if (jpeg_bits_get(irq_mask, irq_status)) {
> + struct jenc_context *ctx = jenc->actx;
[Severity: High]
Is there a possibility that jenc->actx points to a freed context here?
Active buffers are forcefully removed and the context can be freed during
an abort (see jpeg_v4l2_work_stop and fop_jpeg_file_release) without halting
the hardware. When the interrupt eventually fires, this dereference could
result in a use-after-free.
> + struct qcom_jenc_queue *dq;
> + size_t out_size;
> +
> + spin_lock_irqsave(&jenc->hw_lock, flags);
> + jenc->actx = NULL;
> + spin_unlock_irqrestore(&jenc->hw_lock, flags);
> +
> + if (!ctx)
> + return IRQ_HANDLED;
> +
> + dq = &ctx->bufq[JENC_DST_QUEUE];
> + if (dq->buff_id >= 0) {
> + struct qcom_jpeg_buff *frame;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&jenc->hw_lock, flags);
> + frame = &dq->buff[dq->buff_id];
> + out_size = jpeg_io_read(jenc, offs->enc_out_size);
> + spin_unlock_irqrestore(&jenc->hw_lock, flags);
> +
> + dev_dbg(jenc->dev, "complete idx:%d addr=0x%llx size=%zu\n",
> + dq->buff_id, frame->plns[0].dma, out_size);
> +
> + jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_DONE,
> + out_size + JPEG_HEADER_MAX);
[Severity: Medium]
Does adding the fixed JPEG_HEADER_MAX (1024) to out_size accurately reflect
the payload? The actual header emitted via qcom_jenc_header_emit() is
usually smaller. Reporting an inflated size can leak residual uninitialized
memory contents to userspace.
> + jpeg_stop(jenc);
[Severity: High]
Could this jpeg_stop() call inadvertently stop a completely new job?
The callback jenc->enc_hw_irq_cb() schedules work that can queue and start
the next job asynchronously. Because this jpeg_stop() is called after the
callback, it could end up cancelling the newly submitted hardware operation
instead of the current one.
> + }
> + }
> +
> + irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_SESSION_ERROR];
[ ... ]
> +static irqreturn_t op_jpeg_irq_top(int irq, void *data)
> +{
> + struct qcom_jenc_dev *jenc = data;
> + const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
> + u32 irq_status;
> + u32 irq_mask;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&jenc->hw_lock, flags);
> +
> + irq_status = jpeg_io_read(jenc, offs->int_status);
[Severity: High]
Is it safe to unconditionally access hardware registers in the interrupt
handler? If a spurious interrupt arrives while the device is in runtime
suspend, its interconnects or clocks might be off, which could trigger a
synchronous external abort. A check using pm_runtime_get_if_active() is
usually required here before reading.
> + jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, irq_status);
[ ... ]
> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
[ ... ]
> +static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
> +{
> + struct vb2_v4l2_buffer *vb;
> +
> + while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
> + if (!v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {
> + if (ctx->is_stopping) {
> + ctx->is_stopping = false;
> + vb2_set_plane_payload(&vb->vb2_buf, 0, 0);
> + }
> + v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vb);
> + } else {
> + v4l2_m2m_buf_done(vb, buff_state);
> + }
> + }
> +
> + while ((vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
> + v4l2_m2m_buf_done(vb, buff_state);
[Severity: High]
Does draining these buffers while the hardware is potentially still active
lead to issues? During an abort or streamoff, active buffers are immediately
returned to userspace via v4l2_m2m_buf_done(), but the driver never issues a
STOP command to halt the hardware DMA. This can cause the hardware to write
to buffers that userspace considers free.
> +
> + schedule_work(&ctx->finish_work);
> +}
[ ... ]
> +static void bop_jpeg_vb2_stop_streaming(struct vb2_queue *q)
> +{
> + struct jenc_context *ectx = vb2_get_drv_priv(q);
> + struct qcom_jenc_dev *jenc = ectx->jenc;
> + const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
> +
> + mutex_lock(&jenc->dev_mutex);
[Severity: High]
Can this cause a self-deadlock? The V4L2 core invokes the stop_streaming
callback while already holding the lock assigned to jenc->vdev->lock, which
in this driver is set to &jenc->dev_mutex. Attempting to acquire the same
mutex here will hang.
> +
> + jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
> +
> + hw->hw_release(ectx, q);
> +
> + mutex_unlock(&jenc->dev_mutex);
> +}
[ ... ]
> +static int iop_jpeg_encoder_command(struct file *file, void *priv, struct v4l2_encoder_cmd *ec)
> +{
> + struct jenc_context *ectx = jpeg_file2ctx(file);
> + struct vb2_queue *vq;
> + int rc;
> +
> + if (ec->cmd == V4L2_ENC_CMD_STOP) {
> + vq = v4l2_m2m_get_src_vq(ectx->fh.m2m_ctx);
> + if (!vb2_is_streaming(vq))
> + return 0;
> +
> + vq = v4l2_m2m_get_dst_vq(ectx->fh.m2m_ctx);
> + if (!vb2_is_streaming(vq))
> + return 0;
> +
> + rc = v4l2_m2m_ioctl_encoder_cmd(file, priv, ec);
> + if (rc)
> + return rc;
> +
> + ectx->is_stopping = true;
> + schedule_work(&ectx->stop_work);
[Severity: Medium]
Should the V4L2_ENC_CMD_STOP command allow currently queued source buffers
to finish encoding instead of immediately aborting them? The V4L2 spec states
that a stop command on an encoder should encode the remaining buffers and set
the LAST flag on the final capture buffer. Scheduling stop_work abruptly drops
all queued data.
> +
> + return 0;
> + }
> +
> + return v4l2_m2m_ioctl_encoder_cmd(file, priv, ec);
> +}
[ ... ]
> +static int jpeg_v4l2_init_queue(void *priv, struct vb2_queue *sq, struct vb2_queue *dq)
> +{
> + struct jenc_context *ectx = priv;
> + int rc;
> +
> + sq->drv_priv = ectx;
> + sq->dev = ectx->dev;
> + sq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> + sq->io_modes = VB2_MMAP | VB2_DMABUF | VB2_USERPTR;
> + sq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
> + sq->ops = &qcom_jpeg_v4l2_vb2_ops;
> + sq->mem_ops = &vb2_dma_sg_memops;
[Severity: High]
Is it correct to use sg_memops instead of contig_memops? If the hardware
is programmed with a single DMA base address per plane, it likely expects a
contiguous physical memory range. Using sg_memops allows fragmented
scatterlists which the hardware won't be able to process linearly.
> + sq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
[ ... ]
> +static int fop_jpeg_file_open(struct file *file)
> +{
> + struct video_device *vdev = video_devdata(file);
> + struct qcom_jenc_dev *jenc = video_drvdata(file);
> + struct jenc_context *ectx;
> + int rc;
> +
> + ectx = kzalloc_obj(*ectx, GFP_KERNEL);
> + if (!ectx)
> + return -ENOMEM;
> +
> + ectx->dev = jenc->dev;
> + ectx->jenc = jenc;
[ ... ]
> + ectx->quality_ctl = v4l2_ctrl_new_std(&ectx->ctrl_hdl,
> + &qcom_jpeg_v4l2_ctrl_ops,
> + V4L2_CID_JPEG_COMPRESSION_QUALITY,
> + QCOM_JPEG_QUALITY_MIN,
> + QCOM_JPEG_QUALITY_MAX,
> + QCOM_JPEG_QUALITY_UNT,
> + QCOM_JPEG_QUALITY_DEF);
> + if (ectx->ctrl_hdl.error) {
> + rc = ectx->ctrl_hdl.error;
> + goto err_fh_exit;
[Severity: Medium]
Will jumping to err_fh_exit leak the V4L2 control handler memory? The
v4l2_ctrl_handler_free() call is placed at the err_ctrl_handler_free label,
which gets skipped entirely if we jump to err_fh_exit.
> + }
> +
> + ectx->fh.ctrl_handler = &ectx->ctrl_hdl;
> +
> + rc = v4l2_ctrl_handler_setup(&ectx->ctrl_hdl);
[ ... ]
> +static int fop_jpeg_file_release(struct file *file)
> +{
> + struct jenc_context *ectx = jpeg_file2ctx(file);
> + struct v4l2_m2m_dev *m2m_dev = ectx->fh.m2m_ctx->m2m_dev;
> +
> + cancel_work_sync(&ectx->stop_work);
> + cancel_work_sync(&ectx->finish_work);
> +
> + v4l2_m2m_ctx_release(ectx->fh.m2m_ctx);
[Severity: High]
Does v4l2_m2m_ctx_release() re-schedule finish_work after it was just
cancelled? Internally, it aborts jobs which invokes jpeg_v4l2_work_stop().
That function unconditionally calls schedule_work(&ctx->finish_work). Because
the cancellation barriers were executed first, the work item will run after
ectx is freed below, causing a use-after-free.
> + v4l2_m2m_put(m2m_dev);
> + v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
> + v4l2_fh_del(&ectx->fh, file);
> + v4l2_fh_exit(&ectx->fh);
> + kfree(ectx);
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding
2026-06-12 19:44 ` [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-06-12 19:57 ` sashiko-bot
@ 2026-06-12 20:42 ` Frank Li
1 sibling, 0 replies; 10+ messages in thread
From: Frank Li @ 2026-06-12 20:42 UTC (permalink / raw)
To: Atanas Filipov
Cc: linux-media, mchehab, bod, robh, krzk+dt, conor+dt, andersson,
konradybcio, linux-arm-msm, devicetree, linux-kernel
On Fri, Jun 12, 2026 at 10:44:14PM +0300, Atanas Filipov wrote:
> Add device-tree binding for the standalone Qualcomm JPEG encoder
> hardware block (separate from CAMSS media pipelines).
>
> Document required resources briefly and scope initial support to
> currently used compatibles in this series, including SM8250,
> QCM6490, and SM8550 class platforms.
>
> Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
> ---
> .../bindings/media/qcom,jpeg-encoder.yaml | 142 ++++++++++++++++++
> 1 file changed, 142 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
>
> diff --git a/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> new file mode 100644
> index 000000000000..83f19719a869
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> @@ -0,0 +1,142 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/qcom,jpeg-encoder.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm JPEG Encoder
> +
> +maintainers:
> + - Azam Sadiq Pasha Kapatrala Syed <akapatra@quicinc.com>
> + - Hariram Purushothaman <hariramp@quicinc.com>
> +
> +description: |
Needn't |
> + Qualcomm JPEG Encoder is the JPEG encode hardware present in Qualcomm SoCs.
> +
> + The JPEG hardware IP version 4.2.0 is shared across SM8250, QCM6490, SM8550
> + and related derivatives. Platforms based on the SM8550 register layout and
> + interrupt wiring can reuse the qcom,sm8550-jenc fallback compatible, provided
> + the correct register base address and IOMMU stream IDs are supplied in the
> + DTS node.
> +
> +properties:
> + compatible:
> + oneOf:
> + - items:
> + - const: qcom,sm8250-jenc
> + - items:
> + - enum:
> + - qcom,qcm6490-jenc
> + - const: qcom,sm8550-jenc
> + - items:
> + - const: qcom,sm8550-jenc
> +
> + cell-index:
> + description: Legacy JPEG HW instance index.
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + reg:
> + maxItems: 1
> +
> + reg-names:
> + oneOf:
> + - items:
> + - const: jpeg
> + - items:
> + - const: jpeg-regs
> + - items:
> + - const: jpeg_regs
use enum
If reg only have 1, needn't reg-names.
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + minItems: 6
> + maxItems: 6
> +
> + clock-names:
> + oneOf:
> + - items:
> + - const: gcc_hf_axi
> + - const: gcc_sf_axi
> + - const: core_ahb
> + - const: cpas_ahb
> + - const: camnoc_axi
> + - const: jpeg
> + - items:
> + - const: gcc_hf_axi_clk
> + - const: gcc_sf_axi_clk
> + - const: core_ahb_clk
> + - const: cpas_ahb_clk
> + - const: camnoc_axi_clk
> + - const: jpeg_clk
Needn't _clk
after remove _clk, both is the same.
> +
> + power-domains:
> + maxItems: 1
> +
> + iommus:
> + maxItems: 2
> +
> + interconnects:
> + maxItems: 4
> +
> + interconnect-names:
> + items:
> + - const: cam_ahb
> + - const: cam_hf_0_mnoc
> + - const: cam_sf_0_mnoc
> + - const: cam_sf_icp_mnoc
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - interrupts
> + - power-domains
> + - iommus
iommus need be listed in top property
iommus:
maxItems: 2
Frank
> + - interconnects
> + - interconnect-names
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + jpeg-encoder@ac53000 {
> + compatible = "qcom,sm8250-jenc";
> + reg = <0xac53000 0x1000>;
> + reg-names = "jpeg";
> +
> + interrupts = <0 474 4>;
> + power-domains = <&camcc 0>;
> +
> + clock-names =
> + "gcc_hf_axi",
> + "gcc_sf_axi",
> + "core_ahb",
> + "cpas_ahb",
> + "camnoc_axi",
> + "jpeg";
> +
> + clocks =
> + <&gcc 0>,
> + <&gcc 1>,
> + <&camcc 0>,
> + <&camcc 1>,
> + <&camcc 2>,
> + <&camcc 3>;
> +
> + iommus = <&apps_smmu 0x2040 0x400>,
> + <&apps_smmu 0x2440 0x400>;
> +
> + interconnects =
> + <&gem_noc 0 0 &config_noc 8 0>,
> + <&mmss_noc 0 0 &mc_virt 0 0>,
> + <&mmss_noc 1 0 &mc_virt 0 0>,
> + <&mmss_noc 2 0 &mc_virt 0 0>;
> +
> + interconnect-names =
> + "cam_ahb",
> + "cam_hf_0_mnoc",
> + "cam_sf_0_mnoc",
> + "cam_sf_icp_mnoc";
> + };
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
2026-06-12 19:44 ` [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
2026-06-12 20:23 ` sashiko-bot
@ 2026-06-12 20:53 ` Frank Li
1 sibling, 0 replies; 10+ messages in thread
From: Frank Li @ 2026-06-12 20:53 UTC (permalink / raw)
To: Atanas Filipov
Cc: linux-media, mchehab, bod, robh, krzk+dt, conor+dt, andersson,
konradybcio, linux-arm-msm, devicetree, linux-kernel
On Fri, Jun 12, 2026 at 10:44:17PM +0300, Atanas Filipov wrote:
> Add a Qualcomm JPEG encoder driver implemented on top of the
> V4L2 mem2mem framework.
>
> The driver wires vb2 queue handling, format negotiation, JPEG header
> handling, interrupt-driven job completion, and runtime PM/clock/ICC
> integration for the standalone JPEG encode hardware block.
>
> Initial support in this series targets SM8250, QCM6490, and SM8550
> class platforms.
>
> Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
> ---
...
> +
> +static __maybe_unused int qcom_jpeg_pm_suspend(struct device *dev)
new API needn't __maybe_unused, check below
> +{
> + struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
> +
> + qcom_jpeg_clk_off(jenc);
> +
> + qcom_jpeg_icc_off(jenc);
> +
> + return 0;
> +}
> +
...
> +static const struct dev_pm_ops qcom_jpeg_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(qcom_jpeg_suspend, qcom_jpeg_resume)
> + SET_RUNTIME_PM_OPS(qcom_jpeg_pm_suspend, qcom_jpeg_pm_resume, NULL)
use new macro RUNTIME_PM_OPS
> +};
> +
> +static int qcom_jpeg_probe(struct platform_device *pdev)
> +{
> + const struct qcom_dev_resources *res;
> + struct qcom_jenc_dev *jenc;
> + int rc;
> +
> + jenc = devm_kzalloc(&pdev->dev, sizeof(*jenc), GFP_KERNEL);
> + if (!jenc)
> + return -ENOMEM;
> +
> + jenc->dev = &pdev->dev;
> + mutex_init(&jenc->dev_mutex);
devm_mutex_init();
> + spin_lock_init(&jenc->hw_lock);
> + init_completion(&jenc->reset_complete);
> + init_completion(&jenc->stop_complete);
> +
> + res = device_get_match_data(jenc->dev);
> + if (!res)
> + return dev_err_probe(jenc->dev, -ENODEV, "unsupported SoC\n");
> + jenc->res = res;
> +
> + if (!jenc->res->hw_offs || !jenc->res->hw_ops)
> + return dev_err_probe(jenc->dev, -EINVAL, "missing hw resources\n");
> +
> + rc = dma_set_mask_and_coherent(jenc->dev, DMA_BIT_MASK(32));
when mask >= 32, needn't check return value.
> + if (rc)
> + return dev_err_probe(jenc->dev, rc, "failed to set DMA mask\n");
> +
> + jenc->jpeg_base = devm_platform_ioremap_resource_byname(pdev, "jpeg");
> + if (IS_ERR(jenc->jpeg_base))
> + return dev_err_probe(jenc->dev, PTR_ERR(jenc->jpeg_base),
> + "failed to map JPEG resource\n");
> +
> + rc = qcom_jpeg_clk_init(jenc);
> + if (rc)
> + return rc;
> +
> + jenc->irq = platform_get_irq(pdev, 0);
> + if (jenc->irq < 0)
> + return dev_err_probe(jenc->dev, jenc->irq, "failed to get IRQ\n");
> +
> + rc = devm_request_threaded_irq(jenc->dev, jenc->irq,
> + jenc->res->hw_ops->hw_irq_top,
> + jenc->res->hw_ops->hw_irq_bot,
> + IRQF_ONESHOT, dev_name(jenc->dev), jenc);
> + if (rc)
> + return dev_err_probe(jenc->dev, rc, "failed to request IRQ\n");
> +
> + rc = qcom_jpeg_icc_init(jenc);
> + if (rc)
> + return rc;
> +
> + rc = v4l2_device_register(jenc->dev, &jenc->v4l2_dev);
> + if (rc) {
> + dev_err(jenc->dev, "failed to register V4L2 device\n");
> + return rc;
> + }
> +
> + jenc->perf = QCOM_SOC_PERF_NOMINAL;
> +
> + rc = qcom_jpeg_v4l2_register(jenc);
> + if (rc) {
> + dev_err(jenc->dev, "failed to register video device\n");
> + goto err_v4l2_device_unregister;
> + }
> +
> + rc = devm_pm_runtime_enable(jenc->dev);
> + if (rc)
> + goto err_v4l2_unregister;
> +
> + dev_dbg(jenc->dev, "Qualcomm JPEG encoder registered\n");
> +
> + platform_set_drvdata(pdev, jenc);
It's better set at early phrase, include some async function use it.
> +
> + return 0;
> +
> +err_v4l2_unregister:
> + qcom_jpeg_v4l2_unregister(jenc);
> +err_v4l2_device_unregister:
> + v4l2_device_unregister(&jenc->v4l2_dev);
> + return rc;
> +}
> +
> +static void qcom_jpeg_remove(struct platform_device *pdev)
> +{
> + struct qcom_jenc_dev *jenc = platform_get_drvdata(pdev);
> +
> + qcom_jpeg_v4l2_unregister(jenc);
> +
> + v4l2_device_unregister(&jenc->v4l2_dev);
> +
> + dev_dbg(jenc->dev, "Qualcomm JPEG encoder deregistered\n");
> +}
> +
> +static const struct of_device_id qcom_jpeg_of_match[] = {
> + {
> + .compatible = "qcom,sm8250-jenc",
> + .data = &qcom_t165_t480_jpeg_drvdata
> + },
> + {
> + .compatible = "qcom,qcm6490-jenc",
> + .data = &qcom_t680_jpeg_drvdata
> + },
> + {
> + .compatible = "qcom,sm8550-jenc",
> + .data = &qcom_t780_jpeg_drvdata
> + },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, qcom_jpeg_of_match);
> +
> +static struct platform_driver qcom_jpeg_platform_driver = {
> + .probe = qcom_jpeg_probe,
> + .remove = qcom_jpeg_remove,
> + .driver = {
> + .name = QCOM_JPEG_ENC_NAME,
> + .of_match_table = qcom_jpeg_of_match,
> + .pm = &qcom_jpeg_pm_ops,
pm_ptr(&qcom_jpeg_pm_ops)
Frank
> + },
> +};
> +
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-06-12 20:54 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-12 19:44 [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-06-12 19:57 ` sashiko-bot
2026-06-12 20:42 ` Frank Li
2026-06-12 19:44 ` [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node Atanas Filipov
2026-06-12 20:06 ` sashiko-bot
2026-06-12 19:44 ` [PATCH v1 3/4] arm64: dts: qcom: sm8250: " Atanas Filipov
2026-06-12 19:44 ` [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
2026-06-12 20:23 ` sashiko-bot
2026-06-12 20:53 ` Frank Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox