* [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280
@ 2026-11-06 8:25 Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
` (7 more replies)
0 siblings, 8 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
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.
The JPEG encoder is a standalone hardware IP within the camera
subsystem. It is modelled as a peer node of CAMSS directly under the
SoC bus, with all resources self-described in its own device tree node
(power-domains, clocks, interconnects, iommus). This follows the
consensus reached in the v4 review thread (Bryan O'Donoghue, Konrad
Dybcio, Gjorgji Rosikopulos).
Supported compatibles:
- qcom,sm8250-jpeg-enc
- qcom,sc7280-jpeg-enc
Driver scope and design choices:
- uses the standard V4L2 mem2mem and vb2 framework
- the JPEG header is written by a common v4l2-jpeg helper
- all resources come from DT (clocks, interconnects, iommu,
power-domains)
- power is managed with power-domains (SM8250: TITAN_TOP_GDSC, MX,
MMCX; SC7280: TITAN_TOP_GDSC, CX) and the OPP framework
- the driver selects the core clock for each job from the image size;
the OPP table votes the SoC rails and the DDR bandwidth
Note on the camera NoC AXI clock (camnoc_axi): several camera blocks
share this clock. For now there is no upstream driver that aggregates
their votes, so the driver does this: while the encoder is powered, it
sets a minimum rate on the camera NoC AXI clock source with
clk_set_rate_range(). The minimum is 300 MHz on SM8250 and 150 MHz on
SC7280, the lowest levels. Another user of the clock cannot set a lower
rate. The series does not depend on the camcc icc-clk series.
Functional validation:
- tested with v4l2-ctl and GStreamer (v4l2jpegenc) on RB5 (SM8250)
and RB3 Gen2 (QCS6490)
- v4l2-compliance passes
- throughput and DDR traffic measured for each OPP on both boards; the
OPP bandwidth values and the reference throughput come from these
measurements
Known limitations:
- no scaling
- width and height must be multiples of 16
- the camera NoC AXI clock has a fixed minimum rate; its bandwidth is
not aggregated with the other camera blocks (see the note above)
Changes since v6:
Series:
- Added SC7280 (Kodiak) support and RB3 Gen2.
- Split the driver patch into media/jpeg.h constants, a common
v4l2-jpeg header writer and the driver. (Dmitry)
- The SoC nodes are disabled by default and enabled per board (new
qrb5165-rb5 and qcs6490-rb3gen2 patches).
- No dependency on the camcc icc-clk series any more.
Binding (dt-bindings: media: qcom: Add JPEG encoder binding):
- Added qcom,sc7280-jpeg-enc (titan_top and cx power domains).
- New clock names: core, bus, config, camnoc_axi. (Dmitry)
- Added the camnoc_axi_src clock.
- SM8250 power domains: titan_top, mx, mmcx. (Dmitry)
media/jpeg.h (media: jpeg: Add JPEG baseline and JFIF APP0 constants):
- New patch with the standard JPEG/JFIF values. (Dmitry)
v4l2-jpeg (media: v4l2-jpeg: Add a baseline JPEG header writer):
- New patch with a common header writer; the qcom JFIF header code is
removed from the driver. (Dmitry)
Driver (media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder):
- Added SC7280 support.
- Power domains are attached with devm_pm_domain_attach_list(); the
OPP table votes the SoC rails with required-opps. (Dmitry)
- Fixed the clock/OPP order in runtime PM. (Dmitry)
- Minimum camera NoC AXI rate with clk_set_rate_range().
- The core clock is selected from a measured reference throughput.
- GENMASK()/BIT() with FIELD_GET()/FIELD_PREP(). (Dmitry)
- JFIF tables from the common V4L2 JPEG helpers. (Dmitry)
- qcom_jenc_v420_* names; removed the qcom_jpeg_hw_ops table.
(Dmitry)
- Removed the custom performance controls. G/S_PARM is not used
either; the driver selects the clock and bandwidth itself.
(Dmitry, Gjorgji)
DTS (arm64: dts: qcom: sm8250, kodiak):
- required-opps on every OPP for the power domains. (Dmitry)
- Interconnect on the mmss_noc camnoc SF path to EBI; added the
camnoc_axi_src clock.
- OPP bandwidth values from measured DDR traffic.
- New kodiak node for SC7280.
Changes since v5:
Binding (dt-bindings: media: qcom,sm8250-jpeg-enc):
- Removed GCC_CAMERA_HF_AXI_CLK (hf_axi) clock and hf-mnoc interconnect
path. The JPEG encoder uses only the SF CAMNOC port; the HF port is for
real-time capture pipelines. (Loic Poulain)
- Added RPMHPD_MMCX and RPMHPD_MX to power-domains. (bod)
- Added power-domain-names property with entries "titan_top", "mmcx", "mx".
- Added power-domain-names to the required list.
DTS (arm64: dts: qcom: sm8250):
- Removed GCC_CAMERA_HF_AXI_CLK (hf_axi) clock and hf-mnoc interconnect
path from jpeg-encoder node. (Loic Poulain)
- Added RPMHPD_MMCX and RPMHPD_MX to power-domains. (bod)
- Added power-domain-names = "titan_top", "mmcx", "mx".
Changes since v4:
Architecture:
- Dropped patch 1/5 (devm_of_platform_populate in camss.c) entirely.
JPEG is now a peer node of CAMSS, not a child node. (bod)
- Dropped patch 2/5 (camss binding unevaluatedProperties). (krzk, bod)
- Moved jpeg-encoder DTS node from camss@ to soc@ level. (bod)
Binding (dt-bindings: media: qcom,sm8250-jpeg-enc):
- Renamed binding file from qcom,jpeg-encoder.yaml to
qcom,sm8250-jpeg-enc.yaml to match compatible string convention.
- Renamed compatible from qcom,sm8250-jenc to qcom,sm8250-jpeg-enc
for readability. (Bjorn Andersson)
- Added power-domains property and required entry. (bod)
- Added iommus description with SID value 0x2040 for SM8250. (bod)
- Dropped soc{} wrapper from example; node is directly under soc. (Dmitry)
- Added Gjorgji Rosikopulos as co-maintainer.
- Patch order corrected: binding before driver before DTS. (krzk)
Driver (media: qcom: jpeg):
- Removed <linux/mod_devicetable.h> include. (Uwe Kleine-Koenig)
- Removed dev_dbg calls from OPP clock selection path. (Dmitry)
- Removed camss_dev field and pm_runtime_resume_and_get(camss_dev);
PM is now managed independently via power-domains. (Dmitry)
- Removed incorrect device_link_add() to parent; not needed in peer
node model.
- Moved file-local defines out of qcom_jenc_defs.h to their respective
.c files. (Dmitry)
- Fixed dev_pm_opp_put() placement: called before rc check. (Dmitry)
- Replaced _DEFINE_DEV_PM_OPS() with SYSTEM_SLEEP_PM_OPS() +
RUNTIME_PM_OPS() macros, consistent with other media drivers. (Frank Li)
- Fixed Kconfig source order to alphabetical. (krzk)
- Updated compatible string in driver match table to qcom,sm8250-jpeg-enc.
DTS (arm64: dts: qcom: sm8250):
- Added status = "disabled" to jpeg-encoder node.
- Used 0x0 instead of bare 0 for zero cells in reg property. (Dmitry)
- Dropped opp-peak-kBps and opp-avg-kBps from OPP table; no upstream
aggregation mechanism available yet.
Changes since v3:
CAMSS binding (dt-bindings: media: qcom,sm8250-camss):
- Added as new patch 2/5; allows #address-cells, #size-cells, ranges,
and child nodes using unevaluatedProperties: false.
Binding (dt-bindings: media: qcom,jpeg-encoder):
- Dropped soc and camss wrapper nodes from example. (Dmitry)
- Removed opp-level from all OPP table entries. (Dmitry)
CAMSS (media: qcom: camss):
- Replaced patch 1/4 with Bryan O'Donoghue's original upstream commit.
Driver (media: qcom: jpeg):
- Cleaned up driver patch commit. (bod)
DTS (arm64: dts: qcom: sm8250):
- Restored camss interconnect-names to original values.
Changes since v2:
Binding (dt-bindings: media: qcom,jpeg-encoder):
- IOMMU stream IDs documented in binding description. (bod)
- Dropped "Properties documented:" section from commit message. (krzk)
- Dropped clocks minItems constraint. (krzk)
- Renamed clock-names to match hardware signal names. (krzk)
- Renamed interconnect-names to reflect path topology. (krzk, Dmitry)
DTS (arm64: dts: qcom: sm8250):
- Removed cell-index; use lowercase hex. (Dmitry)
- Moved jpeg-encoder node into the camss block as a child node. (bod)
Driver (media: qcom: jpeg):
- Sort obj-y entries alphabetically in qcom/Makefile. (krzk)
- Use devm_mutex_init() instead of mutex_init(). (Frank Li)
Changes since v1:
Binding (dt-bindings: media: qcom,jpeg-encoder):
- Removed qcm6490 and sm8550 compatible strings; SM8250 only.
- Fixed clock-names block in the binding example.
- Dropped reg-names: with a single reg entry it is unnecessary.
- Removed description literal block scalar (|).
- Dropped _clk suffix from clock-names.
- Added iommus description in the properties section.
Driver (media: qcom: jpeg):
- Migrated to devm_clk_bulk_get_all().
- Migrated to the OPP framework for clock rate management.
- Switched to RUNTIME_PM_OPS() and SYSTEM_SLEEP_PM_OPS() macros.
- platform_set_drvdata() moved to immediately after allocation.
- .pm uses pm_ptr().
Atanas Filipov (8):
dt-bindings: media: qcom: Add JPEG encoder binding
media: jpeg: Add JPEG baseline and JFIF APP0 constants
media: v4l2-jpeg: Add a baseline JPEG header writer
media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
arm64: dts: qcom: sm8250: Add JPEG encoder node
arm64: dts: qcom: qrb5165-rb5: Enable JPEG encoder
arm64: dts: qcom: kodiak: Add JPEG encoder node
arm64: dts: qcom: qcs6490-rb3gen2: Enable JPEG encoder
.../bindings/media/qcom,sm8250-jpeg-enc.yaml | 193 +++
arch/arm64/boot/dts/qcom/kodiak.dtsi | 69 +
arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 4 +
arch/arm64/boot/dts/qcom/qrb5165-rb5.dts | 4 +
arch/arm64/boot/dts/qcom/sm8250.dtsi | 74 +
drivers/media/platform/qcom/Kconfig | 1 +
drivers/media/platform/qcom/Makefile | 1 +
drivers/media/platform/qcom/jpeg/Kconfig | 15 +
drivers/media/platform/qcom/jpeg/Makefile | 7 +
.../media/platform/qcom/jpeg/qcom_jenc_dev.c | 375 ++++
.../media/platform/qcom/jpeg/qcom_jenc_dev.h | 122 ++
.../media/platform/qcom/jpeg/qcom_jenc_v420.c | 1527 +++++++++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_v420.h | 43 +
.../qcom/jpeg/qcom_jenc_v420_hw_info.h | 215 +++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1176 +++++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.h | 13 +
drivers/media/v4l2-core/v4l2-jpeg.c | 165 ++
include/media/jpeg.h | 79 +
include/media/v4l2-jpeg.h | 31 +
19 files changed, 4114 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.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_dev.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420.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: ddfe51b0a0f5dc9e415cc2c8ad8db17532dd426b
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 13:31 ` Rob Herring
2026-11-06 8:25 ` [PATCH v7 2/8] media: jpeg: Add JPEG baseline and JFIF APP0 constants Atanas Filipov
` (6 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add device-tree binding for the Qualcomm JPEG encoder hardware block
present in SM8250 (Kona) and SC7280 (Kodiak) SoCs.
The JPEG encoder is a standalone hardware IP within the camera subsystem
that performs JPEG compression in memory-to-memory fashion. It is
separate from the CAMSS ISP pipeline and has its own register space,
interrupt, clocks, power domains, IOMMU stream, and interconnect path.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
.../bindings/media/qcom,sm8250-jpeg-enc.yaml | 193 ++++++++++++++++++
1 file changed, 193 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml
diff --git a/Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml b/Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml
new file mode 100644
index 0000000000000..86b17575600c5
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml
@@ -0,0 +1,193 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/qcom,sm8250-jpeg-enc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm JPEG Encoder
+
+maintainers:
+ - Atanas Filipov <atanas.filipov@oss.qualcomm.com>
+
+description:
+ Qualcomm JPEG Encoder is the JPEG encode hardware present in Qualcomm SoCs.
+ The encoder is a standalone hardware IP within the camera subsystem that
+ performs JPEG compression in memory-to-memory fashion.
+
+properties:
+ compatible:
+ enum:
+ - qcom,sc7280-jpeg-enc
+ - qcom,sm8250-jpeg-enc
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 6
+
+ clock-names:
+ items:
+ - const: bus
+ - const: config
+ - const: cpas
+ - const: camnoc_axi
+ - const: camnoc_axi_src
+ - const: core
+
+ power-domains:
+ minItems: 2
+ maxItems: 3
+
+ power-domain-names:
+ minItems: 2
+ maxItems: 3
+
+ interconnects:
+ maxItems: 1
+
+ interconnect-names:
+ items:
+ - const: camnoc
+
+ iommus:
+ description:
+ SMMU stream ID for the JPEG hardware.
+ maxItems: 1
+
+ operating-points-v2: true
+
+ opp-table:
+ type: object
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - interrupts
+ - power-domains
+ - power-domain-names
+ - interconnects
+ - interconnect-names
+ - iommus
+ - operating-points-v2
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,sc7280-jpeg-enc
+ then:
+ properties:
+ power-domains:
+ items:
+ - description: Titan top GDSC
+ - description: CX power domain
+ power-domain-names:
+ items:
+ - const: titan_top
+ - const: cx
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,sm8250-jpeg-enc
+ then:
+ properties:
+ power-domains:
+ items:
+ - description: Titan top GDSC
+ - description: MX power domain
+ - description: MMCX power domain
+ power-domain-names:
+ items:
+ - const: titan_top
+ - const: mx
+ - const: mmcx
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,camcc-sm8250.h>
+ #include <dt-bindings/clock/qcom,gcc-sm8250.h>
+ #include <dt-bindings/interconnect/qcom,sm8250.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/power/qcom,rpmhpd.h>
+
+ jpeg-encoder@ac53000 {
+ compatible = "qcom,sm8250-jpeg-enc";
+ reg = <0x0ac53000 0x1000>;
+
+ interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING>;
+
+ power-domains = <&camcc TITAN_TOP_GDSC>,
+ <&rpmhpd RPMHPD_MX>,
+ <&rpmhpd RPMHPD_MMCX>;
+ power-domain-names = "titan_top", "mx", "mmcx";
+
+ clocks = <&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_CAMNOC_AXI_CLK_SRC>,
+ <&camcc CAM_CC_JPEG_CLK>;
+ clock-names = "bus",
+ "config",
+ "cpas",
+ "camnoc_axi",
+ "camnoc_axi_src",
+ "core";
+
+ interconnects = <&mmss_noc MASTER_CAMNOC_SF 0 &mc_virt SLAVE_EBI_CH0 0>;
+ interconnect-names = "camnoc";
+
+ iommus = <&apps_smmu 0x2040 0x400>;
+
+ operating-points-v2 = <&jpeg_opp_table>;
+
+ jpeg_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-100000000 {
+ opp-hz = /bits/ 64 <100000000>;
+ opp-peak-kBps = <262560>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-200000000 {
+ opp-hz = /bits/ 64 <200000000>;
+ opp-peak-kBps = <524760>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_svs>;
+ };
+
+ opp-400000000 {
+ opp-hz = /bits/ 64 <400000000>;
+ opp-peak-kBps = <978600>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_svs_l1>;
+ };
+
+ opp-480000000 {
+ opp-hz = /bits/ 64 <480000000>;
+ opp-peak-kBps = <1095600>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_nom>;
+ };
+
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-peak-kBps = <1095600>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_turbo>;
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 2/8] media: jpeg: Add JPEG baseline and JFIF APP0 constants
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 3/8] media: v4l2-jpeg: Add a baseline JPEG header writer Atanas Filipov
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add the standard field values of a baseline (ITU-T T.81) JFIF header
to the shared JPEG marker header: sample precision, component counts,
sampling factors, quantization table identifiers, SOS table selectors,
spectral selection and successive approximation and the JFIF APP0
payload values. Describe the marker segment header and the SOF0 and
SOS headers and component specifications as packed structs, so their
sizes and field offsets can be taken with sizeof() and offsetof().
Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
include/media/jpeg.h | 79 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/include/media/jpeg.h b/include/media/jpeg.h
index a01e142e99a7d..a93d0a580bbe3 100644
--- a/include/media/jpeg.h
+++ b/include/media/jpeg.h
@@ -3,6 +3,8 @@
#ifndef _MEDIA_JPEG_H_
#define _MEDIA_JPEG_H_
+#include <linux/types.h>
+
/* JPEG markers */
#define JPEG_MARKER_TEM 0x01
#define JPEG_MARKER_SOF0 0xc0
@@ -17,4 +19,81 @@
#define JPEG_MARKER_APP0 0xe0
#define JPEG_MARKER_COM 0xfe
+/* JPEG baseline sample precision (bits per sample) */
+#define JPEG_SAMPLE_PRECISION_BITS 8
+
+/* Number of components: greyscale (1) and colour YCbCr (3) */
+#define JPEG_COMP_MONO 1
+#define JPEG_COMP_COLOR 3
+
+/* Chroma subsampling factors encoded in SOF0 sampling byte */
+#define JPEG_SAMPLING_H1V1 0x11
+#define JPEG_SAMPLING_H2V2 0x22
+
+/* Quantization table destination identifiers */
+#define JPEG_QTABLE_LUMA 0
+#define JPEG_QTABLE_CHROMA 1
+
+/* SOS spectral selection and approximation fields (baseline JPEG) */
+#define JPEG_SPECTRAL_START 0x00
+#define JPEG_SPECTRAL_END 0x3f
+#define JPEG_APPROX_HIGH_LOW 0x00
+
+/* SOS table selector bytes (luma: DC=0/AC=0, chroma: DC=1/AC=1) */
+#define JPEG_TABLE_SEL_LUMA 0x00
+#define JPEG_TABLE_SEL_CHROMA 0x11
+
+/*
+ * JFIF APP0 payload fields, as specified by the JFIF spec referenced from
+ * ITU-T T.81 Annex B. See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+#define JFIF_IDENT_TERM 0x00
+#define JFIF_VERSION_MAJOR 0x01
+#define JFIF_VERSION_MINOR 0x01
+#define JFIF_UNITS_NONE 0x00
+#define JFIF_DENSITY_HI 0x00
+#define JFIF_DENSITY_LO 0x01
+#define JFIF_THUMBNAIL_SIZE 0x00
+
+/* Marker segment header: marker and length (ITU-T T.81 B.1.1.4) */
+struct jpeg_seg_hdr {
+ __be16 marker;
+ __be16 length; /* bytes from the length field to the end of the segment */
+} __packed;
+
+/* SOF0 frame header up to the component specifications (ITU-T T.81 B.2.2) */
+struct jpeg_sof_hdr {
+ struct jpeg_seg_hdr seg;
+ u8 precision;
+ __be16 height;
+ __be16 width;
+ u8 num_components;
+} __packed;
+
+/* SOF0 frame component specification, one per component */
+struct jpeg_sof_comp {
+ u8 id;
+ u8 sampling;
+ u8 qtable;
+} __packed;
+
+/* SOS scan header up to the component specifications (ITU-T T.81 B.2.3) */
+struct jpeg_sos_hdr {
+ struct jpeg_seg_hdr seg;
+ u8 num_components;
+} __packed;
+
+/* SOS scan component specification, one per component */
+struct jpeg_sos_comp {
+ u8 id;
+ u8 tables; /* DC and AC entropy table selectors */
+} __packed;
+
+/* SOS fields after the component specifications */
+struct jpeg_sos_tail {
+ u8 spectral_start;
+ u8 spectral_end;
+ u8 approx;
+} __packed;
+
#endif /* _MEDIA_JPEG_H_ */
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 3/8] media: v4l2-jpeg: Add a baseline JPEG header writer
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 2/8] media: jpeg: Add JPEG baseline and JFIF APP0 constants Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 4/8] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Several V4L2 JPEG encoder drivers build the JPEG header in the driver
from a fixed template and patch the image size and quantization tables
for each frame. Add v4l2_jpeg_write_header() for the baseline case: it
writes SOI, an optional JFIF APP0, one DQT segment per table, SOF0, the
ITU-T T.81 Annex K reference Huffman tables in one DHT segment and SOS,
and returns the offsets of the fields set per frame. Add
v4l2_jpeg_hdr_set_size() to set the image size in such a header.
The field values come from media/jpeg.h, so the driver keeps only the
hardware specifics.
Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
drivers/media/v4l2-core/v4l2-jpeg.c | 165 ++++++++++++++++++++++++++++
include/media/v4l2-jpeg.h | 31 ++++++
2 files changed, 196 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-jpeg.c b/drivers/media/v4l2-core/v4l2-jpeg.c
index 36a0f1a1b0d97..f466dc6a457de 100644
--- a/drivers/media/v4l2-core/v4l2-jpeg.c
+++ b/drivers/media/v4l2-core/v4l2-jpeg.c
@@ -13,7 +13,10 @@
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
#include <linux/types.h>
+#include <media/jpeg.h>
#include <media/v4l2-jpeg.h>
MODULE_DESCRIPTION("V4L2 JPEG header parser helpers");
@@ -711,3 +714,165 @@ int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out)
return marker;
}
EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_header);
+
+/* baseline JPEG header writer */
+
+/* Segment length of a header struct: the marker is not counted */
+#define JPEG_SEG_LEN(hdr) (sizeof(struct hdr) - offsetof(struct jpeg_seg_hdr, length))
+
+struct jpeg_hdr_buf {
+ u8 *buf;
+ size_t len;
+ size_t pos;
+};
+
+/* Writes past the end only advance pos; v4l2_jpeg_write_header() checks it */
+static void jpeg_put_bytes(struct jpeg_hdr_buf *b, const void *src, size_t n)
+{
+ if (b->pos + n <= b->len) {
+ if (src)
+ memcpy(b->buf + b->pos, src, n);
+ else
+ memset(b->buf + b->pos, 0, n);
+ }
+ b->pos += n;
+}
+
+static void jpeg_put_u8(struct jpeg_hdr_buf *b, u8 val)
+{
+ jpeg_put_bytes(b, &val, 1);
+}
+
+static void jpeg_put_be16(struct jpeg_hdr_buf *b, u16 val)
+{
+ u8 data[2] = { val >> 8, val & 0xff };
+
+ jpeg_put_bytes(b, data, sizeof(data));
+}
+
+static void jpeg_put_dht(struct jpeg_hdr_buf *b, u8 class_id, const u8 *table, size_t len)
+{
+ jpeg_put_u8(b, class_id);
+ jpeg_put_bytes(b, table, len);
+}
+
+/**
+ * v4l2_jpeg_write_header - write a baseline JPEG header
+ * @buf: destination buffer
+ * @len: length of the destination buffer
+ * @params: header parameters
+ * @layout: returns the header size and the offsets of the fields set per frame
+ *
+ * Writes SOI, an optional JFIF APP0, one DQT segment per quantization table,
+ * SOF0, one DHT segment with the reference Huffman tables of ITU-T.81 Annex K
+ * and SOS. Width, height and the quantization table values are written as
+ * zero; set them with v4l2_jpeg_hdr_set_size() and through the
+ * @layout->dqt_offset offsets.
+ *
+ * Returns 0 on success, -EINVAL for an unsupported number of components or
+ * -ENOSPC if @buf is too small.
+ */
+int v4l2_jpeg_write_header(void *buf, size_t len, const struct v4l2_jpeg_hdr_params *params,
+ struct v4l2_jpeg_hdr_layout *layout)
+{
+ static const u8 jfif[] = {
+ 'J', 'F', 'I', 'F', JFIF_IDENT_TERM,
+ JFIF_VERSION_MAJOR, JFIF_VERSION_MINOR,
+ JFIF_UNITS_NONE,
+ JFIF_DENSITY_HI, JFIF_DENSITY_LO, /* X density */
+ JFIF_DENSITY_HI, JFIF_DENSITY_LO, /* Y density */
+ JFIF_THUMBNAIL_SIZE, JFIF_THUMBNAIL_SIZE,
+ };
+ struct jpeg_hdr_buf b = { .buf = buf, .len = len };
+ unsigned int nc = params->num_components;
+ unsigned int ntables = nc == JPEG_COMP_MONO ? 1 : 2;
+ unsigned int i;
+
+ if (nc != JPEG_COMP_MONO && nc != JPEG_COMP_COLOR)
+ return -EINVAL;
+
+ memset(layout, 0, sizeof(*layout));
+
+ jpeg_put_be16(&b, SOI);
+
+ if (params->jfif) {
+ jpeg_put_be16(&b, APP0);
+ jpeg_put_be16(&b, sizeof(__be16) + sizeof(jfif));
+ jpeg_put_bytes(&b, jfif, sizeof(jfif));
+ }
+
+ /* one 8-bit table per segment, luma Tq 0, chroma Tq 1 */
+ for (i = 0; i < ntables; i++) {
+ jpeg_put_be16(&b, DQT);
+ /* length field, Pq/Tq byte, table */
+ jpeg_put_be16(&b, sizeof(__be16) + sizeof(u8) + V4L2_JPEG_PIXELS_IN_BLOCK);
+ jpeg_put_u8(&b, i ? JPEG_QTABLE_CHROMA : JPEG_QTABLE_LUMA);
+ layout->dqt_offset[i] = b.pos;
+ jpeg_put_bytes(&b, NULL, V4L2_JPEG_PIXELS_IN_BLOCK);
+ }
+
+ layout->sof_offset = b.pos;
+ jpeg_put_be16(&b, SOF0);
+ jpeg_put_be16(&b, JPEG_SEG_LEN(jpeg_sof_hdr) + nc * sizeof(struct jpeg_sof_comp));
+ jpeg_put_u8(&b, JPEG_SAMPLE_PRECISION_BITS);
+ jpeg_put_be16(&b, 0); /* height */
+ jpeg_put_be16(&b, 0); /* width */
+ jpeg_put_u8(&b, nc);
+ for (i = 0; i < nc; i++) {
+ jpeg_put_u8(&b, i + 1);
+ jpeg_put_u8(&b, i ? JPEG_SAMPLING_H1V1 : params->luma_sampling);
+ jpeg_put_u8(&b, i ? JPEG_QTABLE_CHROMA : JPEG_QTABLE_LUMA);
+ }
+
+ jpeg_put_be16(&b, DHT);
+ /* length field, then per table class a Tc/Th byte and the DC and AC tables */
+ jpeg_put_be16(&b, sizeof(__be16) + ntables * (2 * sizeof(u8) + V4L2_JPEG_REF_HT_DC_LEN +
+ V4L2_JPEG_REF_HT_AC_LEN));
+ jpeg_put_dht(&b, V4L2_JPEG_LUM_HT | V4L2_JPEG_DC_HT, v4l2_jpeg_ref_table_luma_dc_ht,
+ V4L2_JPEG_REF_HT_DC_LEN);
+ jpeg_put_dht(&b, V4L2_JPEG_LUM_HT | V4L2_JPEG_AC_HT, v4l2_jpeg_ref_table_luma_ac_ht,
+ V4L2_JPEG_REF_HT_AC_LEN);
+ if (nc == JPEG_COMP_COLOR) {
+ jpeg_put_dht(&b, V4L2_JPEG_CHR_HT | V4L2_JPEG_DC_HT,
+ v4l2_jpeg_ref_table_chroma_dc_ht, V4L2_JPEG_REF_HT_DC_LEN);
+ jpeg_put_dht(&b, V4L2_JPEG_CHR_HT | V4L2_JPEG_AC_HT,
+ v4l2_jpeg_ref_table_chroma_ac_ht, V4L2_JPEG_REF_HT_AC_LEN);
+ }
+
+ jpeg_put_be16(&b, SOS);
+ jpeg_put_be16(&b, JPEG_SEG_LEN(jpeg_sos_hdr) + nc * sizeof(struct jpeg_sos_comp) +
+ sizeof(struct jpeg_sos_tail));
+ jpeg_put_u8(&b, nc);
+ for (i = 0; i < nc; i++) {
+ jpeg_put_u8(&b, i + 1);
+ jpeg_put_u8(&b, i ? JPEG_TABLE_SEL_CHROMA : JPEG_TABLE_SEL_LUMA);
+ }
+ jpeg_put_u8(&b, JPEG_SPECTRAL_START);
+ jpeg_put_u8(&b, JPEG_SPECTRAL_END);
+ jpeg_put_u8(&b, JPEG_APPROX_HIGH_LOW);
+
+ if (b.pos > len)
+ return -ENOSPC;
+
+ layout->size = b.pos;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_jpeg_write_header);
+
+/**
+ * v4l2_jpeg_hdr_set_size - set the image size in a header from v4l2_jpeg_write_header()
+ * @buf: header buffer
+ * @layout: layout returned by v4l2_jpeg_write_header()
+ * @width: image width
+ * @height: image height
+ */
+void v4l2_jpeg_hdr_set_size(void *buf, const struct v4l2_jpeg_hdr_layout *layout, u16 width,
+ u16 height)
+{
+ u8 *sof = (u8 *)buf + layout->sof_offset;
+
+ put_unaligned_be16(height, sof + offsetof(struct jpeg_sof_hdr, height));
+ put_unaligned_be16(width, sof + offsetof(struct jpeg_sof_hdr, width));
+}
+EXPORT_SYMBOL_GPL(v4l2_jpeg_hdr_set_size);
diff --git a/include/media/v4l2-jpeg.h b/include/media/v4l2-jpeg.h
index 62dda1560275a..d789ccfeba734 100644
--- a/include/media/v4l2-jpeg.h
+++ b/include/media/v4l2-jpeg.h
@@ -167,8 +167,39 @@ struct v4l2_jpeg_header {
enum v4l2_jpeg_app14_tf app14_tf;
};
+/**
+ * struct v4l2_jpeg_hdr_params - baseline JPEG header parameters
+ * @num_components: number of color components, 1 (grey) or 3 (YCbCr)
+ * @luma_sampling: luma horizontal and vertical sampling factors, as in the
+ * SOF0 component specification (0x11, 0x21 or 0x22); chroma uses 0x11
+ * @jfif: emit a JFIF APP0 segment after SOI
+ */
+struct v4l2_jpeg_hdr_params {
+ u8 num_components;
+ u8 luma_sampling;
+ bool jfif;
+};
+
+/**
+ * struct v4l2_jpeg_hdr_layout - offsets of the fields set per frame
+ * @size: header size in bytes
+ * @sof_offset: offset of the SOF0 marker
+ * @dqt_offset: offset of the luma and chroma quantization table values
+ * (V4L2_JPEG_PIXELS_IN_BLOCK bytes in zigzag order), 0 if not present
+ */
+struct v4l2_jpeg_hdr_layout {
+ u32 size;
+ u32 sof_offset;
+ u32 dqt_offset[2];
+};
+
int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out);
+int v4l2_jpeg_write_header(void *buf, size_t len, const struct v4l2_jpeg_hdr_params *params,
+ struct v4l2_jpeg_hdr_layout *layout);
+void v4l2_jpeg_hdr_set_size(void *buf, const struct v4l2_jpeg_hdr_layout *layout, u16 width,
+ u16 height);
+
extern const u8 v4l2_jpeg_zigzag_scan_index[V4L2_JPEG_PIXELS_IN_BLOCK];
extern const u8 v4l2_jpeg_ref_table_luma_qt[V4L2_JPEG_PIXELS_IN_BLOCK];
extern const u8 v4l2_jpeg_ref_table_chroma_qt[V4L2_JPEG_PIXELS_IN_BLOCK];
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 4/8] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
` (2 preceding siblings ...)
2026-11-06 8:25 ` [PATCH v7 3/8] media: v4l2-jpeg: Add a baseline JPEG header writer Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 5/8] arm64: dts: qcom: sm8250: Add JPEG encoder node Atanas Filipov
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
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.
This series targets SM8250 (Kona) and SC7280 (Kodiak) 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 | 15 +
drivers/media/platform/qcom/jpeg/Makefile | 7 +
.../media/platform/qcom/jpeg/qcom_jenc_dev.c | 375 ++++
.../media/platform/qcom/jpeg/qcom_jenc_dev.h | 122 ++
.../media/platform/qcom/jpeg/qcom_jenc_v420.c | 1527 +++++++++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_v420.h | 43 +
.../qcom/jpeg/qcom_jenc_v420_hw_info.h | 215 +++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1176 +++++++++++++
.../media/platform/qcom/jpeg/qcom_jenc_v4l2.h | 13 +
11 files changed, 3495 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_dev.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c
create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420.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 4f4d3a68e6e5f..3efae2bcae7a9 100644
--- a/drivers/media/platform/qcom/Kconfig
+++ b/drivers/media/platform/qcom/Kconfig
@@ -4,4 +4,5 @@ comment "Qualcomm media platform drivers"
source "drivers/media/platform/qcom/camss/Kconfig"
source "drivers/media/platform/qcom/iris/Kconfig"
+source "drivers/media/platform/qcom/jpeg/Kconfig"
source "drivers/media/platform/qcom/venus/Kconfig"
diff --git a/drivers/media/platform/qcom/Makefile b/drivers/media/platform/qcom/Makefile
index ea2221a202c09..011b5d2371931 100644
--- a/drivers/media/platform/qcom/Makefile
+++ b/drivers/media/platform/qcom/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += camss/
obj-y += iris/
+obj-y += jpeg/
obj-y += venus/
diff --git a/drivers/media/platform/qcom/jpeg/Kconfig b/drivers/media/platform/qcom/jpeg/Kconfig
new file mode 100644
index 0000000000000..d1949d277d2ba
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Kconfig
@@ -0,0 +1,15 @@
+# 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 VIDEOBUF2_DMA_SG
+ select V4L2_JPEG_HELPER
+ select V4L2_MEM2MEM_DEV
+ help
+ V4L2 mem2mem driver for the Qualcomm JPEG encoder hardware
+ found in Snapdragon SoCs.
+
+ 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 0000000000000..e702475245708
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Makefile
@@ -0,0 +1,7 @@
+# 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_v420.o
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 0000000000000..da69c0bb3bbe7
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
@@ -0,0 +1,375 @@
+// 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/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <media/v4l2-mem2mem.h>
+
+#include "qcom_jenc_dev.h"
+#include "qcom_jenc_v420.h"
+#include "qcom_jenc_v4l2.h"
+
+struct qcom_jpeg_data {
+ const struct dev_pm_domain_attach_data *opp_pd_data;
+ unsigned long camnoc_axi_min_rate;
+};
+
+static const char * const qcom_jpeg_pd_names[] = { "titan_top" };
+
+static const struct dev_pm_domain_attach_data qcom_jpeg_pd_data = {
+ .pd_names = qcom_jpeg_pd_names,
+ .num_pd_names = ARRAY_SIZE(qcom_jpeg_pd_names),
+};
+
+static const char * const sc7280_opp_pd_names[] = { "cx" };
+
+static const struct dev_pm_domain_attach_data sc7280_opp_pd_data = {
+ .pd_names = sc7280_opp_pd_names,
+ .num_pd_names = ARRAY_SIZE(sc7280_opp_pd_names),
+ .pd_flags = PD_FLAG_REQUIRED_OPP,
+};
+
+static const struct qcom_jpeg_data sc7280_data = {
+ .opp_pd_data = &sc7280_opp_pd_data,
+ .camnoc_axi_min_rate = 150000000,
+};
+
+static const char * const sm8250_opp_pd_names[] = { "mx", "mmcx" };
+
+static const struct dev_pm_domain_attach_data sm8250_opp_pd_data = {
+ .pd_names = sm8250_opp_pd_names,
+ .num_pd_names = ARRAY_SIZE(sm8250_opp_pd_names),
+ .pd_flags = PD_FLAG_REQUIRED_OPP,
+};
+
+static const struct qcom_jpeg_data sm8250_data = {
+ .opp_pd_data = &sm8250_opp_pd_data,
+ .camnoc_axi_min_rate = 300000000,
+};
+
+/*
+ * qcom_jpeg_pd_init - attach the power domains of the JPEG encoder
+ *
+ * TITAN_TOP_GDSC powers the block. The OPP table votes the performance
+ * levels of the SoC domains through required-opps, in the order of
+ * the opp_pd_data names.
+ */
+static int qcom_jpeg_pd_init(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_data *data = device_get_match_data(jenc->dev);
+ int rc;
+
+ rc = devm_pm_domain_attach_list(jenc->dev, &qcom_jpeg_pd_data, &jenc->pd_list);
+ if (rc < 0)
+ return rc;
+
+ rc = devm_pm_domain_attach_list(jenc->dev, data->opp_pd_data, &jenc->opp_pd_list);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static int qcom_jpeg_opp_init(struct qcom_jenc_dev *jenc)
+{
+ struct dev_pm_opp *opp;
+ int rc;
+
+ rc = devm_pm_opp_set_clkname(jenc->dev, "core");
+ if (rc)
+ return rc;
+
+ rc = devm_pm_opp_of_add_table(jenc->dev);
+ if (rc)
+ return rc;
+
+ /* initialize the maximum available frequency for the JPEG core */
+ jenc->max_freq = ULONG_MAX;
+ opp = dev_pm_opp_find_freq_floor_indexed(jenc->dev, &jenc->max_freq, 0);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ dev_pm_opp_put(opp);
+
+ /* initialize the default optimized frequency for the JPEG core */
+ jenc->opt_freq = jenc->max_freq;
+
+ return 0;
+}
+
+static int qcom_jpeg_clk_init(struct qcom_jenc_dev *jenc)
+{
+ const struct qcom_jpeg_data *data = device_get_match_data(jenc->dev);
+ int i;
+
+ jenc->num_clks = devm_clk_bulk_get_all(jenc->dev, &jenc->clks);
+ if (jenc->num_clks < 0)
+ return jenc->num_clks;
+
+ for (i = 0; i < jenc->num_clks; i++) {
+ if (!strcmp(jenc->clks[i].id, "camnoc_axi_src"))
+ jenc->camnoc_axi_src = jenc->clks[i].clk;
+ }
+
+ if (!jenc->camnoc_axi_src)
+ return -ENOENT;
+
+ jenc->camnoc_axi_min_rate = data->camnoc_axi_min_rate;
+
+ return 0;
+}
+
+int qcom_jpeg_clk_retune(struct qcom_jenc_dev *jenc, unsigned long *freq)
+{
+ struct dev_pm_opp *opp;
+ int rc;
+
+ /* setup the OPP according to the calculated optimal frequency */
+ opp = dev_pm_opp_find_freq_ceil_indexed(jenc->dev, freq, 0);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ rc = dev_pm_opp_set_opp(jenc->dev, opp);
+ if (rc) {
+ dev_pm_opp_put(opp);
+ return rc;
+ }
+
+ dev_pm_opp_put(opp);
+
+ return 0;
+}
+
+static int qcom_jpeg_clk_on(struct qcom_jenc_dev *jenc)
+{
+ int rc;
+
+ rc = clk_bulk_prepare(jenc->num_clks, jenc->clks);
+ if (rc)
+ return rc;
+
+ rc = clk_set_rate_range(jenc->camnoc_axi_src, jenc->camnoc_axi_min_rate, ULONG_MAX);
+ if (rc)
+ goto err_clk_unprepare;
+
+ rc = qcom_jpeg_clk_retune(jenc, &jenc->opt_freq);
+ if (rc)
+ goto err_camnoc_release;
+
+ rc = clk_bulk_enable(jenc->num_clks, jenc->clks);
+ if (rc)
+ goto err_opp_clear;
+
+ return 0;
+
+err_opp_clear:
+ dev_pm_opp_set_opp(jenc->dev, NULL);
+err_camnoc_release:
+ clk_set_rate_range(jenc->camnoc_axi_src, 0, ULONG_MAX);
+err_clk_unprepare:
+ clk_bulk_unprepare(jenc->num_clks, jenc->clks);
+
+ return rc;
+}
+
+static void qcom_jpeg_clk_off(struct qcom_jenc_dev *jenc)
+{
+ clk_bulk_disable(jenc->num_clks, jenc->clks);
+ dev_pm_opp_set_opp(jenc->dev, NULL);
+ clk_set_rate_range(jenc->camnoc_axi_src, 0, ULONG_MAX);
+ clk_bulk_unprepare(jenc->num_clks, jenc->clks);
+ jenc->opt_freq = jenc->max_freq;
+}
+
+static int qcom_jpeg_pm_suspend(struct device *dev)
+{
+ struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+
+ qcom_jpeg_clk_off(jenc);
+
+ return 0;
+}
+
+static int qcom_jpeg_pm_resume(struct device *dev)
+{
+ struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+ int rc;
+
+ rc = qcom_jpeg_clk_on(jenc);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static int qcom_jpeg_pm_system_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 int qcom_jpeg_pm_system_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 = {
+ SYSTEM_SLEEP_PM_OPS(qcom_jpeg_pm_system_suspend, qcom_jpeg_pm_system_resume)
+ RUNTIME_PM_OPS(qcom_jpeg_pm_suspend, qcom_jpeg_pm_resume, NULL)
+};
+
+static void qcom_jpeg_v4l2_dev_unregister(void *data)
+{
+ v4l2_device_unregister(data);
+}
+
+static int qcom_jpeg_probe(struct platform_device *pdev)
+{
+ struct qcom_jenc_dev *jenc;
+ int rc;
+
+ jenc = devm_kzalloc(&pdev->dev, sizeof(*jenc), GFP_KERNEL);
+ if (!jenc)
+ return -ENOMEM;
+
+ jenc->dev = &pdev->dev;
+ platform_set_drvdata(pdev, jenc);
+
+ rc = devm_mutex_init(&pdev->dev, &jenc->dev_mutex);
+ if (rc)
+ goto err_free_jenc;
+ rc = devm_mutex_init(&pdev->dev, &jenc->hw_ref_lock);
+ if (rc)
+ goto err_free_jenc;
+ spin_lock_init(&jenc->hw_lock);
+ init_completion(&jenc->reset_complete);
+ init_completion(&jenc->stop_complete);
+
+ rc = dma_set_mask_and_coherent(jenc->dev, DMA_BIT_MASK(32));
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to set DMA mask\n");
+ goto err_free_jenc;
+ }
+
+ dma_set_max_seg_size(jenc->dev, UINT_MAX);
+
+ jenc->jpeg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(jenc->jpeg_base)) {
+ rc = dev_err_probe(jenc->dev, PTR_ERR(jenc->jpeg_base),
+ "failed to map JPEG resource\n");
+ goto err_free_jenc;
+ }
+
+ rc = qcom_jpeg_pd_init(jenc);
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to attach power domains\n");
+ goto err_free_jenc;
+ }
+
+ rc = qcom_jpeg_opp_init(jenc);
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to init OPP\n");
+ goto err_free_jenc;
+ }
+
+ rc = qcom_jpeg_clk_init(jenc);
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to init clocks\n");
+ goto err_free_jenc;
+ }
+
+ jenc->irq = platform_get_irq(pdev, 0);
+ if (jenc->irq < 0) {
+ rc = dev_err_probe(jenc->dev, jenc->irq, "failed to get IRQ\n");
+ goto err_free_jenc;
+ }
+
+ rc = devm_request_threaded_irq(jenc->dev, jenc->irq,
+ qcom_jenc_v420_irq_top,
+ qcom_jenc_v420_irq_bot,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, dev_name(jenc->dev), jenc);
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to request IRQ\n");
+ goto err_free_jenc;
+ }
+
+ rc = v4l2_device_register(jenc->dev, &jenc->v4l2_dev);
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to register V4L2 device\n");
+ goto err_free_jenc;
+ }
+
+ rc = devm_add_action_or_reset(jenc->dev, qcom_jpeg_v4l2_dev_unregister, &jenc->v4l2_dev);
+ if (rc)
+ goto err_free_jenc;
+
+ rc = devm_pm_runtime_enable(jenc->dev);
+ if (rc)
+ goto err_free_jenc;
+
+ rc = qcom_jpeg_v4l2_register(jenc);
+ if (rc) {
+ dev_err_probe(jenc->dev, rc, "failed to register video device\n");
+ goto err_free_jenc;
+ }
+
+ return 0;
+
+err_free_jenc:
+ return rc;
+}
+
+static const struct of_device_id qcom_jpeg_of_match[] = {
+ {
+ .compatible = "qcom,sc7280-jpeg-enc",
+ .data = &sc7280_data
+ },
+ {
+ .compatible = "qcom,sm8250-jpeg-enc",
+ .data = &sm8250_data
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_jpeg_of_match);
+
+static struct platform_driver qcom_jpeg_platform_driver = {
+ .probe = qcom_jpeg_probe,
+ .driver = {
+ .name = QCOM_JPEG_ENC_NAME,
+ .of_match_table = qcom_jpeg_of_match,
+ .pm = pm_ptr(&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 0000000000000..c7e4da0310d99
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
@@ -0,0 +1,122 @@
+/* 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 <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-jpeg.h>
+#include <media/videobuf2-core.h>
+
+#define QCOM_JPEG_ENC_NAME "qcom-jpeg-enc"
+
+#define JPEG_HEADER_MAX 1024
+
+/* JPEG header built once per source format, copied into every output buffer */
+struct qcom_jenc_header {
+ u8 data[JPEG_HEADER_MAX];
+ struct v4l2_jpeg_hdr_layout layout;
+};
+
+#define QCOM_JPEG_MAX_PLANES 3
+
+#define QCOM_JPEG_QUALITY_MIN 1
+#define QCOM_JPEG_QUALITY_DEF 98
+#define QCOM_JPEG_QUALITY_MAX 100
+#define QCOM_JPEG_QUALITY_UNT 1
+
+#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;
+ int irq;
+ void __iomem *jpeg_base;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct clk *camnoc_axi_src;
+ unsigned long camnoc_axi_min_rate;
+ struct dev_pm_domain_list *pd_list;
+ struct dev_pm_domain_list *opp_pd_list;
+ unsigned long max_freq;
+ unsigned long opt_freq;
+ /* device mutex lock */
+ struct mutex dev_mutex;
+ /* hardware reference count lock */
+ struct mutex hw_ref_lock;
+ unsigned int ref_count;
+ struct completion reset_complete;
+ struct completion stop_complete;
+ /* encoder hardware lock */
+ spinlock_t hw_lock;
+ struct jenc_context *actx;
+
+ u32 pending_irq_status;
+
+ void (*enc_hw_irq_cb)
+ (void *data, enum vb2_buffer_state ev, size_t out_size);
+};
+
+int qcom_jpeg_clk_retune(struct qcom_jenc_dev *jenc, unsigned long *freq);
+
+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 hw_acquired;
+
+ struct qcom_jenc_queue bufq[JENC_QUEUE_MAX];
+ struct qcom_jenc_header hdr_cache;
+
+ struct work_struct finish_work;
+};
+
+#endif
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c
new file mode 100644
index 0000000000000..cd1501bca71a7
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.c
@@ -0,0 +1,1527 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/math64.h>
+#include <linux/pm_runtime.h>
+#include <linux/scatterlist.h>
+#include <linux/string.h>
+
+#include <media/jpeg.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-jpeg.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "qcom_jenc_dev.h"
+#include "qcom_jenc_v420.h"
+#include "qcom_jenc_v420_hw_info.h"
+
+/* JPEG quality constants */
+#define QCOM_JPEG_QUALITY_MID (QCOM_JPEG_QUALITY_MAX / 2)
+
+/* Per-plane register offset lookup arrays (indexed by plane number 0..2) */
+static const u32 jpeg_v420_fe_pntr[] = {
+ JPEG_V420_REG_FE_PNTR_0,
+ JPEG_V420_REG_FE_PNTR_1,
+ JPEG_V420_REG_FE_PNTR_2,
+};
+
+static const u32 jpeg_v420_fe_offs[] = {
+ JPEG_V420_REG_FE_OFFS_0,
+ JPEG_V420_REG_FE_OFFS_1,
+ JPEG_V420_REG_FE_OFFS_2,
+};
+
+static const u32 jpeg_v420_fe_bsize[] = {
+ JPEG_V420_REG_FE_BSIZE_0,
+ JPEG_V420_REG_FE_BSIZE_1,
+ JPEG_V420_REG_FE_BSIZE_2,
+};
+
+static const u32 jpeg_v420_fe_stride[] = {
+ JPEG_V420_REG_FE_STRIDE_0,
+ JPEG_V420_REG_FE_STRIDE_1,
+ JPEG_V420_REG_FE_STRIDE_2,
+};
+
+static const u32 jpeg_v420_fe_hinit[] = {
+ JPEG_V420_REG_FE_HINIT_0,
+ JPEG_V420_REG_FE_HINIT_1,
+ JPEG_V420_REG_FE_HINIT_2,
+};
+
+static const u32 jpeg_v420_fe_vinit[] = {
+ JPEG_V420_REG_FE_VINIT_0,
+ JPEG_V420_REG_FE_VINIT_1,
+ JPEG_V420_REG_FE_VINIT_2,
+};
+
+static const u32 jpeg_v420_we_pntr[] = {
+ JPEG_V420_REG_WE_PNTR_0,
+ JPEG_V420_REG_WE_PNTR_1,
+ JPEG_V420_REG_WE_PNTR_2,
+};
+
+static const u32 jpeg_v420_we_stride[] = {
+ JPEG_V420_REG_WE_STRIDE_0,
+ JPEG_V420_REG_WE_STRIDE_1,
+ JPEG_V420_REG_WE_STRIDE_2,
+};
+
+static const u32 jpeg_v420_we_hinit[] = {
+ JPEG_V420_REG_WE_HINIT_0,
+ JPEG_V420_REG_WE_HINIT_1,
+ JPEG_V420_REG_WE_HINIT_2,
+};
+
+static const u32 jpeg_v420_we_hstep[] = {
+ JPEG_V420_REG_WE_HSTEP_0,
+ JPEG_V420_REG_WE_HSTEP_1,
+ JPEG_V420_REG_WE_HSTEP_2,
+};
+
+static const u32 jpeg_v420_we_vinit[] = {
+ JPEG_V420_REG_WE_VINIT_0,
+ JPEG_V420_REG_WE_VINIT_1,
+ JPEG_V420_REG_WE_VINIT_2,
+};
+
+static const u32 jpeg_v420_we_vstep[] = {
+ JPEG_V420_REG_WE_VSTEP_0,
+ JPEG_V420_REG_WE_VSTEP_1,
+ JPEG_V420_REG_WE_VSTEP_2,
+};
+
+static const u32 jpeg_v420_scale_hstep[] = {
+ JPEG_V420_REG_SCALE_HSTEP_0,
+ JPEG_V420_REG_SCALE_HSTEP_1,
+ JPEG_V420_REG_SCALE_HSTEP_2,
+};
+
+static const u32 jpeg_v420_scale_vstep[] = {
+ JPEG_V420_REG_SCALE_VSTEP_0,
+ JPEG_V420_REG_SCALE_VSTEP_1,
+ JPEG_V420_REG_SCALE_VSTEP_2,
+};
+
+static const u32 jpeg_v420_scale_out_cfg[] = {
+ JPEG_V420_REG_SCALE_OUT_CFG_0,
+ JPEG_V420_REG_SCALE_OUT_CFG_1,
+ JPEG_V420_REG_SCALE_OUT_CFG_2,
+};
+
+#define JPEG_RESET_TIMEOUT_MS 300
+#define JPEG_STOP_TIMEOUT_MS 200
+
+#define JPEG_DQT_SHIFT 20
+
+#define JPEG_MCU_BLOCK_8 8
+#define JPEG_MCU_BLOCK_16 16
+
+#define JPEG_DEFAULT_SCALE_STEP 0x200000
+
+/* The core encodes 430 Mpixel/s at 600 MHz; the clock is scaled for 60 fps */
+#define JPEG_REF_CLK_HZ 600000000UL
+#define JPEG_REF_THROUGHPUT_MPPS 430UL
+#define JPEG_REF_FPS 60UL
+
+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 {
+ enum qcom_jpeg_encode_fmt encode;
+ enum qcom_jpeg_memory_fmt memory;
+ u32 fourcc;
+};
+
+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_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 = 2048, .boundary = JPEG_CFG_MAL_BOUND_2K_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;
+
+ 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 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;
+
+ 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;
+}
+
+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);
+}
+
+static inline u32 jpeg_rd_bits(struct qcom_jenc_dev *jenc, u32 offs, u32 mask)
+{
+ u32 reg = jpeg_io_read(jenc, offs);
+
+ return field_get(mask, reg);
+}
+
+/*
+ * Read-modify-write (for R/W registers)
+ */
+static inline void jpeg_rw_bits(struct qcom_jenc_dev *jenc, u32 offs, u32 mask, u32 val)
+{
+ u32 reg = jpeg_io_read(jenc, offs);
+
+ reg &= ~mask;
+ reg |= field_prep(mask, val);
+
+ jpeg_io_write(jenc, offs, reg);
+}
+
+static u8 jpeg_calculate_dqt(struct jenc_context *ectx, u8 dqt_value)
+{
+ u64 calc_val;
+ u64 ratio;
+
+ 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 clamp_t(u64, calc_val, 1, U8_MAX);
+}
+
+/*
+ * jpeg_update_dqt_cache - compute scaled DQT coefficients and store them in
+ * the software JPEG header cache (hdr_cache). Safe to call before the
+ * hardware is powered on; no MMIO access is performed here. The DQT segment
+ * lists coefficients in zigzag order, so each zigzag position is mapped back
+ * to the natural position of the reference table.
+ */
+static void jpeg_update_dqt_cache(struct jenc_context *ectx)
+{
+ u8 *base;
+ u8 dqt_val, nat;
+ int zz;
+
+ /* Luma DQT cache update */
+ if (ectx->hdr_cache.layout.dqt_offset[0]) {
+ base = &ectx->hdr_cache.data[ectx->hdr_cache.layout.dqt_offset[0]];
+ for (zz = 0; zz < V4L2_JPEG_PIXELS_IN_BLOCK; zz++) {
+ nat = v4l2_jpeg_zigzag_scan_index[zz];
+ dqt_val = jpeg_calculate_dqt(ectx, v4l2_jpeg_ref_table_luma_qt[nat]);
+ base[zz] = dqt_val;
+ }
+ }
+
+ /* Chroma DQT cache update */
+ if (ectx->hdr_cache.layout.dqt_offset[1]) {
+ base = &ectx->hdr_cache.data[ectx->hdr_cache.layout.dqt_offset[1]];
+ for (zz = 0; zz < V4L2_JPEG_PIXELS_IN_BLOCK; zz++) {
+ nat = v4l2_jpeg_zigzag_scan_index[zz];
+ dqt_val = jpeg_calculate_dqt(ectx, v4l2_jpeg_ref_table_chroma_qt[nat]);
+ base[zz] = dqt_val;
+ }
+ }
+}
+
+/*
+ * jpeg_dqt_to_natural - reorder one DQT table from the zigzag order of the
+ * JPEG header to the natural (raster) order taken by the hardware DMI.
+ */
+static void jpeg_dqt_to_natural(const u8 *zz_qt, u8 *nat_qt)
+{
+ int zz;
+
+ for (zz = 0; zz < V4L2_JPEG_PIXELS_IN_BLOCK; zz++)
+ nat_qt[v4l2_jpeg_zigzag_scan_index[zz]] = zz_qt[zz];
+}
+
+/*
+ * 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.
+ * The hardware takes them in natural order, as 65536 / q reciprocals.
+ */
+static void jpeg_upload_dmi_table(struct jenc_context *ectx)
+{
+ const u8 *luma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.layout.dqt_offset[0]];
+ u8 nat_qt[V4L2_JPEG_PIXELS_IN_BLOCK];
+ u32 pcfg = JPEG_DMI_ENABLE_UPLOAD;
+ u32 addr = 0x00000000;
+ u32 reg_val;
+ int i;
+
+ /* DMI upload start sequence */
+ jpeg_io_write(ectx->jenc, JPEG_V420_REG_DMI_ADDR, addr);
+ jpeg_io_write(ectx->jenc, JPEG_V420_REG_DMI_CFG, pcfg);
+
+ /* DMI Luma upload - values are stored in zigzag order in hdr_cache */
+ jpeg_dqt_to_natural(luma_qt, nat_qt);
+ for (i = 0; i < V4L2_JPEG_PIXELS_IN_BLOCK; i++) {
+ reg_val = div_u64(U16_MAX + 1U, nat_qt[i]);
+ reg_val = clamp_t(u32, reg_val, 0, U16_MAX);
+ jpeg_io_write(ectx->jenc, JPEG_V420_REG_DMI_DATA, reg_val);
+ }
+
+ /* DMI Chroma upload - only present for color formats */
+ if (ectx->hdr_cache.layout.dqt_offset[1]) {
+ const u8 *chroma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.layout.dqt_offset[1]];
+
+ jpeg_dqt_to_natural(chroma_qt, nat_qt);
+ for (i = 0; i < V4L2_JPEG_PIXELS_IN_BLOCK; i++) {
+ reg_val = div_u64(U16_MAX + 1U, nat_qt[i]);
+ reg_val = clamp_t(u32, reg_val, 0, U16_MAX);
+ jpeg_io_write(ectx->jenc, JPEG_V420_REG_DMI_DATA, reg_val);
+ }
+ }
+
+ /* DMI upload end sequence */
+ jpeg_io_write(ectx->jenc, JPEG_V420_REG_DMI_CFG, addr);
+
+ ectx->quality_programmed = ectx->quality_requested;
+
+ dev_dbg(ectx->dev, "quality_programmed=%d\n", 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);
+ }
+}
+
+/*
+ * Only an explicit CLEAR_RD/WR_PLNS_QUEUE command drains the FE/WE hardware
+ * pointer queues; a core reset or HW_STOP does not.
+ */
+static void jpeg_clear_planes_queues(struct qcom_jenc_dev *jenc)
+{
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, JPEG_V420_CMD_CLR_RD_PLNS_QUEUE_MSK);
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, 0);
+
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, JPEG_V420_CMD_CLR_WR_PLNS_QUEUE_MSK);
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, 0);
+}
+
+static int jpeg_init(struct qcom_jenc_dev *jenc)
+{
+ unsigned long rtime;
+ u32 hw_ver;
+
+ if (WARN_ON_ONCE(!jenc || !jenc->dev || !jenc->jpeg_base))
+ return -EINVAL;
+
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_CLR, JPEG_V420_IRQ_STATUS_ALL_BITS);
+
+ reinit_completion(&jenc->reset_complete);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_INT_MASK, JPEG_V420_IRQ_STATUS_RESET_ACK, 1);
+
+ jpeg_io_write(jenc, JPEG_V420_REG_RESET_CMD, JPEG_V420_RST_CMD_ALL_BITS);
+
+ 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");
+ return -ETIME;
+ }
+
+ hw_ver = jpeg_io_read(jenc, JPEG_V420_REG_HW_VERSION);
+ dev_dbg(jenc->dev, "JPEG HW encoder version %lu.%lu.%lu\n",
+ FIELD_GET(JPEG_V420_HW_VER_MAJOR_MSK, hw_ver),
+ FIELD_GET(JPEG_V420_HW_VER_MINOR_MSK, hw_ver),
+ FIELD_GET(JPEG_V420_HW_VER_STEP_MSK, hw_ver));
+
+ jpeg_clear_planes_queues(jenc);
+
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_CLR, JPEG_V420_IRQ_STATUS_ALL_BITS);
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_MASK, JPEG_V420_IRQ_STATUS_ALL_BITS);
+
+ return 0;
+}
+
+static void jpeg_exec(struct qcom_jenc_dev *jenc)
+{
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, FIELD_PREP(JPEG_V420_CMD_HW_START, 1));
+}
+
+static void jpeg_stop(struct qcom_jenc_dev *jenc)
+{
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, FIELD_PREP(JPEG_V420_CMD_HW_START, 0));
+
+ jpeg_clear_planes_queues(jenc);
+}
+
+static int jpeg_deinit(struct qcom_jenc_dev *jenc)
+{
+ unsigned long rtime;
+
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_CLR, JPEG_V420_IRQ_STATUS_ALL_BITS);
+
+ reinit_completion(&jenc->stop_complete);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_INT_MASK, JPEG_V420_IRQ_STATUS_STOP_ACK, 1);
+
+ jpeg_io_write(jenc, JPEG_V420_REG_HW_CMD, FIELD_PREP(JPEG_V420_CMD_HW_STOP, 1));
+
+ rtime = wait_for_completion_timeout(&jenc->stop_complete,
+ msecs_to_jiffies(JPEG_STOP_TIMEOUT_MS));
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_MASK, 0);
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_CLR, JPEG_V420_IRQ_STATUS_ALL_BITS);
+
+ if (!rtime) {
+ dev_err(jenc->dev, "encoder HW stop timeout\n");
+ return -ETIME;
+ }
+
+ /*
+ * Only after STOP_ACK: drop the addresses an aborted job left in the
+ * FE/WE queues. A completed job clears them in jpeg_stop().
+ */
+ jpeg_clear_planes_queues(jenc);
+
+ return 0;
+}
+
+static void jpeg_apply_fe_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
+ struct vb2_buffer *vb)
+{
+ struct qcom_jpeg_buff *frame = &q->buff[vb->index];
+ struct v4l2_pix_format_mplane *fmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ for (pln = 0; pln < fmt->num_planes; pln++) {
+ if (!frame->plns[pln].sgt || !frame->plns[pln].sgt->sgl)
+ break;
+
+ jpeg_io_write(jenc, jpeg_v420_fe_pntr[pln], frame->plns[pln].dma);
+ jpeg_io_write(jenc, jpeg_v420_fe_offs[pln], 0);
+
+ dev_dbg(jenc->dev, "FE pln=%d addr=%pad idx:%d\n",
+ pln, &frame->plns[pln].dma, vb->index);
+ }
+}
+
+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;
+
+ for (pln = 0; 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 void jpeg_setup_fe_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct v4l2_pix_format_mplane *sfmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_rw_bits(jenc, jpeg_v420_fe_bsize[pln],
+ JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK, 0);
+ jpeg_rw_bits(jenc, jpeg_v420_fe_bsize[pln],
+ JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK, 0);
+ jpeg_rw_bits(jenc, jpeg_v420_fe_stride[pln], JPEG_V420_PLN_RD_STRIDE_MSK, 0);
+ }
+
+ for (pln = 0; pln < sfmt->num_planes; pln++) {
+ jpeg_rw_bits(jenc, jpeg_v420_fe_bsize[pln], JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK,
+ sfmt->width - 1);
+ jpeg_rw_bits(jenc, jpeg_v420_fe_bsize[pln], JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK,
+ sfmt->height - 1);
+ jpeg_rw_bits(jenc, jpeg_v420_fe_stride[pln], JPEG_V420_PLN_RD_STRIDE_MSK,
+ sfmt->plane_fmt[pln].bytesperline);
+
+ dev_dbg(ectx->dev, "FE pln=%d width=%d height=%d stride=%d\n",
+ pln,
+ jpeg_rd_bits(jenc, jpeg_v420_fe_bsize[pln],
+ JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK),
+ jpeg_rd_bits(jenc, jpeg_v420_fe_bsize[pln],
+ JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK),
+ jpeg_rd_bits(jenc, jpeg_v420_fe_stride[pln],
+ JPEG_V420_PLN_RD_STRIDE_MSK));
+ }
+}
+
+static void jpeg_setup_fe_hinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+ jpeg_io_write(jenc, jpeg_v420_fe_hinit[pln], 0);
+}
+
+static void jpeg_setup_fe_vinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+ jpeg_io_write(jenc, jpeg_v420_fe_vinit[pln], 0);
+}
+
+static int jpeg_setup_fe_params(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct v4l2_pix_format_mplane *sfmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 expected_planes, pln;
+ int rval;
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_MAL_EN, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_BOTTOM_VPAD_EN, 1);
+
+ rval = jpeg_get_memory_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "invalid memory format for v4l2 format: %p4cc\n",
+ &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, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_MEMORY_FORMAT_MSK, rval);
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_PLNS_EN_MSK, 0);
+
+ /* Scaling is not supported; always configure for 1:1 */
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_SIXTEEN_MCU_EN, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_MCUS_PER_BLOCK, 0);
+
+ dev_dbg(ectx->dev, "sixteen MCU enabled=%d, %d MCU per blocks\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_SIXTEEN_MCU_EN),
+ jpeg_rd_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_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, "failed to get FE mal boundary width=%u\n",
+ sfmt->width);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_MAL_BOUNDARY, rval);
+
+ dev_dbg(ectx->dev, "optimal FE mal boundary=%d\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_MAL_BOUNDARY));
+
+ rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "unsupported encode format %p4cc\n",
+ &sfmt->pixelformat);
+ return -EINVAL;
+ }
+
+ switch (rval) {
+ case JPEG_ENCODE_MONO:
+ case JPEG_ENCODE_H1V1:
+ case JPEG_ENCODE_H2V1:
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_VBPAD_CFG, JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK,
+ DIV_ROUND_UP(sfmt->height, JPEG_MCU_BLOCK_8));
+ break;
+ case JPEG_ENCODE_H1V2:
+ case JPEG_ENCODE_H2V2:
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_VBPAD_CFG, JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK,
+ DIV_ROUND_UP(sfmt->height, JPEG_MCU_BLOCK_16));
+ break;
+ default:
+ dev_err(ectx->dev, "unsupported encode format: %d\n", rval);
+ return -EINVAL;
+ }
+
+ dev_dbg(ectx->dev, "FE vpad config=%d\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_FE_VBPAD_CFG,
+ JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK));
+
+ if (sfmt->pixelformat == V4L2_PIX_FMT_NV21M)
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_FE_CFG_CBCR_ORDER, 1);
+ else
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG, JPEG_V420_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, JPEG_V420_REG_FE_CFG,
+ JPEG_V420_FE_CFG_PLN0_EN, 1);
+ break;
+ case 1:
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG,
+ JPEG_V420_FE_CFG_PLN1_EN, 1);
+ break;
+ case 2:
+ jpeg_rw_bits(jenc, JPEG_V420_REG_FE_CFG,
+ JPEG_V420_FE_CFG_PLN2_EN, 1);
+ break;
+ }
+ }
+ }
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_CORE_CFG, JPEG_V420_CORE_CFG_FE_ENABLE, 1);
+
+ return 0;
+}
+
+static int jpeg_setup_fe(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ int rc;
+
+ jpeg_setup_fe_size(ectx, q);
+ jpeg_setup_fe_hinit(ectx, q);
+ jpeg_setup_fe_vinit(ectx, q);
+
+ rc = jpeg_setup_fe_params(ectx, q);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+/*
+ * jpeg_header_init - build the JPEG header for a source format: one component
+ * for GREY, otherwise YCbCr with 2x2 luma sampling.
+ */
+static int jpeg_header_init(struct qcom_jenc_header *hdr, u32 fourcc)
+{
+ bool mono = fourcc == V4L2_PIX_FMT_GREY;
+ struct v4l2_jpeg_hdr_params params = {
+ .num_components = mono ? JPEG_COMP_MONO : JPEG_COMP_COLOR,
+ .luma_sampling = mono ? JPEG_SAMPLING_H1V1 : JPEG_SAMPLING_H2V2,
+ .jfif = true,
+ };
+
+ return v4l2_jpeg_write_header(hdr->data, sizeof(hdr->data), ¶ms, &hdr->layout);
+}
+
+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.layout.size)
+ return 0;
+
+ rc = jpeg_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_jpeg_buff *frame = &q->buff[vb->index];
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ void *mptr = vb2_plane_vaddr(vb, 0);
+ dma_addr_t dma = frame->plns[0].dma;
+ size_t header_size;
+ u8 pln = 0;
+ int rc;
+
+ if (WARN_ON_ONCE(!dma))
+ return -EPERM;
+
+ if (WARN_ON_ONCE(!mptr))
+ return -EPERM;
+
+ rc = jpeg_ensure_header_cache(ectx);
+ if (rc)
+ return rc;
+
+ /* Update DQT cache if quality changed, then upload to HW DMI. */
+ mutex_lock(&ectx->quality_mutex);
+ if (!ectx->hdr_cache.layout.size || ectx->quality_programmed != ectx->quality_requested)
+ jpeg_update_dqt_cache(ectx);
+ jpeg_upload_dmi_table(ectx);
+ mutex_unlock(&ectx->quality_mutex);
+
+ header_size = ectx->hdr_cache.layout.size;
+ if (vb->planes[0].length < header_size)
+ return -EINVAL;
+
+ memcpy(mptr, ectx->hdr_cache.data, header_size);
+ v4l2_jpeg_hdr_set_size(mptr, &ectx->hdr_cache.layout, q->vf.width, q->vf.height);
+
+ dma += header_size;
+
+ /*
+ * 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, jpeg_v420_we_pntr[pln], dma);
+
+ dev_dbg(jenc->dev, "WE pln=%d addr=%pad idx:%d\n",
+ pln, &dma, vb->index);
+
+ q->buff_id = vb->index;
+
+ 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 void jpeg_setup_we_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+ jpeg_rw_bits(jenc, jpeg_v420_we_stride[pln], JPEG_V420_PLN_WR_STRIDE_MSK, 0);
+
+ jpeg_io_write(jenc, JPEG_V420_REG_WE_BSIZE_0, dfmt->plane_fmt[0].sizeimage);
+
+ dev_dbg(ectx->dev, "WE size=%u\n", dfmt->plane_fmt[0].sizeimage);
+}
+
+static int jpeg_setup_we_hinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ if (!dfmt->width) {
+ dev_err(ectx->dev, "invalid destination width=%d\n", dfmt->width);
+ return -EINVAL;
+ }
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_rw_bits(jenc, jpeg_v420_we_hinit[pln], JPEG_V420_PLN_WR_HINIT_INTEGER_MSK, 0);
+ jpeg_rw_bits(jenc, jpeg_v420_we_hstep[pln], JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK, 0);
+ }
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_HSTEP_0,
+ JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK, dfmt->width);
+
+ dev_dbg(ectx->dev, "WE hstep=%u\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_WE_HSTEP_0, JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK));
+
+ return 0;
+}
+
+static int jpeg_setup_we_vinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u8 pln;
+
+ if (!dfmt->height) {
+ dev_err(ectx->dev, "invalid destination height=%d\n", dfmt->height);
+ return -EINVAL;
+ }
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_rw_bits(jenc, jpeg_v420_we_vinit[pln], JPEG_V420_PLN_WR_VINIT_INTEGER_MSK, 0);
+ jpeg_rw_bits(jenc, jpeg_v420_we_vstep[pln], JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK, 0);
+ }
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_VSTEP_0,
+ JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK, dfmt->height);
+
+ dev_dbg(ectx->dev, "WE vstep=%u\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_WE_VSTEP_0, JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK));
+
+ return 0;
+}
+
+static int jpeg_setup_we_params(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ struct v4l2_pix_format_mplane *dfmt = &q->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u32 blk_w = 0, blk_h = 0, mcu_cols = 0, mcu_rows = 0;
+ int rval;
+
+ rval = jpeg_get_memory_fmt(dfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "invalid memory format for v4l2 format: %p4cc\n",
+ &dfmt->pixelformat);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK, rval);
+
+ rval = jpeg_get_mal_boundary(dfmt->width, jpeg_mal_bounds, ARRAY_SIZE(jpeg_mal_bounds));
+ if (rval < 0) {
+ dev_err(ectx->dev, "failed to get WE mal boundary width=%u\n",
+ dfmt->width);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_MAL_BOUNDARY, rval);
+
+ dev_dbg(ectx->dev, "optimal WE mal boundary=%d\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_MAL_BOUNDARY));
+
+ rval = jpeg_get_encode_fmt(dfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "unsupported encode format %p4cc\n",
+ &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, "invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+ mcu_cols, mcu_rows);
+ return rval;
+ }
+
+ dev_dbg(ectx->dev, "WE blk_w=%u blk_h=%u cols=%u rows=%u\n",
+ blk_w, blk_h, mcu_cols, mcu_rows);
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_BLOCKS_0,
+ JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_MSK, mcu_rows - 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_BLOCKS_0,
+ JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_MSK, mcu_cols - 1);
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_CBCR_ORDER, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_MAL_EN, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_POP_BUFF_ON_EOS, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_WE_CFG, JPEG_V420_WE_CFG_PLN0_EN, 1);
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_CORE_CFG, JPEG_V420_CORE_CFG_MODE, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_CORE_CFG, JPEG_V420_CORE_CFG_WE_ENABLE, 1);
+
+ return 0;
+}
+
+static int jpeg_setup_we(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+ int rc;
+
+ jpeg_setup_we_size(ectx, q);
+
+ 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_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;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u32 blk_w = 0, blk_h = 0, mcu_cols = 0, mcu_rows = 0;
+ int rval;
+ u8 pln;
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_RESET_CMD, JPEG_V420_RST_CMD_SCALE_RESET, 1);
+
+ /* explicit no scaling */
+ jpeg_rw_bits(jenc, JPEG_V420_REG_SCALE_CFG, JPEG_V420_SCALE_CFG_HSCALE_ENABLE, 0);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_SCALE_CFG, JPEG_V420_SCALE_CFG_VSCALE_ENABLE, 0);
+
+ for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+ jpeg_io_write(jenc, jpeg_v420_scale_hstep[pln], JPEG_DEFAULT_SCALE_STEP);
+ jpeg_io_write(jenc, jpeg_v420_scale_vstep[pln], JPEG_DEFAULT_SCALE_STEP);
+ }
+
+ rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "unsupported encode format %p4cc\n",
+ &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, "invalid MCU geometry blk_w=%d blk_h=%d\n",
+ blk_w, blk_h);
+ return -EINVAL;
+ }
+
+ dev_dbg(ectx->dev, "scale blk_w=%u blk_h=%u cols=%u rows=%u\n",
+ blk_w, blk_h, mcu_cols, mcu_rows);
+
+ for (pln = 0; pln < sq->vf.num_planes; pln++) {
+ jpeg_rw_bits(jenc, jpeg_v420_scale_out_cfg[pln],
+ JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_MSK, blk_w - 1);
+ jpeg_rw_bits(jenc, jpeg_v420_scale_out_cfg[pln],
+ JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_MSK, blk_h - 1);
+ }
+
+ dev_dbg(ectx->dev, "scale src=%ux%u dst=%ux%u enable=%d/%d\n",
+ sfmt->width, sfmt->height, dfmt->width, dfmt->height,
+ jpeg_rd_bits(jenc, JPEG_V420_REG_SCALE_CFG, JPEG_V420_SCALE_CFG_HSCALE_ENABLE),
+ jpeg_rd_bits(jenc, JPEG_V420_REG_SCALE_CFG, JPEG_V420_SCALE_CFG_VSCALE_ENABLE));
+
+ /* Disabled, but must be configured */
+ jpeg_rw_bits(jenc, JPEG_V420_REG_CORE_CFG, JPEG_V420_CORE_CFG_SCALE_ENABLE, 0);
+
+ return 0;
+}
+
+static int jpeg_setup_encode(struct jenc_context *ectx)
+{
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct v4l2_pix_format_mplane *sfmt = &sq->vf;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u32 blk_w = 0, blk_h = 0, mcu_cols = 0, mcu_rows = 0;
+ int rval;
+
+ if (!sfmt->width || !sfmt->height)
+ return -EINVAL;
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_RESET_CMD, JPEG_V420_RST_CMD_ENCODER_RESET, 1);
+
+ rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+ if (rval < 0) {
+ dev_err(ectx->dev, "unsupported encode format %p4cc\n",
+ &sfmt->pixelformat);
+ return -EINVAL;
+ }
+ jpeg_rw_bits(jenc, JPEG_V420_REG_ENC_CFG, JPEG_V420_ENC_CFG_IMAGE_FORMAT_MSK, rval);
+
+ rval = jpeg_get_mcu_geometry(rval, sfmt->width, sfmt->height, &blk_w, &blk_h,
+ NULL, &mcu_rows);
+ if (rval < 0) {
+ dev_err(ectx->dev, "invalid MCU geometry mcu_rows=%d\n", mcu_rows);
+ return -EINVAL;
+ }
+
+ mcu_cols = sfmt->width / blk_w;
+
+ dev_dbg(ectx->dev, "encode blk_w=%u blk_h=%u cols=%u rows=%u\n",
+ blk_w, blk_h, mcu_cols, mcu_rows);
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_ENC_IMG_SIZE,
+ JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK, mcu_cols - 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_ENC_IMG_SIZE,
+ JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK, mcu_rows - 1);
+
+ dev_dbg(ectx->dev, "encode width=%d height=%d\n",
+ jpeg_rd_bits(jenc, JPEG_V420_REG_ENC_IMG_SIZE,
+ JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK),
+ jpeg_rd_bits(jenc, JPEG_V420_REG_ENC_IMG_SIZE,
+ JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK));
+
+ jpeg_rw_bits(jenc, JPEG_V420_REG_ENC_CFG, JPEG_V420_ENC_CFG_APPLY_EOI, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_CORE_CFG, JPEG_V420_CORE_CFG_ENC_ENABLE, 1);
+
+ return 0;
+}
+
+irqreturn_t qcom_jenc_v420_irq_bot(int irq, void *data)
+{
+ struct qcom_jenc_dev *jenc = data;
+ unsigned long flags;
+ u32 irq_status;
+
+ irq_status = READ_ONCE(jenc->pending_irq_status);
+
+ if (FIELD_GET(JPEG_V420_IRQ_STATUS_SESSION_DONE, irq_status)) {
+ struct jenc_context *ctx;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ ctx = jenc->actx;
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ if (ctx && ctx->bufq[JENC_DST_QUEUE].buff_id >= 0) {
+ struct qcom_jenc_queue *dq = &ctx->bufq[JENC_DST_QUEUE];
+ struct qcom_jpeg_buff *frame = &dq->buff[dq->buff_id];
+ size_t out_size = jpeg_rd_bits(jenc, JPEG_V420_REG_ENC_OUT_SIZE,
+ JPEG_V420_STATUS_OUT_SIZE_BYTES_MSK);
+
+ dev_dbg(jenc->dev, "complete idx:%d addr=%pad size=%zu\n",
+ dq->buff_id, &frame->plns[0].dma, out_size);
+
+ /*
+ * Stop the hardware before enc_hw_irq_cb(): it leads to
+ * v4l2_m2m_job_finish(), which can start the next job's device_run().
+ */
+ jpeg_stop(jenc);
+ jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_DONE,
+ out_size + ctx->hdr_cache.layout.size);
+ } else {
+ jpeg_stop(jenc);
+ }
+ }
+
+ if (FIELD_GET(JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK, irq_status)) {
+ struct jenc_context *ctx;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ ctx = jenc->actx;
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ dev_err(jenc->dev, "encoder hardware failure=0x%lx\n",
+ FIELD_GET(JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK, irq_status));
+
+ jpeg_stop(jenc);
+ if (ctx)
+ jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_ERROR, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t qcom_jenc_v420_irq_top(int irq, void *data)
+{
+ struct qcom_jenc_dev *jenc = data;
+ unsigned long flags;
+ u32 irq_status;
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+
+ irq_status = jpeg_io_read(jenc, JPEG_V420_REG_INT_STATUS);
+ jpeg_io_write(jenc, JPEG_V420_REG_INT_CLR, irq_status & JPEG_V420_IRQ_STATUS_ALL_BITS);
+
+ if (FIELD_GET(JPEG_V420_IRQ_STATUS_RESET_ACK, irq_status))
+ complete(&jenc->reset_complete);
+
+ if (FIELD_GET(JPEG_V420_IRQ_STATUS_STOP_ACK, irq_status)) {
+ complete(&jenc->stop_complete);
+ dev_dbg(jenc->dev, "hardware stop acknowledged\n");
+ }
+
+ /* The ACKs are handled above; wake the threaded handler only for the rest */
+ irq_status &= ~(JPEG_V420_IRQ_STATUS_RESET_ACK | JPEG_V420_IRQ_STATUS_STOP_ACK);
+ if (!irq_status) {
+ 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;
+}
+
+void qcom_jenc_v420_get_hw_caps(struct qcom_jenc_dev *jenc, u32 *caps)
+{
+ u32 hw_caps;
+
+ hw_caps = jpeg_io_read(jenc, JPEG_V420_REG_HW_CAPABILITY);
+ dev_dbg(jenc->dev, "CAPS: encode=%lu decode=%lu upscale=%lu downscale=%lu\n",
+ FIELD_GET(JPEG_V420_HW_CAP_ENCODE, hw_caps),
+ FIELD_GET(JPEG_V420_HW_CAP_DECODE, hw_caps),
+ FIELD_GET(JPEG_V420_HW_CAP_UPSCALE_MSK, hw_caps),
+ FIELD_GET(JPEG_V420_HW_CAP_DOWNSCALE_MSK, hw_caps));
+
+ *caps = hw_caps;
+}
+
+int qcom_jenc_v420_get_block_width(u32 fourcc, u32 *blk_w)
+{
+ int fmt = jpeg_get_encode_fmt(fourcc);
+
+ if (fmt < 0)
+ return fmt;
+
+ return jpeg_get_mcu_geometry(fmt, 0, 0, blk_w, NULL, NULL, NULL);
+}
+
+int qcom_jenc_v420_get_block_height(u32 fourcc, u32 *blk_h)
+{
+ int fmt = jpeg_get_encode_fmt(fourcc);
+
+ if (fmt < 0)
+ return fmt;
+
+ return jpeg_get_mcu_geometry(fmt, 0, 0, NULL, blk_h, NULL, NULL);
+}
+
+struct qcom_jenc_queue *qcom_jenc_v420_get_buff_queue(struct jenc_context *ectx,
+ enum qcom_enc_qid id)
+{
+ return &ectx->bufq[id];
+}
+
+int qcom_jenc_v420_queue_setup(struct jenc_context *ectx, enum qcom_enc_qid id)
+{
+ struct qcom_jenc_queue *q = &ectx->bufq[id];
+ int rc;
+
+ /*
+ * REQBUFS has freed the previous buffers: drop their cached sg_table
+ * and DMA addresses.
+ */
+ memset(q->buff, 0, sizeof(q->buff));
+
+ if (id == JENC_SRC_QUEUE) {
+ rc = jpeg_header_init(&ectx->hdr_cache, q->vf.pixelformat);
+ if (rc) {
+ dev_err(ectx->dev, "JFIF header init failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+int qcom_jenc_v420_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 = jpeg_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 */
+ mutex_lock(&ectx->quality_mutex);
+ ectx->quality_programmed = 0;
+ mutex_unlock(&ectx->quality_mutex);
+
+ return 0;
+}
+
+int qcom_jenc_v420_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, "cannot set up fetch engine address\n");
+ } else {
+ rc = jpeg_store_we_next(ectx, vb2);
+ if (rc)
+ dev_err(ectx->dev, "cannot set up write engine address\n");
+ }
+
+ return rc;
+}
+
+int qcom_jenc_v420_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;
+
+ jpeg_apply_fe_addr(ectx, sq, vb);
+ } 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;
+ }
+
+ /* Both src and dst buffers queued for this frame - trigger encode. */
+ 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:
+ /* HW_START was not issued: drop the addresses already queued for this job */
+ jpeg_stop(jenc);
+
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ if (jenc->actx == ectx)
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ return rc;
+}
+
+void qcom_jenc_v420_prepare(struct qcom_jenc_dev *jenc)
+{
+ jpeg_rw_bits(jenc, JPEG_V420_REG_RESET_CMD, JPEG_V420_RST_CMD_ENCODER_RESET, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_RESET_CMD, JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST, 1);
+ jpeg_rw_bits(jenc, JPEG_V420_REG_RESET_CMD, JPEG_V420_RST_CMD_CORE_RESET, 1);
+}
+
+static unsigned long jpeg_calc_optimal_clk(struct qcom_jenc_dev *jenc,
+ u32 width, u32 height, u32 fps)
+{
+ unsigned long ref_pixel_rate;
+ unsigned long optimal_clk;
+ unsigned long pixel_rate;
+
+ if (!width || !height || !fps)
+ return jenc->max_freq;
+
+ ref_pixel_rate = JPEG_REF_THROUGHPUT_MPPS * 1000000UL;
+ pixel_rate = (unsigned long)width * height * fps;
+
+ optimal_clk = DIV_ROUND_UP_ULL(pixel_rate * JPEG_REF_CLK_HZ, ref_pixel_rate);
+
+ return min_t(unsigned long, jenc->max_freq, optimal_clk);
+}
+
+int qcom_jenc_v420_retune_clk(struct jenc_context *ectx)
+{
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ unsigned long freq;
+ int rc;
+
+ freq = jpeg_calc_optimal_clk(jenc, sq->vf.width, sq->vf.height, JPEG_REF_FPS);
+
+ rc = qcom_jpeg_clk_retune(jenc, &freq);
+ if (rc)
+ return rc;
+
+ jenc->opt_freq = freq;
+
+ return 0;
+}
+
+int qcom_jenc_v420_acquire(struct jenc_context *ectx)
+{
+ struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+ struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ int rc = 0;
+
+ /* reset per-context stream state for each (re)acquire */
+ sq->sequence = 0;
+ 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.layout.size = 0;
+ /* force DQT upload on first frame after (re)acquire */
+ mutex_lock(&ectx->quality_mutex);
+ ectx->quality_programmed = 0;
+ mutex_unlock(&ectx->quality_mutex);
+
+ mutex_lock(&jenc->hw_ref_lock);
+
+ if (!jenc->ref_count) {
+ jenc->opt_freq = jpeg_calc_optimal_clk(jenc, sq->vf.width, sq->vf.height,
+ JPEG_REF_FPS);
+
+ dev_dbg(jenc->dev, "freq=%lu src=%ux%u\n", jenc->opt_freq,
+ 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");
+ goto out_unlock;
+ }
+
+ enable_irq(jenc->irq);
+
+ rc = jpeg_init(jenc);
+ if (rc) {
+ dev_err(jenc->dev, "hardware init failed\n");
+ disable_irq(jenc->irq);
+ pm_runtime_put_sync(jenc->dev);
+ goto out_unlock;
+ }
+ }
+
+ jenc->ref_count++;
+
+out_unlock:
+ mutex_unlock(&jenc->hw_ref_lock);
+
+ return rc;
+}
+
+int qcom_jenc_v420_release(struct jenc_context *ectx)
+{
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ unsigned long flags;
+ int rc = 0;
+ int pm_rc;
+
+ mutex_lock(&jenc->hw_ref_lock);
+
+ if (WARN_ON_ONCE(!jenc->ref_count))
+ goto out_unlock;
+
+ jenc->ref_count--;
+ if (!jenc->ref_count) {
+ rc = jpeg_deinit(jenc);
+ if (rc)
+ dev_err(jenc->dev, "hardware exit failed\n");
+
+ /*
+ * A job aborted by HW_STOP raises no SESSION_DONE: drop its
+ * active context reference here.
+ */
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ if (jenc->actx == ectx)
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ disable_irq(jenc->irq);
+
+ 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");
+ }
+
+out_unlock:
+ mutex_unlock(&jenc->hw_ref_lock);
+
+ return rc;
+}
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.h
new file mode 100644
index 0000000000000..1ec1ae8663c06
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_V420_H
+#define QCOM_JENC_V420_H
+
+#include <linux/irqreturn.h>
+#include <linux/types.h>
+
+#include "qcom_jenc_dev.h"
+
+void qcom_jenc_v420_get_hw_caps(struct qcom_jenc_dev *jenc, u32 *caps);
+
+int qcom_jenc_v420_get_block_width(u32 fourcc, u32 *blk_w);
+
+int qcom_jenc_v420_get_block_height(u32 fourcc, u32 *blk_h);
+
+int qcom_jenc_v420_acquire(struct jenc_context *ectx);
+
+int qcom_jenc_v420_release(struct jenc_context *ectx);
+
+void qcom_jenc_v420_prepare(struct qcom_jenc_dev *jenc);
+
+struct qcom_jenc_queue *qcom_jenc_v420_get_buff_queue(struct jenc_context *ectx,
+ enum qcom_enc_qid id);
+
+int qcom_jenc_v420_queue_setup(struct jenc_context *ectx, enum qcom_enc_qid id);
+
+int qcom_jenc_v420_src_fmt_update(struct jenc_context *ectx, u32 old_fourcc, u32 new_fourcc);
+
+int qcom_jenc_v420_retune_clk(struct jenc_context *ectx);
+
+int qcom_jenc_v420_buffer_prepare(struct jenc_context *ectx, struct vb2_buffer *vb2);
+
+int qcom_jenc_v420_process_exec(struct qcom_jenc_dev *jenc, struct jenc_context *ectx,
+ struct vb2_buffer *vb);
+
+irqreturn_t qcom_jenc_v420_irq_top(int irq_num, void *data);
+irqreturn_t qcom_jenc_v420_irq_bot(int irq_num, void *data);
+
+#endif /* QCOM_JENC_V420_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 0000000000000..4fc5eb980c5f7
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
@@ -0,0 +1,215 @@
+/* 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 <linux/bits.h>
+
+/* HW_VERSION fields */
+#define JPEG_V420_HW_VER_STEP_MSK GENMASK(15, 0)
+#define JPEG_V420_HW_VER_MINOR_MSK GENMASK(27, 16)
+#define JPEG_V420_HW_VER_MAJOR_MSK GENMASK(31, 28)
+
+/* HW_CAPABILITY fields */
+#define JPEG_V420_HW_CAP_ENCODE BIT(0)
+#define JPEG_V420_HW_CAP_DECODE BIT(1)
+#define JPEG_V420_HW_CAP_UPSCALE_MSK GENMASK(6, 4)
+#define JPEG_V420_HW_CAP_DOWNSCALE_MSK GENMASK(10, 8)
+
+/* RESET_CMD fields */
+#define JPEG_V420_RST_CMD_FE_RESET BIT(0)
+#define JPEG_V420_RST_CMD_WE_RESET BIT(1)
+#define JPEG_V420_RST_CMD_ENCODER_RESET BIT(4)
+#define JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST BIT(6)
+#define JPEG_V420_RST_CMD_SCALE_RESET BIT(7)
+#define JPEG_V420_RST_CMD_REGISTER_RESET BIT(13)
+#define JPEG_V420_RST_CMD_MISR_RESET BIT(16)
+#define JPEG_V420_RST_CMD_CORE_RESET BIT(17)
+#define JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET BIT(29)
+#define JPEG_V420_RST_CMD_ALL_BITS \
+ (JPEG_V420_RST_CMD_FE_RESET | \
+ JPEG_V420_RST_CMD_WE_RESET | \
+ JPEG_V420_RST_CMD_ENCODER_RESET | \
+ JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST | \
+ JPEG_V420_RST_CMD_SCALE_RESET | \
+ JPEG_V420_RST_CMD_REGISTER_RESET | \
+ JPEG_V420_RST_CMD_MISR_RESET | \
+ JPEG_V420_RST_CMD_CORE_RESET | \
+ JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET)
+
+/* CORE_CFG fields */
+#define JPEG_V420_CORE_CFG_FE_ENABLE BIT(0)
+#define JPEG_V420_CORE_CFG_WE_ENABLE BIT(1)
+#define JPEG_V420_CORE_CFG_ENC_ENABLE BIT(4)
+#define JPEG_V420_CORE_CFG_SCALE_ENABLE BIT(7)
+#define JPEG_V420_CORE_CFG_MODE BIT(24)
+
+/* HW_CMD fields */
+#define JPEG_V420_CMD_HW_START BIT(0)
+#define JPEG_V420_CMD_HW_STOP BIT(1)
+#define JPEG_V420_CMD_CLR_RD_PLNS_QUEUE_MSK GENMASK(6, 4)
+#define JPEG_V420_CMD_CLR_WR_PLNS_QUEUE_MSK GENMASK(10, 8)
+
+/* FE_CFG fields */
+#define JPEG_V420_FE_CFG_MEMORY_FORMAT_MSK GENMASK(9, 8)
+#define JPEG_V420_FE_CFG_CBCR_ORDER BIT(12)
+#define JPEG_V420_FE_CFG_BOTTOM_VPAD_EN BIT(13)
+#define JPEG_V420_FE_CFG_PLN0_EN BIT(16)
+#define JPEG_V420_FE_CFG_PLN1_EN BIT(17)
+#define JPEG_V420_FE_CFG_PLN2_EN BIT(18)
+#define JPEG_V420_FE_CFG_SIXTEEN_MCU_EN BIT(21)
+#define JPEG_V420_FE_CFG_MCUS_PER_BLOCK BIT(22)
+#define JPEG_V420_FE_CFG_MAL_BOUNDARY BIT(24)
+#define JPEG_V420_FE_CFG_MAL_EN BIT(27)
+#define JPEG_V420_FE_CFG_PLNS_EN_MSK GENMASK(18, 16)
+
+/* FE per-plane BSIZE/STRIDE fields */
+#define JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK GENMASK(15, 0)
+#define JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK GENMASK(31, 16)
+#define JPEG_V420_PLN_RD_STRIDE_MSK GENMASK(15, 0)
+
+/* WE_CFG fields */
+#define JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK GENMASK(9, 8)
+#define JPEG_V420_WE_CFG_CBCR_ORDER BIT(12)
+#define JPEG_V420_WE_CFG_PLN0_EN BIT(16)
+#define JPEG_V420_WE_CFG_MAL_BOUNDARY BIT(24)
+#define JPEG_V420_WE_CFG_MAL_EN BIT(27)
+#define JPEG_V420_WE_CFG_POP_BUFF_ON_EOS BIT(28)
+
+/* WE per-plane STRIDE/HINIT/VINIT/HSTEP/VSTEP/BLOCKS fields */
+#define JPEG_V420_PLN_WR_STRIDE_MSK GENMASK(15, 0)
+#define JPEG_V420_PLN_WR_HINIT_INTEGER_MSK GENMASK(15, 0)
+#define JPEG_V420_PLN_WR_VINIT_INTEGER_MSK GENMASK(15, 0)
+#define JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK GENMASK(16, 0)
+#define JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK GENMASK(16, 0)
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_MSK GENMASK(15, 0)
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_MSK GENMASK(31, 16)
+
+/* ENC_CFG fields */
+#define JPEG_V420_ENC_CFG_IMAGE_FORMAT_MSK GENMASK(2, 0)
+#define JPEG_V420_ENC_CFG_APPLY_EOI BIT(7)
+
+/* ENC_IMG_SIZE fields */
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK GENMASK(12, 0)
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK GENMASK(28, 16)
+
+/* ENC_OUT_SIZE field */
+#define JPEG_V420_STATUS_OUT_SIZE_BYTES_MSK GENMASK(28, 0)
+
+/* SCALE_CFG fields */
+#define JPEG_V420_SCALE_CFG_HSCALE_ENABLE BIT(4)
+#define JPEG_V420_SCALE_CFG_VSCALE_ENABLE BIT(5)
+
+/* SCALE_OUT_CFG fields */
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_MSK GENMASK(7, 0)
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_MSK GENMASK(19, 16)
+
+/* DMI_CFG field */
+#define JPEG_V420_DMI_CFG_AUTO_INC_EN BIT(4)
+
+/* DMI_CFG value to select quantization table memory and enable auto-increment */
+#define JPEG_DMI_ENABLE_UPLOAD (JPEG_V420_DMI_CFG_AUTO_INC_EN | 0x1u)
+
+/* FE_VBPAD_CFG field */
+#define JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK GENMASK(12, 0)
+
+/* INT_STATUS/INT_MASK/INT_CLR fields */
+#define JPEG_V420_IRQ_STATUS_SESSION_DONE BIT(0)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE BIT(10)
+#define JPEG_V420_IRQ_STATUS_STOP_ACK BIT(27)
+#define JPEG_V420_IRQ_STATUS_RESET_ACK BIT(28)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_DONE_MSK GENMASK(6, 4)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK GENMASK(9, 7)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK GENMASK(15, 13)
+#define JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK GENMASK(26, 19)
+#define JPEG_V420_IRQ_STATUS_ALL_BITS \
+ (JPEG_V420_IRQ_STATUS_SESSION_DONE | \
+ JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_DONE_MSK | \
+ JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE | \
+ 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 | \
+ JPEG_V420_IRQ_STATUS_RESET_ACK)
+
+/* Register offsets for JPEG v4.2.0 hardware */
+#define JPEG_V420_REG_HW_VERSION 0x000
+#define JPEG_V420_REG_HW_CAPABILITY 0x004
+#define JPEG_V420_REG_RESET_CMD 0x008
+#define JPEG_V420_REG_CORE_CFG 0x00c
+#define JPEG_V420_REG_HW_CMD 0x010
+#define JPEG_V420_REG_INT_MASK 0x018
+#define JPEG_V420_REG_INT_CLR 0x01c
+#define JPEG_V420_REG_INT_STATUS 0x020
+#define JPEG_V420_REG_FE_CFG 0x024
+
+/* FE (fetch engine) plane registers */
+#define JPEG_V420_REG_FE_PNTR_0 0x038
+#define JPEG_V420_REG_FE_PNTR_1 0x044
+#define JPEG_V420_REG_FE_PNTR_2 0x050
+#define JPEG_V420_REG_FE_OFFS_0 0x03c
+#define JPEG_V420_REG_FE_OFFS_1 0x048
+#define JPEG_V420_REG_FE_OFFS_2 0x054
+#define JPEG_V420_REG_FE_BSIZE_0 0x060
+#define JPEG_V420_REG_FE_BSIZE_1 0x068
+#define JPEG_V420_REG_FE_BSIZE_2 0x070
+#define JPEG_V420_REG_FE_STRIDE_0 0x064
+#define JPEG_V420_REG_FE_STRIDE_1 0x06c
+#define JPEG_V420_REG_FE_STRIDE_2 0x08c
+#define JPEG_V420_REG_FE_HINIT_0 0x074
+#define JPEG_V420_REG_FE_HINIT_1 0x078
+#define JPEG_V420_REG_FE_HINIT_2 0x07c
+#define JPEG_V420_REG_FE_VINIT_0 0x080
+#define JPEG_V420_REG_FE_VINIT_1 0x084
+#define JPEG_V420_REG_FE_VINIT_2 0x088
+#define JPEG_V420_REG_FE_VBPAD_CFG 0x2e8
+
+/* WE (write engine) plane registers */
+#define JPEG_V420_REG_WE_CFG 0x0c0
+#define JPEG_V420_REG_WE_PNTR_0 0x0cc
+#define JPEG_V420_REG_WE_PNTR_1 0x0d0
+#define JPEG_V420_REG_WE_PNTR_2 0x0d4
+#define JPEG_V420_REG_WE_BSIZE_0 0x0e8
+#define JPEG_V420_REG_WE_STRIDE_0 0x0f4
+#define JPEG_V420_REG_WE_STRIDE_1 0x0f8
+#define JPEG_V420_REG_WE_STRIDE_2 0x0fc
+#define JPEG_V420_REG_WE_HINIT_0 0x100
+#define JPEG_V420_REG_WE_HINIT_1 0x104
+#define JPEG_V420_REG_WE_HINIT_2 0x108
+#define JPEG_V420_REG_WE_VINIT_0 0x10c
+#define JPEG_V420_REG_WE_VINIT_1 0x110
+#define JPEG_V420_REG_WE_VINIT_2 0x114
+#define JPEG_V420_REG_WE_HSTEP_0 0x118
+#define JPEG_V420_REG_WE_HSTEP_1 0x11c
+#define JPEG_V420_REG_WE_HSTEP_2 0x120
+#define JPEG_V420_REG_WE_VSTEP_0 0x124
+#define JPEG_V420_REG_WE_VSTEP_1 0x128
+#define JPEG_V420_REG_WE_VSTEP_2 0x12c
+#define JPEG_V420_REG_WE_BLOCKS_0 0x130
+
+/* Encoder registers */
+#define JPEG_V420_REG_ENC_CFG 0x13c
+#define JPEG_V420_REG_ENC_IMG_SIZE 0x140
+#define JPEG_V420_REG_ENC_OUT_SIZE 0x180
+
+/* Scaler registers */
+#define JPEG_V420_REG_SCALE_CFG 0x26c
+#define JPEG_V420_REG_SCALE_OUT_CFG_0 0x270
+#define JPEG_V420_REG_SCALE_OUT_CFG_1 0x274
+#define JPEG_V420_REG_SCALE_OUT_CFG_2 0x278
+#define JPEG_V420_REG_SCALE_HSTEP_0 0x27c
+#define JPEG_V420_REG_SCALE_HSTEP_1 0x280
+#define JPEG_V420_REG_SCALE_HSTEP_2 0x284
+#define JPEG_V420_REG_SCALE_VSTEP_0 0x28c
+#define JPEG_V420_REG_SCALE_VSTEP_1 0x290
+#define JPEG_V420_REG_SCALE_VSTEP_2 0x294
+
+/* DMI registers */
+#define JPEG_V420_REG_DMI_CFG 0x298
+#define JPEG_V420_REG_DMI_ADDR 0x29c
+#define JPEG_V420_REG_DMI_DATA 0x2a0
+
+#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 0000000000000..b068a9d2322ed
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
@@ -0,0 +1,1176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ioctl.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_v420.h"
+#include "qcom_jenc_v4l2.h"
+
+/* JPEG hardware constraints */
+#define QCOM_JPEG_HW_MAX_WIDTH 8192
+#define QCOM_JPEG_HW_MAX_HEIGHT 8192
+#define QCOM_JPEG_HW_MIN_WIDTH 256
+#define QCOM_JPEG_HW_MIN_HEIGHT 256
+#define QCOM_JPEG_HW_DEF_HSTEP 8
+#define QCOM_JPEG_HW_DEF_VSTEP 1
+#define QCOM_JPEG_HW_DEF_WIDTH 1920
+#define QCOM_JPEG_HW_DEF_HEIGHT 1088
+
+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)
+{
+ return container_of(file_to_v4l2_fh(file), 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_dbg(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_dbg(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)
+{
+ const struct v4l2_format_info *info = v4l2_format_info(ef->fourcc);
+ struct v4l2_pix_format_mplane *f = &v4f->fmt.pix_mp;
+ u32 blk_w, blk_h, height, buf_height;
+ 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);
+
+ rc = qcom_jenc_v420_get_block_width(ef->fourcc, &blk_w);
+ if (rc)
+ return rc;
+
+ rc = qcom_jenc_v420_get_block_height(ef->fourcc, &blk_h);
+ if (rc)
+ return rc;
+
+ /*
+ * Width crops down to the nearest full MCU block instead of
+ * rounding up: the fetch engine has no horizontal pad register.
+ */
+ f->width = clamp_t(u32, f->width, QCOM_JPEG_HW_MIN_WIDTH, QCOM_JPEG_HW_MAX_WIDTH);
+ f->width = ALIGN_DOWN(f->width, blk_w);
+
+ /*
+ * FE_VBPAD_CFG pads the last partial block row of the encoded image,
+ * but the fetch engine still reads whole block rows from memory, so
+ * the planes are sized for the height rounded up to the block.
+ */
+ height = clamp_t(u32, f->height, QCOM_JPEG_HW_MIN_HEIGHT, QCOM_JPEG_HW_MAX_HEIGHT);
+ height = round_up(height, info->vdiv);
+ buf_height = round_up(height, blk_h);
+
+ rc = v4l2_fill_pixfmt_mp(f, ef->fourcc, f->width, buf_height);
+ if (rc)
+ return rc;
+
+ f->height = height;
+
+ 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;
+ u32 nbx, nby;
+ u64 size;
+
+ 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);
+
+ f->width = clamp_t(u32, f->width, QCOM_JPEG_HW_MIN_WIDTH, QCOM_JPEG_HW_MAX_WIDTH);
+ f->width = ALIGN_DOWN(f->width, QCOM_JPEG_HW_DEF_HSTEP);
+
+ f->height = clamp_t(u32, f->height, QCOM_JPEG_HW_MIN_HEIGHT, QCOM_JPEG_HW_MAX_HEIGHT);
+
+ /*
+ * 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 += JPEG_HEADER_MAX; /* JPEG header written by CPU before HW DMA */
+ size += SZ_4K; /* safety margin and alignment */
+
+ 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 %p4cc\n",
+ TYPE2QID(f->type), &ef->fourcc);
+
+ 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 {
+ 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)
+{
+ 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;
+
+ /*
+ * OUTPUT TRY_FMT may crop the width to the block size; keep the
+ * CAPTURE size equal to the negotiated OUTPUT size.
+ */
+ 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;
+ }
+ }
+
+ } 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;
+ }
+ }
+
+ q->vf = *pm;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+ rc = qcom_jenc_v420_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_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_v4l2_work_done(struct jenc_context *ctx, size_t out_size)
+{
+ struct vb2_v4l2_buffer *vb;
+ bool last = false;
+
+ vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ if (vb) {
+ last = v4l2_m2m_is_last_draining_src_buf(ctx->fh.m2m_ctx, 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);
+ if (last) {
+ vb->flags |= V4L2_BUF_FLAG_LAST;
+ v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx);
+ }
+ v4l2_m2m_buf_done(vb, VB2_BUF_STATE_DONE);
+ }
+
+ schedule_work(&ctx->finish_work);
+}
+
+static void jpeg_v4l2_return_bufs(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
+{
+ bool draining = ctx->fh.m2m_ctx->is_draining;
+ struct vb2_v4l2_buffer *vb;
+
+ /* Drain CAPTURE queue; signal EOS on last buffer if V4L2_ENC_CMD_STOP. */
+ while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
+ if (draining && !v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {
+ 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);
+}
+
+static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
+{
+ jpeg_v4l2_return_bufs(ctx, buff_state);
+
+ /* Always call job_finish to let m2m schedule the next job. */
+ 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 jpeg_v4l2_s_ctrl(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, "invalid control id=%#x\n", ctrl->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops qcom_jpeg_v4l2_ctrl_ops = {
+ .s_ctrl = jpeg_v4l2_s_ctrl,
+};
+
+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_queue *q;
+ int pln;
+
+ q = qcom_jenc_v420_get_buff_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, "queue=%d size[%d]=%d\n", TYPE2QID(vq->type),
+ pln, sizes[pln]);
+ }
+
+ return qcom_jenc_v420_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_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;
+
+ if (vb->planes[pln].data_offset)
+ return -EINVAL;
+ }
+
+ rc = qcom_jenc_v420_buffer_prepare(ectx, vb);
+ if (rc) {
+ dev_err_ratelimited(ectx->dev, "buffer prepare failed\n");
+ 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);
+
+ /* Drain pending with no CAPTURE buffer: return this one empty as LAST */
+ if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) && vb2_is_streaming(vb->vb2_queue) &&
+ v4l2_m2m_dst_buf_is_last(ectx->fh.m2m_ctx)) {
+ vb2_set_plane_payload(vb, 0, 0);
+ v4l2_m2m_last_buffer_done(ectx->fh.m2m_ctx, vbuf);
+ return;
+ }
+
+ v4l2_m2m_buf_queue(ectx->fh.m2m_ctx, vbuf);
+}
+
+static void jpeg_v4l2_buffers_done(struct jenc_context *ectx, u32 type, enum vb2_buffer_state state)
+{
+ struct vb2_v4l2_buffer *vb;
+
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ while ((vb = v4l2_m2m_src_buf_remove(ectx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(vb, state);
+ } else {
+ while ((vb = v4l2_m2m_dst_buf_remove(ectx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(vb, state);
+ }
+}
+
+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_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+ struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ u32 hw_caps;
+ u8 pln;
+ int rc;
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+ dev_dbg(ectx->dev, "%p4cc %dx%d\n",
+ &sq->vf.pixelformat, 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, "%p4cc %dx%d\n",
+ &dq->vf.pixelformat,
+ dq->vf.width, dq->vf.height);
+ }
+
+ mutex_lock(&jenc->dev_mutex);
+
+ v4l2_m2m_update_start_streaming_state(ectx->fh.m2m_ctx, q);
+ mutex_unlock(&jenc->dev_mutex);
+
+ /*
+ * Header cache is initialized lazily when the first job runs, so valid V4L2
+ * orders like STREAMON before first QBUF still get a proper JPEG header.
+ */
+ if (!ectx->hw_acquired) {
+ rc = qcom_jenc_v420_acquire(ectx);
+ if (!rc) {
+ ectx->hw_acquired = true;
+ qcom_jenc_v420_get_hw_caps(jenc, &hw_caps);
+ dev_dbg(ectx->dev, "hw_caps=0x%x\n", hw_caps);
+ }
+ } else {
+ rc = 0;
+ }
+
+ if (rc)
+ jpeg_v4l2_buffers_done(ectx, q->type, VB2_BUF_STATE_QUEUED);
+
+ 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;
+ bool do_release;
+
+ mutex_lock(&jenc->dev_mutex);
+
+ do_release = ectx->hw_acquired;
+ ectx->hw_acquired = false;
+
+ mutex_unlock(&jenc->dev_mutex);
+
+ /*
+ * qcom_jenc_v420_release() calls jpeg_deinit() which issues HW_STOP and
+ * waits for the STOP_ACK interrupt completion. It runs before any buffer
+ * is returned below and outside dev_mutex: the threaded IRQ handler takes
+ * dev_mutex, and with IRQF_ONESHOT the STOP_ACK interrupt stays masked
+ * until that handler returns.
+ */
+ if (do_release)
+ qcom_jenc_v420_release(ectx);
+
+ mutex_lock(&jenc->dev_mutex);
+ jpeg_v4l2_return_bufs(ectx, VB2_BUF_STATE_ERROR);
+ v4l2_m2m_update_stop_streaming_state(ectx->fh.m2m_ctx, 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_run(void *priv)
+{
+ struct jenc_context *ectx = priv;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ 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 (qcom_jenc_v420_retune_clk(ectx))
+ goto err_stop;
+
+ qcom_jenc_v420_prepare(jenc);
+
+ sq = qcom_jenc_v420_get_buff_queue(ectx, TYPE2QID(src_vb->vb2_buf.type));
+ src_vb->sequence = sq->sequence++;
+ if (qcom_jenc_v420_process_exec(jenc, ectx, &src_vb->vb2_buf))
+ goto err_stop;
+
+ dq = qcom_jenc_v420_get_buff_queue(ectx, TYPE2QID(dst_vb->vb2_buf.type));
+ dst_vb->sequence = dq->sequence++;
+ if (qcom_jenc_v420_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,
+};
+
+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 v4l2_format_info *info;
+ const struct jenc_enc_format *efmt;
+ u32 blk_w;
+
+ if (fsize->index != 0)
+ return -EINVAL;
+
+ efmt = jpeg_find_pix_format(JENC_SRC_QUEUE, fsize->pixel_format);
+ if (efmt) {
+ info = v4l2_format_info(efmt->fourcc);
+ if (!info || qcom_jenc_v420_get_block_width(efmt->fourcc, &blk_w))
+ return -EINVAL;
+
+ fsize->type = jpeg_def_frmsize.type;
+ fsize->stepwise = jpeg_def_frmsize.stepwise;
+ fsize->stepwise.step_width = blk_w;
+ fsize->stepwise.step_height = info->vdiv;
+ 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;
+
+ 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;
+ }
+
+ 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;
+ /* The CPU writes the JPEG header, the hardware the bitstream */
+ dq->bidirectional = true;
+ 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 qcom_jenc_dev *jenc = video_drvdata(file);
+ struct video_device *vdev = video_devdata(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);
+
+ 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:
+ mutex_destroy(&ectx->quality_mutex);
+ mutex_destroy(&ectx->ctx_lock);
+ 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;
+ struct qcom_jenc_dev *jenc = ectx->jenc;
+ unsigned long flags;
+
+ /*
+ * Waits until a running job of this context completes: its
+ * SESSION_DONE interrupt needs jenc->actx, and finish_work must not
+ * be cancelled before, as it calls v4l2_m2m_job_finish().
+ */
+ v4l2_m2m_ctx_release(ectx->fh.m2m_ctx);
+
+ /*
+ * Ensure the threaded IRQ handler cannot dereference this context
+ * after it is freed. Clear jenc->actx under hw_lock before
+ * cancelling work.
+ */
+ spin_lock_irqsave(&jenc->hw_lock, flags);
+ if (jenc->actx == ectx)
+ jenc->actx = NULL;
+ spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+ cancel_work_sync(&ectx->finish_work);
+
+ v4l2_m2m_put(m2m_dev);
+ v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
+ v4l2_fh_del(&ectx->fh, file);
+ v4l2_fh_exit(&ectx->fh);
+ mutex_destroy(&ectx->quality_mutex);
+ mutex_destroy(&ectx->ctx_lock);
+ 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,
+};
+
+static void qcom_jpeg_m2m_put(void *data)
+{
+ struct qcom_jenc_dev *jenc = data;
+
+ v4l2_m2m_put(jenc->m2m_dev);
+}
+
+static void qcom_jpeg_video_unregister(void *data)
+{
+ struct qcom_jenc_dev *jenc = data;
+
+ video_unregister_device(jenc->vdev);
+}
+
+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;
+ }
+
+ rc = devm_add_action_or_reset(jenc->dev, qcom_jpeg_m2m_put, jenc);
+ if (rc)
+ goto err_mutex_unlock;
+
+ jenc->vdev = video_device_alloc();
+ if (!jenc->vdev) {
+ rc = -ENOMEM;
+ goto err_mutex_unlock;
+ }
+
+ 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;
+ video_set_drvdata(jenc->vdev, jenc);
+
+ 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;
+ }
+
+ rc = devm_add_action_or_reset(jenc->dev, qcom_jpeg_video_unregister, jenc);
+ if (rc)
+ goto err_mutex_unlock;
+
+ mutex_unlock(&jenc->dev_mutex);
+
+ dev_dbg(jenc->dev, "device registered as /dev/video%d\n", jenc->vdev->num);
+
+ return 0;
+
+err_video_device_release:
+ video_device_release(jenc->vdev);
+err_mutex_unlock:
+ mutex_unlock(&jenc->dev_mutex);
+
+ return rc;
+}
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 0000000000000..75af7e265c0d4
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
@@ -0,0 +1,13 @@
+/* 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
+
+struct qcom_jenc_dev;
+
+int qcom_jpeg_v4l2_register(struct qcom_jenc_dev *jenc);
+
+#endif /* QCOM_JENC_V4L2_H */
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 5/8] arm64: dts: qcom: sm8250: Add JPEG encoder node
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
` (3 preceding siblings ...)
2026-11-06 8:25 ` [PATCH v7 4/8] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 6/8] arm64: dts: qcom: qrb5165-rb5: Enable JPEG encoder Atanas Filipov
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add the JPEG encoder hardware node to the SM8250 device tree so the
qcom-jpeg V4L2 encoder driver can bind and operate on this platform.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/sm8250.dtsi | 74 ++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi
index f975c2183269a..0ad0935d72b65 100644
--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
@@ -4480,6 +4480,80 @@ cci1_i2c1: i2c-bus@1 {
};
};
+ jpeg_enc: jpeg-encoder@ac53000 {
+ compatible = "qcom,sm8250-jpeg-enc";
+ status = "disabled";
+
+ reg = <0x0 0x0ac53000 0x0 0x1000>;
+
+ interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING>;
+
+ power-domains = <&camcc TITAN_TOP_GDSC>,
+ <&rpmhpd RPMHPD_MX>,
+ <&rpmhpd RPMHPD_MMCX>;
+ power-domain-names = "titan_top", "mx", "mmcx";
+
+ clocks = <&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_CAMNOC_AXI_CLK_SRC>,
+ <&camcc CAM_CC_JPEG_CLK>;
+
+ clock-names = "bus",
+ "config",
+ "cpas",
+ "camnoc_axi",
+ "camnoc_axi_src",
+ "core";
+
+ interconnects = <&mmss_noc MASTER_CAMNOC_SF 0 &mc_virt SLAVE_EBI_CH0 0>;
+ interconnect-names = "camnoc";
+
+ iommus = <&apps_smmu 0x2040 0x400>;
+
+ operating-points-v2 = <&jpeg_opp_table>;
+
+ jpeg_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-100000000 {
+ opp-hz = /bits/ 64 <100000000>;
+ opp-peak-kBps = <262560>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-200000000 {
+ opp-hz = /bits/ 64 <200000000>;
+ opp-peak-kBps = <524760>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_svs>;
+ };
+
+ opp-400000000 {
+ opp-hz = /bits/ 64 <400000000>;
+ opp-peak-kBps = <978600>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_svs_l1>;
+ };
+
+ opp-480000000 {
+ opp-hz = /bits/ 64 <480000000>;
+ opp-peak-kBps = <1095600>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_nom>;
+ };
+
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-peak-kBps = <1095600>;
+ required-opps = <&rpmhpd_opp_svs_l1>,
+ <&rpmhpd_opp_turbo>;
+ };
+ };
+ };
+
camss: camss@ac6a000 {
compatible = "qcom,sm8250-camss";
status = "disabled";
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 6/8] arm64: dts: qcom: qrb5165-rb5: Enable JPEG encoder
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
` (4 preceding siblings ...)
2026-11-06 8:25 ` [PATCH v7 5/8] arm64: dts: qcom: sm8250: Add JPEG encoder node Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 7/8] arm64: dts: qcom: kodiak: Add JPEG encoder node Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 8/8] arm64: dts: qcom: qcs6490-rb3gen2: Enable JPEG encoder Atanas Filipov
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Enable the JPEG encoder on the RB5 board.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/qrb5165-rb5.dts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
index d6af679abe1a3..949328806a9de 100644
--- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
+++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
@@ -668,6 +668,10 @@ redriver_usb_con_sbu: endpoint {
};
};
+&jpeg_enc {
+ status = "okay";
+};
+
&mdss {
status = "okay";
};
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 7/8] arm64: dts: qcom: kodiak: Add JPEG encoder node
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
` (5 preceding siblings ...)
2026-11-06 8:25 ` [PATCH v7 6/8] arm64: dts: qcom: qrb5165-rb5: Enable JPEG encoder Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 8/8] arm64: dts: qcom: qcs6490-rb3gen2: Enable JPEG encoder Atanas Filipov
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Add the JPEG encoder hardware node to the SC7280 (Kodiak) device tree
so the qcom-jpeg V4L2 encoder driver can bind and operate on this
platform. The node is disabled by default.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/kodiak.dtsi | 69 ++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
index 8c7523602d271..463ca9a654365 100644
--- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
+++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
@@ -5164,6 +5164,75 @@ cci1_i2c1: i2c-bus@1 {
};
};
+ jpeg_enc: jpeg-encoder@ac4e000 {
+ compatible = "qcom,sc7280-jpeg-enc";
+ status = "disabled";
+
+ reg = <0x0 0x0ac4e000 0x0 0x1000>;
+
+ interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING>;
+
+ power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>,
+ <&rpmhpd SC7280_CX>;
+ power-domain-names = "titan_top", "cx";
+
+ clocks = <&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_CAMNOC_AXI_CLK_SRC>,
+ <&camcc CAM_CC_JPEG_CLK>;
+
+ clock-names = "bus",
+ "config",
+ "cpas",
+ "camnoc_axi",
+ "camnoc_axi_src",
+ "core";
+
+ interconnects = <&mmss_noc MASTER_CAMNOC_SF QCOM_ICC_TAG_ALWAYS
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>;
+ interconnect-names = "camnoc";
+
+ iommus = <&apps_smmu 0x20c0 0x20>;
+
+ operating-points-v2 = <&jpeg_opp_table>;
+
+ jpeg_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-100000000 {
+ opp-hz = /bits/ 64 <100000000>;
+ opp-peak-kBps = <258480>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-200000000 {
+ opp-hz = /bits/ 64 <200000000>;
+ opp-peak-kBps = <531480>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-400000000 {
+ opp-hz = /bits/ 64 <400000000>;
+ opp-peak-kBps = <1006560>;
+ required-opps = <&rpmhpd_opp_svs>;
+ };
+
+ opp-480000000 {
+ opp-hz = /bits/ 64 <480000000>;
+ opp-peak-kBps = <1084440>;
+ required-opps = <&rpmhpd_opp_svs_l1>;
+ };
+
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-peak-kBps = <1084440>;
+ required-opps = <&rpmhpd_opp_nom>;
+ };
+ };
+ };
+
camss: isp@acb3000 {
compatible = "qcom,sc7280-camss";
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 8/8] arm64: dts: qcom: qcs6490-rb3gen2: Enable JPEG encoder
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
` (6 preceding siblings ...)
2026-11-06 8:25 ` [PATCH v7 7/8] arm64: dts: qcom: kodiak: Add JPEG encoder node Atanas Filipov
@ 2026-11-06 8:25 ` Atanas Filipov
7 siblings, 0 replies; 11+ messages in thread
From: Atanas Filipov @ 2026-11-06 8:25 UTC (permalink / raw)
To: linux-media
Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel, atanas.filipov
Enable the JPEG encoder on the RB3 Gen2 board.
Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
index 909039816a216..b96214758ce13 100644
--- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
+++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
@@ -789,6 +789,10 @@ redriver_usb_con_sbu: endpoint {
};
};
+&jpeg_enc {
+ status = "okay";
+};
+
&lpass_va_macro {
status = "okay";
};
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding
2026-11-06 8:25 ` [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
@ 2026-11-06 13:31 ` Rob Herring
2026-11-06 14:42 ` Bryan O'Donoghue
0 siblings, 1 reply; 11+ messages in thread
From: Rob Herring @ 2026-11-06 13:31 UTC (permalink / raw)
To: Atanas Filipov
Cc: linux-media, bod, mchehab, krzk+dt, conor+dt, andersson,
konradybcio, loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel
On Fri, Nov 06, 2026 at 10:25:15AM +0200, Atanas Filipov wrote:
> Add device-tree binding for the Qualcomm JPEG encoder hardware block
> present in SM8250 (Kona) and SC7280 (Kodiak) SoCs.
>
> The JPEG encoder is a standalone hardware IP within the camera subsystem
> that performs JPEG compression in memory-to-memory fashion. It is
> separate from the CAMSS ISP pipeline and has its own register space,
> interrupt, clocks, power domains, IOMMU stream, and interconnect path.
>
> Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
> ---
> .../bindings/media/qcom,sm8250-jpeg-enc.yaml | 193 ++++++++++++++++++
> 1 file changed, 193 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml
>
> diff --git a/Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml b/Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml
> new file mode 100644
> index 0000000000000..86b17575600c5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,sm8250-jpeg-enc.yaml
> @@ -0,0 +1,193 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/qcom,sm8250-jpeg-enc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm JPEG Encoder
> +
> +maintainers:
> + - Atanas Filipov <atanas.filipov@oss.qualcomm.com>
> +
> +description:
> + Qualcomm JPEG Encoder is the JPEG encode hardware present in Qualcomm SoCs.
> + The encoder is a standalone hardware IP within the camera subsystem that
> + performs JPEG compression in memory-to-memory fashion.
> +
> +properties:
> + compatible:
> + enum:
> + - qcom,sc7280-jpeg-enc
> + - qcom,sm8250-jpeg-enc
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 6
> +
> + clock-names:
> + items:
> + - const: bus
> + - const: config
> + - const: cpas
> + - const: camnoc_axi
> + - const: camnoc_axi_src
These 2 don't sound like clocks that are part of this block. And 'src'
sounds like the parent clock? Use assigned-clocks for parent clocks (or
just retrieve the parent from the clk api).
> + - const: core
I would expect the main clock for the block to come first in the list,
not last.
Rob
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding
2026-11-06 13:31 ` Rob Herring
@ 2026-11-06 14:42 ` Bryan O'Donoghue
0 siblings, 0 replies; 11+ messages in thread
From: Bryan O'Donoghue @ 2026-11-06 14:42 UTC (permalink / raw)
To: Rob Herring, Atanas Filipov
Cc: linux-media, mchehab, krzk+dt, conor+dt, andersson, konradybcio,
loic.poulain, dmitry.baryshkov, gjorgji.rosikopulos,
linux-arm-msm, devicetree, linux-kernel
On 06/11/2026 13:31, Rob Herring wrote:
>> + - const: camnoc_axi
>> + - const: camnoc_axi_src
> These 2 don't sound like clocks that are part of this block. And 'src'
> sounds like the parent clock? Use assigned-clocks for parent clocks (or
> just retrieve the parent from the clk api).
_src is a parent clock indeed.
---
bod
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-06 14:42 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 8:25 [PATCH v7 0/8] Add Qualcomm JPEG V4L2 encoder for SM8250 and SC7280 Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 1/8] dt-bindings: media: qcom: Add JPEG encoder binding Atanas Filipov
2026-11-06 13:31 ` Rob Herring
2026-11-06 14:42 ` Bryan O'Donoghue
2026-11-06 8:25 ` [PATCH v7 2/8] media: jpeg: Add JPEG baseline and JFIF APP0 constants Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 3/8] media: v4l2-jpeg: Add a baseline JPEG header writer Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 4/8] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 5/8] arm64: dts: qcom: sm8250: Add JPEG encoder node Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 6/8] arm64: dts: qcom: qrb5165-rb5: Enable JPEG encoder Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 7/8] arm64: dts: qcom: kodiak: Add JPEG encoder node Atanas Filipov
2026-11-06 8:25 ` [PATCH v7 8/8] arm64: dts: qcom: qcs6490-rb3gen2: Enable JPEG encoder Atanas Filipov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®